/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.    
 */
#include "apache/das/rdb/ApplyChanges.h"

namespace apache {
	namespace das {
		namespace rdb {

ApplyChanges::ApplyChanges(DASImpl& das, commonj::sdo::DataObjectPtr root) : summary(root->getChangeSummary()) {
	this->config = (const ConfigImpl*) &das.getConfig();
	this->root = root;
	processRootDataObject(root);
	StatementPtr stmt = *das.getConnection()->createStatement();

	try {

		for (std::list<DASDataObject*>::iterator it = updateOrderList.begin() ;
			it != updateOrderList.end() ; it++) {

				(*it)->execute(stmt);
				
		}

		stmt->executeQuery("commit");

	} catch (...) {
		stmt->executeQuery("rollback");	
		throw;

	}
	
}

ApplyChanges::~ApplyChanges(void) {
	
	for (std::map<std::string, const Relationship*>::iterator relationshipsIterator = relationships.begin() ;
		relationshipsIterator != relationships.end() ; relationshipsIterator++) {

			delete relationshipsIterator->second;

	}

	for (std::map<std::string, const Table*>::iterator tablesIterator = tables.begin() ;
		tablesIterator != tables.end() ; tablesIterator++) {

			delete tablesIterator->second;

	}

	for (std::map<std::string, std::list<DASDataObject*>*>::iterator dasDataObjectsIterator = dasDataObjects.begin() ;
		dasDataObjectsIterator != dasDataObjects.end() ; dasDataObjectsIterator++) {

			std::list<DASDataObject*>* list = dasDataObjectsIterator->second;

			for (std::list<DASDataObject*>::iterator listIterator = list->begin() ;
				listIterator != list->end() ; listIterator++) {

					delete *listIterator;

			}

			delete list;

	}

}

void ApplyChanges::processRootDataObject(commonj::sdo::DataObjectPtr dataObject) {
	commonj::sdo::ChangedDataObjectList& changedObjects = summary->getChangedDataObjects();
	std::list<commonj::sdo::DataObjectPtr> createdObjects;
	std::list<commonj::sdo::DataObjectPtr> deletedObjects;
	std::list<commonj::sdo::DataObjectPtr> modifiedObjects;
	
	for (unsigned int i = 0 ; i < changedObjects.size() ; i++) {
		
		if (changedObjects[i] == root) {
			continue;
		}
		
		processMetadata(changedObjects[i]);

		if (summary->isCreated(changedObjects[i])) {
			createdObjects.push_back(changedObjects[i]);

		} else if (summary->isDeleted(changedObjects[i])) {
			deletedObjects.push_back(changedObjects[i]);

		} else {
			modifiedObjects.push_back(changedObjects[i]);
		}

		//const commonj::sdo::SettingList& setting = summary->getOldValues(changedObjects[i]);
		
	}

	std::list<commonj::sdo::DataObjectPtr>::iterator deletedIterator;
	for (deletedIterator = deletedObjects.begin() ; deletedIterator != deletedObjects.end() ; deletedIterator++) {
		processDeletedDataObject(*deletedIterator);
	}

	std::list<commonj::sdo::DataObjectPtr>::iterator createdIterator;
	for (createdIterator = createdObjects.begin() ; createdIterator != createdObjects.end() ; createdIterator++) {
		processCreatedDataObject(*createdIterator);
	}

	std::list<commonj::sdo::DataObjectPtr>::iterator modifiedIterator;
	for (modifiedIterator = modifiedObjects.begin() ; modifiedIterator != modifiedObjects.end() ; modifiedIterator++) {
		processModifiedDataObject(*modifiedIterator);
	}

}

DASDataObject& ApplyChanges::processDataObject(commonj::sdo::DataObjectPtr dataObject) {
	
	if (summary->isCreated(dataObject)) {
		return processCreatedDataObject(dataObject);
	} else if (summary->isDeleted(dataObject)) {
		return processDeletedDataObject(dataObject);
	} else {
		return processModifiedDataObject(dataObject);
	}

}

void ApplyChanges::processMetadata(commonj::sdo::DataObjectPtr dataObject) {
	const commonj::sdo::Type& type = dataObject->getType();
	std::map<std::string, const Table*>::iterator it = tables.find((std::string) type.getName());

	if (it != tables.end()) {
		return;
	}

	const Table* configTable = config->getTableByTypeName((std::string) type.getName());
	commonj::sdo::PropertyList properties = type.getProperties();
	const std::map<std::string, const Column*>* configTableColumns;
	std::map<std::string, const Column*>* configTableColumnsByPropertyName = 0;
	Table* table;
	unsigned int pkColumnCount = 0;
	unsigned int pkFoundCount = 0;

	if (configTable != 0) {
		table = new Table(configTable->getTableName(), configTable->getTypeName());
		configTableColumns = &configTable->getColumns();
		configTableColumnsByPropertyName = new std::map<std::string, const Column*>();
		pkColumnCount = configTable->getPKColumnCount();

		for (std::map<std::string, const Column*>::const_iterator configTableColumnsIterator = configTableColumns->begin() ;
			configTableColumnsIterator != configTableColumns->end() ; configTableColumnsIterator++) {

				configTableColumnsByPropertyName->insert(
					std::make_pair(configTableColumnsIterator->second->getPropertyName(), 
									configTableColumnsIterator->second));

		}

	} else {
		table = new Table((std::string) type.getName());
	}

	for (unsigned int j = 0 ; j < properties.size() ; j++) {

		if (properties[j].getType().isDataType()) {
		
			if (configTable != 0) {
				std::map<std::string, const Column*>::const_iterator configTableColumnsIterator =
					configTableColumnsByPropertyName->find(properties[j].getName());

				if (configTableColumnsIterator !=	configTableColumnsByPropertyName->end()) {
					const Column& column = 	*configTableColumnsIterator->second;
				
					if (column.isPK()) {
						pkFoundCount++;
					}

					table->addColumn(column);

				} else {
					table->addColumn(properties[j].getName(), ODBCTypeHelper::getSQLType(properties[j].getType()));
				}

			} else {
				table->addColumn(properties[j].getName(), ODBCTypeHelper::getSQLType(properties[j].getType()));
			}

		} else {
			const Table* fkTable = config->getTableByTypeName(properties[j].getType().getName());
			std::string fkTableName;

			if (fkTable == 0) {
				fkTableName = properties[j].getType().getName();
			} else {
				fkTableName = fkTable->getTableName();
			}

			const Relationship* relationship = config->getRelationship(table->getTableName(), fkTableName);

			if (relationship == 0) {
				Relationship* aux = new Relationship(table->getTableName(), fkTableName);
				aux->addKeyPair("ID", table->getTableName() + "_ID");
				relationships.insert(std::make_pair(table->getTableName() + "." + fkTableName, aux));				

			} else {
				relationships.insert(std::make_pair(table->getTableName() + "." + fkTableName, new Relationship(*relationship)));				
			}

		}

	}

	if (pkColumnCount == 0 || pkColumnCount > pkFoundCount) {
		Column* column = table->getColumn("ID");

		if (column == 0) {
			throw DASPrimaryKeyNotFoundException((std::string) "No PK found on Type " + type.getURI() + "." + type.getName());
		}

		column->setPK(true);

	}

	if (configTableColumnsByPropertyName != 0) {
		delete configTableColumnsByPropertyName;
	}

	tables.insert(std::make_pair(type.getName(), table));

}

bool ApplyChanges::isInStack(const DASDataObject& dasDataObject) {
	
	for (std::list<DASDataObject*>::const_iterator it = stack.begin() ; stack.end() != it ; it++) {

		if ((*it)->getDataObject() == dasDataObject.getDataObject()) {
			return true;
		}

	}

	return false;
	
}

const Table& ApplyChanges::getTable(commonj::sdo::DataObjectPtr dataObject) {
	std::string typeName = dataObject->getType().getName();
	std::map<std::string, const Table*>::const_iterator it = tables.find(typeName);

	if (it == tables.end()) {
		processMetadata(dataObject);
	}

	it = tables.find(typeName);

	return *it->second;

}

CreatedDataObject& ApplyChanges::processCreatedDataObject(commonj::sdo::DataObjectPtr dataObject) {
	DASDataObject* dasDB = getDASDataObject(dataObject);
	//dataObject->printSelf(std::cout);
	
	if (dasDB != 0) {
		return (CreatedDataObject&) *dasDB;
	}

	CreatedDataObject* createdDataObject = new CreatedDataObject(
		getTable(dataObject), dataObject, summary);
	
	updateOrderList.push_back(createdDataObject);
	addDataObject(*createdDataObject);
	commonj::sdo::PropertyList properties = dataObject->getInstanceProperties();

	for (unsigned int i = 0 ; i < properties.size() ; i++) {
		
		if (properties[i].getType().isDataObjectType()) {
			
			if (properties[i].isMany()) {
				commonj::sdo::DataObjectList& dataObjectList = dataObject->getList(properties[i]);

				for (unsigned int j = 0 ; j < dataObjectList.size() ; j++) {
					DASDataObject& dob = processDataObject(dataObjectList[j]);
					RelationshipStatement& rs = dob.addFK(getRelationship(*createdDataObject, dob), &createdDataObject->getPrimaryKeys());
					dob.addDependency(rs);
					
				}

			} else {
				commonj::sdo::DataObjectPtr dao = dataObject->getDataObject(properties[i]);
				DASDataObject& dob = processDataObject(dao);
				dob.addFK(getRelationship(*createdDataObject, dob), &createdDataObject->getPrimaryKeys());
				RelationshipStatement& rs = dob.addFK(getRelationship(*createdDataObject, dob), &createdDataObject->getPrimaryKeys());
				dob.addDependency(rs);

			}

		}

	}

	return *createdDataObject;
	
}

const Relationship& ApplyChanges::getRelationship(DASDataObject& parentDataObject, DASDataObject& dataObject) const {
	std::map<std::string, const Relationship*>::const_iterator relatIt = relationships.
		find(parentDataObject.getTable().getTableName() + "." + dataObject.getTable().getTableName());

	return *relatIt->second;

}

DeletedDataObject& ApplyChanges::processDeletedDataObject(commonj::sdo::DataObjectPtr dataObject) {
	DASDataObject* dasDB = getDASDataObject(dataObject);
	//dataObject->printSelf(std::cout);
	
	if (dasDB != 0) {
		return (DeletedDataObject&) *dasDB;
	}

	DeletedDataObject* deletedDataObject = new DeletedDataObject(
		getTable(dataObject), dataObject, summary);

	const commonj::sdo::SettingList& setting = summary->getOldValues(dataObject);

	for (int i = 0 ; i < setting.size() ; i++) {
		
		if (setting[i].isSet() && setting[i].getProperty().getType().isDataObjectType()) {
			commonj::sdo::DataObjectPtr containedDataObject = setting[i].getDataObjectValue();

			if (!summary->isDeleted(containedDataObject)) {
				DASDataObject& modDO = processDataObject(containedDataObject);
				RelationshipStatement& rs = modDO.addFK(getRelationship(*deletedDataObject, modDO), 0);
				deletedDataObject->addDependency(rs);

			}

		}

	}

	addDataObject(*deletedDataObject);
	updateOrderList.push_back(deletedDataObject);

	return *deletedDataObject;

}

DASDataObject* ApplyChanges::getDASDataObject(commonj::sdo::DataObjectPtr dataObject) const {
	std::map<std::string, std::list<DASDataObject*>*>::const_iterator it = dasDataObjects.find((std::string) dataObject->getType().getName());

	if (it != dasDataObjects.end()) {
		std::list<DASDataObject*>& list = *it->second;
		std::list<DASDataObject*>::iterator it2;

		for (it2 = list.begin() ; it2 != list.end() ; it2++) {
	
			if ((*it2)->getDataObject() == dataObject) {
				return *it2;
			}

		}

	}

	return 0;

}

ModifiedDataObject& ApplyChanges::processModifiedDataObject(commonj::sdo::DataObjectPtr dataObject) {
	DASDataObject* dasDB = getDASDataObject(dataObject);
	//dataObject->printSelf(std::cout);

	if (dasDB != 0) {
		return (ModifiedDataObject&) *dasDB;
	}

	ModifiedDataObject* modifiedDataObject = new ModifiedDataObject(
		getTable(dataObject), dataObject, summary);
		
	updateOrderList.push_back(modifiedDataObject);
	addDataObject(*modifiedDataObject);

	if (summary->isModified(dataObject)) {
		std::map<const commonj::sdo::Property*, std::list<commonj::sdo::DataObjectPtr>*> manyOldValuedProperties;
		std::map<const commonj::sdo::Property*, commonj::sdo::DataObjectPtr> singleOldValuedProperties;
		commonj::sdo::PropertyList props = dataObject->getInstanceProperties();
		const commonj::sdo::SettingList& setting = summary->getOldValues(dataObject);

		for (int i = 0 ; i < setting.size() ; i++) {
			const commonj::sdo::Property& prop = setting[i].getProperty();

			if (prop.getType().isDataObjectType()) {

				if (!setting[i].isNull() && setting[i].isSet()) {
					commonj::sdo::DataObjectPtr oldDO = setting[i].getDataObjectValue();
					//oldDO->printSelf(std::cout);
					//std::cout << std::endl;

					if (prop.isMany()) {
						std::map<const commonj::sdo::Property*, std::list<commonj::sdo::DataObjectPtr>*>::iterator manyIt =
							manyOldValuedProperties.find(&prop);

						std::list<commonj::sdo::DataObjectPtr>* list;

						if (manyIt == manyOldValuedProperties.end()) {
							list = new std::list<commonj::sdo::DataObjectPtr>();
							manyOldValuedProperties.insert(std::make_pair(&prop, list));

						} else {
							list = manyIt->second;
						}

						list->push_back(oldDO);

					} else {
						singleOldValuedProperties.insert(std::make_pair(&prop, oldDO));
					}

				}

			}
		}

		for (unsigned int i = 0 ; i < props.size() ; i++) {
			
			if (props[i].getType().isDataObjectType()) {

				if (props[i].isMany()) {
					
					if (!dataObject->isSet(props[i]) || !dataObject->isNull(props[i])) {
						commonj::sdo::DataObjectList& doList = dataObject->getList(props[i]);

 						for (unsigned int j = 0 ; j < doList.size() ; j++) {
							commonj::sdo::DataObjectPtr dob = doList[j];
							
							if (summary->isCreated(dob)) {
								CreatedDataObject& createdDO = processCreatedDataObject(dob);
								RelationshipStatement& rs = createdDO.addFK(getRelationship(*modifiedDataObject, createdDO), &modifiedDataObject->getPrimaryKeys());
								modifiedDataObject->addDependency(rs);

							} else {
								std::map<const commonj::sdo::Property*, std::list<commonj::sdo::DataObjectPtr>*>::iterator manyIt = 
									manyOldValuedProperties.find(&props[i]);

								bool existsOldDO = false;

								if (manyIt != manyOldValuedProperties.end()) {
									std::list<commonj::sdo::DataObjectPtr>& list = *manyIt->second;

									for (std::list<commonj::sdo::DataObjectPtr>::iterator listIt = list.begin() ;
										listIt != list.end() ; listIt++) {

											if (*listIt == dob) {
												existsOldDO = true;
												list.erase(listIt);
												break;

											}

									}

								}

								if (!existsOldDO) {
									ModifiedDataObject& modDO = processModifiedDataObject(dob);
									RelationshipStatement& rs = modDO.addFK(getRelationship(*modifiedDataObject, modDO), &modifiedDataObject->getPrimaryKeys());
									modifiedDataObject->addDependency(rs);

								}

							}

						}

					}

				} else {

					if (!dataObject->isSet(props[i]) || !dataObject->isNull(props[i])) {
						commonj::sdo::DataObjectPtr dob = dataObject->getDataObject(props[i]);

						if (summary->isCreated(dob)) {
							CreatedDataObject& createdDO = processCreatedDataObject(dob);
							RelationshipStatement& rs = createdDO.addFK(getRelationship(*modifiedDataObject, createdDO), &modifiedDataObject->getPrimaryKeys());
							modifiedDataObject->addDependency(rs);

						} else {
							std::map<const commonj::sdo::Property*, commonj::sdo::DataObjectPtr>::iterator singleIt = 
								singleOldValuedProperties.find(&props[i]);

							if (singleIt == singleOldValuedProperties.end() || singleIt->second != dob) {
								ModifiedDataObject& modDO = processModifiedDataObject(dob);
								RelationshipStatement& rs = modDO.addFK(getRelationship(*modifiedDataObject, modDO), &modifiedDataObject->getPrimaryKeys());
								modifiedDataObject->addDependency(rs);


							}

						}

					}

				}

			}

		}

		for (std::map<const commonj::sdo::Property*, std::list<commonj::sdo::DataObjectPtr>*>::iterator manyIt = manyOldValuedProperties.begin() ;
			manyIt != manyOldValuedProperties.end() ; manyIt++) {

				std::list<commonj::sdo::DataObjectPtr>* list = manyIt->second;

				for (std::list<commonj::sdo::DataObjectPtr>::const_iterator listIt = list->begin() ;
					listIt != list->end() ; listIt++) {
					
						ModifiedDataObject& modDO = processModifiedDataObject(*listIt);
						RelationshipStatement& rs = modDO.addFK(getRelationship(*modifiedDataObject, modDO), 0);
						modDO.addDependency(rs);

				}

				delete list;

		}

	}

	return *modifiedDataObject;

}

void ApplyChanges::addDataObject(DASDataObject& dataObject) {
	std::list<DASDataObject*>* list;
	std::string typeName = dataObject.getDataObject()->getType().getName();
	std::map<std::string, std::list<DASDataObject*>*>::iterator it = 
		dasDataObjects.find(typeName);
	
	if (it == dasDataObjects.end()) {
		list = new std::list<DASDataObject*>();
		dasDataObjects.insert(std::make_pair(typeName, list));

	} else {
		list = it->second;
	}

	list->push_back(&dataObject);

}

		};
	};
};
