/*
* Copyright 2006 Sony Computer Entertainment Inc.
*
* Licensed under the MIT Open Source License, for details please see license.txt or the website
* http://www.opensource.org/licenses/mit-license.php
*
*/ 

#include <modules/daeSTLDatabase.h>
#include <dae/daeMetaElement.h>

using namespace std;

daeSTLDatabase::daeSTLDatabase(DAE& dae) : daeDatabase(dae)
{ }

daeSTLDatabase::~daeSTLDatabase()
{
	clear();
}

daeInt daeSTLDatabase::setMeta(daeMetaElement *_topMeta)
{
	topMeta = _topMeta;
	return DAE_OK;
}

daeBool
daeSTLDatabase::isDocumentLoaded(daeString name)
{
	daeDocument* document = getDocument(name);
	if(document)
		return(true);
	else
		return(false);
}

// Element Types of all Elements
daeUInt daeSTLDatabase::getTypeCount()
{
	return (daeUInt)elements.size();
}


daeString daeSTLDatabase::getTypeName(daeUInt index)
{
	daeUInt count = 0;
	
	map<string, vector< daeElement* > >::iterator iter = elements.begin();
	map<string, vector< daeElement* > >::iterator end = elements.end();
	while ( iter != end )
	{
		if ( count == index )
		{
			return (*iter).first.c_str();
		}
		++count;
		++iter;
	}

	return NULL;
}

// Documents
daeInt daeSTLDatabase::insertDocument(const char *name, daeElement* dom, daeDocument** document, bool zaeRootDocument, const std::string& extractedFileURI)
{
	return createDocument( name, dom, document, zaeRootDocument, extractedFileURI );
}
daeInt daeSTLDatabase::createDocument(const char *name, daeElement* dom, daeDocument** document, bool zaeRootDocument, const std::string& extractedFileURI)
{
	// If a document already exists with the same name, error
	if(isDocumentLoaded(name))
	{
		if (document)
			*document = NULL;
		return DAE_ERR_COLLECTION_ALREADY_EXISTS;
	}
	
	// Make a new document
	daeDocument *newDocument = new daeDocument(dae, zaeRootDocument, extractedFileURI);
	newDocument->getDocumentURI()->setURI(name);
	newDocument->setDomRoot(dom);
	// Push the connection into the database
	documents.push_back(newDocument);
	
	if (document)
		*document = newDocument;

	return DAE_OK;
}
// !!!GAC revised version of insertDocument, creates a domCollada and fills it in for you.
daeInt daeSTLDatabase::insertDocument(const char *name, daeDocument** document)
{
	return createDocument( name, document );
}
daeInt daeSTLDatabase::createDocument(const char *name, daeDocument** document)
{

	// If a document already exists with the same name, error
	if(isDocumentLoaded(name))
	{
		if (document)
			*document = NULL;
		return DAE_ERR_COLLECTION_ALREADY_EXISTS;
	}
	// Make the new document
	daeDocument *newDocument = new daeDocument(dae);
	// Make a domCOLLADA to be the root of this new document (this makes a reference so the domCOLLADA won't delete itself
	daeElementRef myCOLLADA = topMeta->create();
	myCOLLADA->setDocument(newDocument);
	newDocument->getDocumentURI()->setURI(name);
	newDocument->setDomRoot(myCOLLADA);

	// Add this document to the list.
	documents.push_back(newDocument);
	// If the user gave us a place to put the document, send it back to them.
	if (document)
		*document = newDocument;
	
	return DAE_OK;
}

daeInt daeSTLDatabase::insertDocument( daeDocument *c ) {
	documents.push_back(c);
	insertElement( c, c->getDomRoot() );
	return DAE_OK;
}

daeInt daeSTLDatabase::removeDocument(daeDocument *document)
{	
	vector< daeDocument* >::iterator iter = documents.begin();
	while ( iter != documents.end() ) {
		if ( (*iter) == document ) {
			//delete all of its children
			removeElement( *iter, (*iter)->getDomRoot() );
            delete *iter; // sthomas (see bug 1466019)
			iter = documents.erase(iter);
		}
		else {
            iter++;
		}
	}
	return DAE_OK;
}

daeUInt daeSTLDatabase::getDocumentCount()
{
	return (daeUInt)documents.size();
}

daeDocument* daeSTLDatabase::getDocument(daeUInt index)
{
	if (index<documents.size())
		return (documents[index]);
	else
		return NULL;
}

daeDocument* daeSTLDatabase::getDocument(daeString name_, bool skipUriNormalization)
{
	string name = name_;
	if (!skipUriNormalization) {
		// Normalize the input string to an absolute URI with no fragment
		name = daeURI(dae, name, true).str();
	}

	// Try to find a document that matches
	daeDocument *document;
	int documentCount	= getDocumentCount();
	for (int i=0;i<documentCount;i++)
	{
		document = getDocument(i);
		if(document->getDocumentURI()->str() == name)
			return(document);
	}
	return(NULL);
}

daeString daeSTLDatabase::getDocumentName(daeUInt index)
{
	if (index<documents.size())
		return getDocument(index)->getDocumentURI()->getURI();
	else
		return NULL;
}

// Elements
daeInt daeSTLDatabase::insertElement(daeDocument* document,daeElement* element)
{
	insertChildren( document, element );
	
	map<string, vector< daeElement* > >::iterator iter = elements.find( string( element->getTypeName() ) );
	if ( iter != elements.end() )
	{
		(*iter).second.push_back( element );
	}
	else
	{
		vector< daeElement* > vec;
		vec.push_back( element );
		elements.insert( make_pair( string( element->getTypeName() ), vec ) );
	}

	// Insert into the type ID map
	typeMap.insert(make_pair(element->typeID(), element));

	//insert into IDMap if element has an ID. IDMap is used to speed up URI resolution
	if ( element->getID() != NULL ) {
		elementsIDMap.insert( make_pair( string( element->getID() ), element ) );
	}

	// Insert into sid map if the element has a sid
// 	string sid = element->getAttribute("sid");
// 	if (!sid.empty())
// 		sidMap.insert(sidMapPair(sid, element));

	dae.getSidRefCache().clear();

	return DAE_OK;
}

daeInt daeSTLDatabase::insertChildren( daeDocument *c, daeElement *element )
{
	daeElementRefArray era;
	element->getChildren( era );
	for ( unsigned int i = 0; i < era.getCount(); i++ ) {
		insertElement( c, era[i] );
	}
	return DAE_OK;
}

daeInt daeSTLDatabase::removeElement(daeDocument* document,daeElement* element)
{
	if ( !element ) {
		return DAE_ERR_INVALID_CALL;
	}
	removeChildren( document, element );
	
	map<string, vector< daeElement* > >::iterator iter = elements.find( string( element->getTypeName() ) );
	if ( iter != elements.end() )
	{
		vector< daeElement* > &vec = (*iter).second;
		vector< daeElement* >::iterator i = vec.begin();
		vector< daeElement* >::iterator end = vec.end();
		while( i != end )
		{
			if ( (*i) == element )
			{
				vec.erase( i );
				break;
			}
			++i;
		}
	}

	typeMapRange range = typeMap.equal_range(element->typeID());
	for (typeMapIter iter = range.first; iter != range.second; iter++) {
		if (iter->second == element) {
			typeMap.erase(iter);
			break;
		}
	}

	if ( element->getID() != NULL ) {
		idMapRange range = elementsIDMap.equal_range( string( element->getID() ) );
		multimap<string, daeElement* >::iterator iter = range.first;
		while( iter != range.second ) {
			if ( (*iter).second == element ) {
				elementsIDMap.erase( iter );
				break;
			}
			++iter;
		}
	}

// 	string sid = element->getAttribute("sid");
// 	if (!sid.empty()) {
// 		pair<sidMapIter, sidMapIter> range = sidMap.equal_range(sid);
// 		for (sidMapIter iter = range.first; iter != range.second; iter++) {
// 			if (iter->second == element) {
// 				sidMap.erase(iter);
// 				break;
// 			}
// 		}
// 	}

	dae.getSidRefCache().clear();
	
	return DAE_OK;
}

daeInt daeSTLDatabase::removeChildren( daeDocument *c, daeElement *element )
{
	daeElementRefArray era;
	element->getChildren( era );
	for ( unsigned int i = 0; i < era.getCount(); i++ ) {
		removeElement( c, era[i] );	
	}
	return DAE_OK;
}

daeInt daeSTLDatabase::changeElementID( daeElement* element, daeString newID )
{
	if ( !element ) {
		return DAE_ERR_INVALID_CALL;
	}

	// Remove the current entry in the ID map if the element has an ID
	if ( element->getID() != NULL ) {
		pair< multimap<string, daeElement* >::iterator, multimap<string, daeElement* >::iterator> range;
		range = elementsIDMap.equal_range( string( element->getID() ) );
		multimap<string, daeElement* >::iterator iter = range.first;
		while( iter != range.second ) {
			if ( (*iter).second == element ) {
				elementsIDMap.erase( iter );
				break;
			}
			++iter;
		}
	}

	// Add an entry to the ID map if the element will have an ID
	if ( newID != NULL ) {
		elementsIDMap.insert( make_pair( string( newID ), element ) );
	}

	dae.getSidRefCache().clear();

	return DAE_OK;
}

daeInt daeSTLDatabase::changeElementSID(daeElement* element, daeString newSID) {
	if (!element)
		return DAE_ERR_INVALID_CALL;

// 	// Remove the current entry in the sid map if the element has a sid
// 	string sid = element->getAttribute("sid");
// 	if (!sid.empty()) {
// 		pair<sidMapIter, sidMapIter> range = sidMap.equal_range(sid);
// 		for (sidMapIter iter = range.first; iter != range.second; iter++) {
// 			if (iter->second == element) {
// 				sidMap.erase(iter);
// 				break;
// 			}
// 		}
// 	}

// 	// Add an entry to the sid map if the element will have a sid
// 	if ( newSID != NULL )
// 		sidMap.insert(sidMapPair(newSID, element));

	dae.getSidRefCache().clear();

	return DAE_OK;
}

daeInt daeSTLDatabase::clear()
{
	elements.clear();
	typeMap.clear();
	elementsIDMap.clear();
	sidMap.clear();
	int i;
	for (i=0;i<(int)documents.size();i++)
		delete documents[i];
	documents.clear(); //this will free the daeElement
	dae.getRawRefCache().clear();
	dae.getSidRefCache().clear();
	return DAE_OK;
}

daeUInt daeSTLDatabase::getElementCount(daeString name,daeString type,daeString file)
{	
	// If none of the search keys was specified, return the total element count in the database
	if ( !name && !type && !file ) 
	{
		daeUInt count = 0;
		map< string, vector< daeElement*> >::iterator iter = elements.begin();
		map< string, vector< daeElement*> >::iterator end = elements.end();
		while( iter != end )
		{
			count += (daeUInt)(*iter).second.size();
			++iter;
		}
		return count;
	}

	if ( name ) 
	{ 
		// name specified
		int count = 0;
		if ( file ) 
		{ 
			// If a document URI was a search key (in file) resolve it to a text URI with no fragment
			daeURI tempURI(dae, file,true);
			daeDocument *col = getDocument( tempURI.getURI() );
			if ( col == NULL ) {
				return 0;
			}
			// a document was specified
			pair< multimap< string, daeElement* >::iterator, multimap< string, daeElement* >::iterator > range;
			range = elementsIDMap.equal_range( string( name ) );
			multimap< string, daeElement* >::iterator i = range.first;
			while ( i != range.second )
			{
				if ( col == (*i).second->getDocument() )
				{
					count++;
				}
				++i;
			}
			return count;
		}
		else 
		{ 
			//no file specified - just name
			return (daeUInt)elementsIDMap.count( string( name ) );
		}
	}

	if ( type ) 
	{ 
		// type specified
		map< string, vector< daeElement*> >::iterator iter = elements.find( string( type ) );
		if ( iter == elements.end() )
		{
			return 0;
		}

		int count = 0;
		if ( file ) 
		{ 
			// If a document URI was a search key (in file) resolve it to a text URI with no fragment
			daeURI tempURI(dae, file,true);
			daeDocument *col = getDocument( tempURI.getURI() );
			if ( col == NULL ) {
				return 0;
			}
			// a document was specified
			vector< daeElement* > &vec = (*iter).second;
			vector< daeElement* >::iterator i = vec.begin();
			vector< daeElement* >::iterator end = vec.end();
			while( i != end )
			{
				if ( col == (*i)->getDocument() )
				{
					++count;
				}
				++i;
			}
			return count;
		}
		else 
		{ 
			//no file specified - just type
			return (daeUInt)(*iter).second.size();
		}
	}

	//if you get here only a file was specified
	daeURI tempURI(dae, file,true);
	daeDocument *col = getDocument( tempURI.getURI() );
	if ( col == NULL ) {
		return 0;
	}
	//a document was specified
	int count = 0;
	map< string, vector< daeElement*> >::iterator iter = elements.begin();
	map< string, vector< daeElement*> >::iterator end = elements.end();
	while( iter != end )
	{
		vector< daeElement* > &vec = (*iter).second;
		vector< daeElement* >::iterator i = vec.begin();
		vector< daeElement* >::iterator end2 = vec.end();
		while( i != end2 )
		{
			if( col == (*i)->getDocument() )
			{
				++count;
			}
			++i;
		}
		++iter;
	}
	return count;

}

daeInt daeSTLDatabase::getElement(daeElement** pElement,daeInt index,daeString name,daeString type,daeString file)
{	
	// If the index is out of range, there can be no match
	if ( index < 0 ) 
	{
		return DAE_ERR_QUERY_NO_MATCH;
	}

	// If no name, type or file was specified we return the element at "index" - SLOW
	if ( !name && !type && !file ) 
	{
		daeUInt count = 0;
		map< string, vector< daeElement*> >::iterator iter = elements.begin();
		map< string, vector< daeElement*> >::iterator end = elements.end();
		while( iter != end )
		{
			count += (daeUInt)(*iter).second.size();
			if ( (daeInt)count > index )
			{
				*pElement = (*iter).second[index - (count - (*iter).second.size())] ;
				return DAE_OK;
			}
			++iter;
		}
		return DAE_ERR_QUERY_NO_MATCH;
	}
	
	if ( name ) 
	{ 
		//name specified
		int count = 0;
		if ( file ) 
		{ 
			// If a document URI was a search key (in file) resolve it to a text URI with no fragment
			daeURI tempURI(dae, file, true);
			daeDocument *col = getDocument( tempURI.getURI() );
			if ( col == NULL ) {
				*pElement = NULL;
				return DAE_ERR_QUERY_NO_MATCH;
			}
			//a document was specified
			pair< multimap< string, daeElement* >::iterator, multimap< string, daeElement* >::iterator> range;
			range = elementsIDMap.equal_range( string( name ) );
			multimap< string, daeElement* >::iterator i = range.first;
			while ( i != range.second )
			{
				if ( col == (*i).second->getDocument() )
				{
					if ( count == index )
					{
						*pElement = (*i).second;
						return DAE_OK;
					}
					count++;
				}
				++i;
			}
			*pElement = NULL;
			return DAE_ERR_QUERY_NO_MATCH;
		}
		else 
		{ 
			//no document specified
			multimap< string, daeElement* >::iterator i = elementsIDMap.find( string( name ) );
			if ( index > (daeInt)elementsIDMap.count( string( name ) ) || i == elementsIDMap.end() )
			{
				*pElement = NULL;
				return DAE_ERR_QUERY_NO_MATCH;
			}
			for ( int x = 0; x < index; x++ )
			{
				++i;
			}
			*pElement = i->second;
			return DAE_OK;
		}
	}
	
	if ( type ) 
	{ 
		map< string, vector< daeElement*> >::iterator iter = elements.find( string( type ) );
		if ( iter == elements.end() )
		{
			*pElement = NULL;
			return DAE_ERR_QUERY_NO_MATCH;
		}
		//type specified
		int count = 0;
		if ( file ) 
		{ 
			// If a document URI was a search key (in file) resolve it to a text URI with no fragment
			daeURI tempURI(dae, file, true);
			daeDocument *col = getDocument( tempURI.getURI() );
			if ( col == NULL ) {
				return DAE_ERR_QUERY_NO_MATCH;
			}
			//a document was specified
			// a document was specified
			vector< daeElement* > &vec = (*iter).second;
			vector< daeElement* >::iterator i = vec.begin();
			vector< daeElement* >::iterator end = vec.end();
			while( i != end )
			{
				if ( col == (*i)->getDocument() )
				{
					if ( count == index )
					{
						*pElement = (*i);
						return DAE_OK;
					}
					++count;
				}
				++i;
			}
			return DAE_ERR_QUERY_NO_MATCH;
		}
		else 
		{ 
			//no document specified
			if ( index >= (daeInt)(*iter).second.size() )
			{
				*pElement = NULL;
				return DAE_ERR_QUERY_NO_MATCH;
			}
			*pElement = (*iter).second[index];
			return DAE_OK;
		}
	}

	//if you get here only the file was specified - SLOW
	daeURI tempURI(dae, file, true);
	daeDocument *col = getDocument( tempURI.getURI() );
	if ( col == NULL ) {
		return DAE_ERR_QUERY_NO_MATCH;
	}
	//a document was specified
	int count = 0;
	map< string, vector< daeElement*> >::iterator iter = elements.begin();
	map< string, vector< daeElement*> >::iterator end = elements.end();
	while( iter != end )
	{
		vector< daeElement* > &vec = (*iter).second;
		vector< daeElement* >::iterator i = vec.begin();
		vector< daeElement* >::iterator end2 = vec.end();
		while( i != end2 )
		{
			if( col == (*i)->getDocument() ) 
			{
				if( count == index ) 
				{
					*pElement = (*i);
					return DAE_OK;
				}
				++count;
			}
			++i;
		}
		++iter;
	}
	return DAE_ERR_QUERY_NO_MATCH;

}

vector<daeElement*> daeSTLDatabase::idLookup(const string& id) {
	vector<daeElement*> matchingElements;
	idMapRange range = elementsIDMap.equal_range(id);
	for (idMapIter iter = range.first; iter != range.second; iter++)
		matchingElements.push_back(iter->second);
	return matchingElements;
}

void daeSTLDatabase::typeLookup(daeInt typeID,
                                vector<daeElement*>& matchingElements,
                                daeDocument* doc) {
	matchingElements.clear();
	typeMapRange range = typeMap.equal_range(typeID);
	for (typeMapIter iter = range.first; iter != range.second; iter++)
		if (!doc  ||  doc == iter->second->getDocument())
			matchingElements.push_back(iter->second);
}

void daeSTLDatabase::sidLookup(const string& sid,
                               vector<daeElement*>& matchingElements,
                               daeDocument* doc) {
	matchingElements.clear();
	if (!sid.empty()) {
		sidMapRange range = sidMap.equal_range(sid);
		for (sidMapIter iter = range.first; iter != range.second; iter++)
			if (!doc  ||  doc == iter->second->getDocument())
				matchingElements.push_back(iter->second);
	}
}
