/*
 * 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/GraphBuilderMetaData.h"

namespace apache {
	namespace das {
		namespace rdb {

GraphBuilderMetaData::GraphBuilderMetaData(const ConfigImpl& config, const ResultSetMetaData& resultSetMetaData) {
	this->resultSetMetaData = &resultSetMetaData;
	this->config = &config;
	graphTables = new std::map<std::string, Table*>();
	const std::map<std::string, Relationship*>& configRelationships = config.getRelationships();
	relationships = new std::map<std::string, Relationship*>();
	std::map<std::string, Relationship*>::const_iterator relationshipIterator;

	unsigned int colCount = resultSetMetaData.getColumnCount();
	std::map<std::string, std::list<Column*>*> tablesColumns;
	
	for (unsigned int i = 0 ; i < colCount ; i++) {
		std::string columnName = resultSetMetaData.getColumnName(i);
		SQLSMALLINT columnSQLType = resultSetMetaData.getSQLType(i);
		std::string columnTableName = resultSetMetaData.getTableName(i);
		std::map<std::string, Table*>::iterator tableIterator = graphTables->
			find(columnTableName);

		Table* table = 0;
		const Table* configTable = config.getTable(columnTableName);
		
		if (tableIterator == graphTables->end()) {
			table = new Table(columnTableName);
			
			if (configTable != 0) {
				table->setTypeName(configTable->getTypeName());
			}
			
			graphTables->insert(std::make_pair(table->getTableName(), table));

		} else {
			table = tableIterator->second;
		}

		if (configTable == 0) {
			table->addColumn(columnName, columnSQLType);
		} else {
			Column* column = (Column*) configTable->getColumn(columnName);

			if (column != 0) {
				table->addColumn(Column(*column));
			} else {
				table->addColumn(columnName, columnSQLType);
			}

		}

	}

	std::map<std::string, Table*>::iterator tableIterator;
	for (tableIterator = graphTables->begin() ; tableIterator != graphTables->end() ; 
		tableIterator++) {

			Table& table = *tableIterator->second;
			const Table* configTable = config.getTable(table.getTableName());
			bool hasPK = false;

			if (configTable != 0 && configTable->getPKColumnCount() == 
				table.getPKColumnCount() && configTable->getPKColumnCount() != 0) {
					hasPK = true;
				
			}

			const std::map<std::string, const Column*>& tableColumns = table.getColumns();
			std::map<std::string, const Column*>::const_iterator columnIterator;

			if (!hasPK) {
				Column* column = table.getColumn("ID");

				if (column != 0) {
					column->setPK(true);
					hasPK = true;

				}


			}

			if (!hasPK) {

				if (configTable == 0 || configTable->getColumns().size() == 0) {

					for (columnIterator = tableColumns.begin() ; 
						columnIterator != tableColumns.end() ; columnIterator++) {
							((Column*) columnIterator->second)->setPK(true);
					}

				}

			}

	}

	for (relationshipIterator = configRelationships.begin() ;
		relationshipIterator != configRelationships.end() ; relationshipIterator++) {

			Relationship& relationship = *relationshipIterator->second;
			bool contains = true;
			const std::map<std::string, const KeyPair*>& keyPairs = relationship.getKeyPairs();
			std::map<std::string, const KeyPair*>::const_iterator keyPairIterator;
			std::map<std::string, Table*>::iterator pkTableIterator = 
				graphTables->find(relationship.getPKTableName());

			std::map<std::string, Table*>::iterator fkTableIterator = 
				graphTables->find(relationship.getFKTableName());

			if (pkTableIterator == graphTables->end() || 
				fkTableIterator == graphTables->end()) {
				continue;
			}

			Table& pkTable = *pkTableIterator->second;
			Table& fkTable = *fkTableIterator->second;
			
			for (keyPairIterator = keyPairs.begin() ; keyPairIterator != keyPairs.end() ; 
				keyPairIterator++) {

					const KeyPair& keyPair = *keyPairIterator->second;
					Column* pkColumn = pkTable.getColumn(keyPair.getPKColumnName());
					Column* fkColumn = fkTable.getColumn(keyPair.getFKColumnName());

					if (fkColumn == 0 || pkColumn == 0 || !pkColumn->isPK() ||
						fkColumn->getSQLType() != pkColumn->getSQLType()) {

						contains = false;
						break;

					}
					
			}

			if (contains) {
				relationships->insert(std::make_pair(
					relationship.getPKTableName() + "." + relationship.getFKTableName(),
					new Relationship(relationship)));

			}

	}

	for (tableIterator = graphTables->begin() ; tableIterator != graphTables->end() ; 
		tableIterator++) {

			Table& table = *tableIterator->second;
			const std::map<std::string, const Column*>& tableColumns = table.getColumns();
			std::map<std::string, const Column*>::const_iterator columnIterator;

			for (columnIterator = tableColumns.begin() ; 
				columnIterator != tableColumns.end() ; columnIterator++) {

					std::string columnName = columnIterator->second->getName();

					if (columnName.size() > 3 && 
						columnName.substr(columnName.size() - 3, 3) == "_ID") {

						std::string pkTableName = columnName.substr(0, columnName.size() - 3);
						std::string pkXfk = pkTableName + "." + table.getTableName();

						std::map<std::string, Table*>::iterator pkTableIterator = 
									graphTables->find(pkTableName);

						if (pkTableIterator == graphTables->end()) { 
							continue;
						}

						Column* pkColumn = pkTableIterator->second->getColumn("ID");
						if (pkColumn == 0 || 
							columnIterator->second->getSQLType() != pkColumn->getSQLType()) {
							continue;
						}

						std::map<std::string, Relationship*>::iterator relationshipIterator = 
							relationships->find(pkXfk);

						if (relationshipIterator == relationships->end() && 
							pkColumn->isPK()) {
								
								if (pkTableIterator->second->getPKColumnCount() == 1) {
									Relationship* relationship = 
									new Relationship(pkTableName, table.getTableName());

									relationship->addKeyPair("ID", columnName);
									relationships->insert(std::make_pair(relationship->getName(), relationship));

								}

						}

					}

			}
			
	}
	
}

GraphBuilderMetaData::~GraphBuilderMetaData(void) {
	std::map<std::string, Table*>::iterator itTables;
	std::map<std::string, Relationship*>::iterator itRelationships;

	for (itTables = graphTables->begin() ; itTables != graphTables->end() ; itTables++) {
		delete itTables->second;
	}

	for (itRelationships = relationships->begin() ; itRelationships != relationships->end() ; itRelationships++) {
		delete itRelationships->second;
	}

	delete graphTables;
	delete relationships;

}

commonj::sdo::DataFactoryPtr GraphBuilderMetaData::createGraph(void) const {
	commonj::sdo::DataFactoryPtr dataFactory = commonj::sdo::DataFactory::getDataFactory();
	dataFactory->addType(config->getURI(), DAS_ROOT_NAME);
	dataFactory->addPropertyToType(config->getURI(), DAS_ROOT_NAME, "ChangeSummary", SDO_NAMESPACE, "ChangeSummary", false, false, true);
	
	std::map<std::string, Table*>::const_iterator it;
	std::map<std::string, Relationship*>::const_iterator it2;
	
	for (it = graphTables->begin() ; it != graphTables->end() ; it++) {
		Table& table = *(it->second);
		std::string tableName = table.getTypeName();

		dataFactory->addType(config->getURI(), tableName.c_str());
		dataFactory->addPropertyToType(config->getURI(), DAS_ROOT_NAME, 
			tableName.c_str(), config->getURI(), tableName.c_str(), true, false, true);

	}

	for (it = graphTables->begin() ; it != graphTables->end() ; it++) {
		it->second->createGraph(*this, dataFactory);
	}

	for (it2 = relationships->begin() ; it2 != relationships->end() ; it2++) {
		const Relationship& relationship = *it2->second;

		dataFactory->addPropertyToType(config->getURI(), relationship.getPKTableName().c_str(),
			relationship.getName().c_str(), config->getURI(), 
			relationship.getFKTableName().c_str(), relationship.isMany(), false, false);

	}

	return dataFactory;

}

const ResultSetMetaData& GraphBuilderMetaData::getResultSetMetaData(void) const {
	return *resultSetMetaData;
}

const ConfigImpl& GraphBuilderMetaData::getConfig(void) const {
	return *config;
}

std::map<std::string, Table*>& GraphBuilderMetaData::getTables(void) const {
	return *graphTables;
}

Table* GraphBuilderMetaData::getTable(std::string tableName) const {
	std::map<std::string, Table*>::iterator it = graphTables->find(tableName);

	if (it == graphTables->end()) {
		return 0;
	}

	return it->second;

}

std::map<std::string, Relationship*>& GraphBuilderMetaData::getRelationships(void) const {
	return *relationships;
}

		};
	};
};
