/*
* 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
*
*/ 

// The user can choose whether or not to include TinyXML support in the DOM. Supporting TinyXML will
// require linking against it. By default TinyXML support isn't included.
#if defined(DOM_INCLUDE_TINYXML)

#if defined(DOM_DYNAMIC) && defined(_MSC_VER)
#pragma comment(lib, "tinyxml.lib")
#endif

#if defined(_MSC_VER)
#pragma warning(disable: 4100) // warning C4100: 'element' : unreferenced formal parameter
#endif

#include <string>
#include <tinyxml.h>
#include <dae.h>
#include <dom.h>
#include <dae/daeMetaElement.h>
#include <dae/daeErrorHandler.h>
#include <dae/daeMetaElementAttribute.h>
#include <dae/daeTinyXMLPlugin.h>
#include <dae/daeDocument.h>

using namespace std;

namespace {
	daeInt getCurrentLineNumber(TiXmlElement* element) {
		return -1;
	}
}

daeTinyXMLPlugin::daeTinyXMLPlugin()
{
  m_doc = NULL;
	supportedProtocols.push_back("*");
}

daeTinyXMLPlugin::~daeTinyXMLPlugin()
{
}

daeInt daeTinyXMLPlugin::setOption( daeString option, daeString value )
{
	return DAE_ERR_INVALID_CALL;
}

daeString daeTinyXMLPlugin::getOption( daeString option )
{
	return NULL;
}

daeElementRef daeTinyXMLPlugin::readFromFile(const daeURI& uri) {
	string file = cdom::uriToNativePath(uri.str());
	if (file.empty())
		return NULL;
	TiXmlDocument doc;
	doc.LoadFile(file.c_str());
	if (!doc.RootElement()) {
		daeErrorHandler::get()->handleError((std::string("Failed to open ") + uri.str() +
		                                     " in daeTinyXMLPlugin::readFromFile\n").c_str());
		return NULL;
	}
	return readElement(doc.RootElement(), NULL);
}

daeElementRef daeTinyXMLPlugin::readFromMemory(daeString buffer, const daeURI& baseUri) {
	TiXmlDocument doc;
	doc.Parse(buffer);
	if (!doc.RootElement()) {
		daeErrorHandler::get()->handleError("Failed to open XML document from memory buffer in "
		                                    "daeTinyXMLPlugin::readFromMemory\n");
		return NULL;
	}
	return readElement(doc.RootElement(), NULL);
}

daeElementRef daeTinyXMLPlugin::readElement(TiXmlElement* tinyXmlElement, daeElement* parentElement) {
	std::vector<attrPair> attributes;
	for (TiXmlAttribute* attrib = tinyXmlElement->FirstAttribute(); attrib != NULL; attrib = attrib->Next())
		attributes.push_back(attrPair(attrib->Name(), attrib->Value()));
		
	daeElementRef element = beginReadElement(parentElement, tinyXmlElement->Value(),
	                                         attributes, getCurrentLineNumber(tinyXmlElement));
	if (!element) {
		// We couldn't create the element. beginReadElement already printed an error message.
		return NULL;
	}

  if (tinyXmlElement->GetText() != NULL)
		readElementText(element, tinyXmlElement->GetText(), getCurrentLineNumber(tinyXmlElement));
  
  // Recurse children
  for (TiXmlElement* child = tinyXmlElement->FirstChildElement(); child != NULL; child = child->NextSiblingElement())
    element->placeElement(readElement(child, element));

	return element;
}

daeInt daeTinyXMLPlugin::write(const daeURI& name, daeDocument *document, daeBool replace)
{
	// Make sure database and document are both set
	if (!database)
		return DAE_ERR_INVALID_CALL;
	if(!document)
		return DAE_ERR_COLLECTION_DOES_NOT_EXIST;

	string fileName = cdom::uriToNativePath(name.str());
	if (fileName.empty())
	{
		daeErrorHandler::get()->handleError( "can't get path in write\n" );
		return DAE_ERR_BACKEND_IO;
	}
	// If replace=false, don't replace existing files
	if(!replace)
	{
		// Using "stat" would be better, but it's not available on all platforms
		FILE *tempfd = fopen(fileName.c_str(), "r");
		if(tempfd != NULL)
		{
			// File exists, return error
			fclose(tempfd);
			return DAE_ERR_BACKEND_FILE_EXISTS;
		}
		fclose(tempfd);
	}
	
  m_doc = new TiXmlDocument(name.getURI());
  if (m_doc)
  {
    m_doc->SetTabSize(4);

   	TiXmlDeclaration* decl = new TiXmlDeclaration( "1.0", "", "" );  
	  m_doc->LinkEndChild( decl ); 

    writeElement(document->getDomRoot());

    m_doc->SaveFile(fileName.c_str());
    delete m_doc;
    m_doc = NULL;
  }
	return DAE_OK;
}

void daeTinyXMLPlugin::writeElement( daeElement* element )
{
	daeMetaElement* _meta = element->getMeta();
  if (!_meta->getIsTransparent() ) 
  {
		TiXmlElement* tiElm = new TiXmlElement( element->getElementName() );  
        
		if (m_elements.empty() == true) {
				m_doc->LinkEndChild(tiElm);
			} else {
			TiXmlElement* first = m_elements.front();
			first->LinkEndChild(tiElm);
		}
		m_elements.push_front(tiElm);

		daeMetaAttributeRefArray& attrs = _meta->getMetaAttributes();
		
		int acnt = (int)attrs.getCount();
		
		for(int i=0;i<acnt;i++) 
    {
			writeAttribute( attrs[i], element );
		}
	}
	writeValue(element);
	
	daeElementRefArray children;
	element->getChildren( children );
	for ( size_t x = 0; x < children.getCount(); x++ ) 
  {
		writeElement( children.get(x) );
	}

	if (!_meta->getIsTransparent() ) 
  {
    m_elements.pop_front();
	}
}


void daeTinyXMLPlugin::writeValue( daeElement* element )
{
	if (daeMetaAttribute* attr = element->getMeta()->getValueAttribute()) {
		std::ostringstream buffer;
		attr->memoryToString(element, buffer);
		std::string s = buffer.str();
		if (!s.empty())
			m_elements.front()->LinkEndChild( new TiXmlText(buffer.str().c_str()) );
	}
}

void daeTinyXMLPlugin::writeAttribute( daeMetaAttribute* attr, daeElement* element )
{
	ostringstream buffer;
	attr->memoryToString(element, buffer);
	string str = buffer.str();

	// Don't write the attribute if
	//  - The attribute isn't required AND
	//     - The attribute has no default value and the current value is ""
	//     - The attribute has a default value and the current value matches the default
	if (!attr->getIsRequired()) {
		if(!attr->getDefaultValue()  &&  str.empty())
			return;
		if(attr->getDefaultValue()  &&  attr->compareToDefault(element) == 0)
			return;
	}

	m_elements.front()->SetAttribute(attr->getName(), str.c_str());
}

#endif // DOM_INCLUDE_TINYXML
