/*
   Copyright (C) 2006, Stefan Greven.
   All Rights Reserved.

   This file is part of GtkRadiant.

   GtkRadiant is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   GtkRadiant is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "xmltextags.h"

#include <string>

#include "stream/stringstream.h"

XmlTagBuilder::XmlTagBuilder(){
}

XmlTagBuilder::~XmlTagBuilder(){
	// clean up
	xmlFreeDoc( doc );
	xmlXPathFreeContext( context );
}

bool XmlTagBuilder::CreateXmlDocument(){
	/* Creates an XML file

	   returns TRUE if the file was created successfully or FALSE when failed
	 */

	xmlTextWriterPtr writer;

	writer = xmlNewTextWriterDoc( &doc, 0 );

	// begin a new UTF-8 formatted xml document
	xmlTextWriterStartDocument( writer, NULL, "UTF-8", NULL );

	// create the root node with stock and custom elements
	xmlTextWriterStartElement( writer, (xmlChar*)"root" );
	xmlTextWriterWriteString( writer, (xmlChar*)"\n  " );
	xmlTextWriterStartElement( writer, (xmlChar*)"stock" );
	xmlTextWriterWriteString( writer, (xmlChar*)"\n  " );
	xmlTextWriterEndElement( writer );
	xmlTextWriterWriteString( writer, (xmlChar*)"\n  " );
	xmlTextWriterStartElement( writer, (xmlChar*)"custom" );
	xmlTextWriterWriteString( writer, (xmlChar*)"\n  " );
	xmlTextWriterEndElement( writer );
	xmlTextWriterWriteString( writer, (xmlChar*)"\n" );
	xmlTextWriterEndElement( writer );

	// end of the xml document
	xmlTextWriterEndDocument( writer );
	xmlFreeTextWriter( writer );

	if ( !doc ) {
		return false;
	}
	else {
		context = xmlXPathNewContext( doc );
		return true;
	}
}

bool XmlTagBuilder::OpenXmlDoc( const char* file, const char* savefile ){
	/* Reads a XML document from a file

	   returns TRUE if the document was read successfully or FALSE when failed
	 */

	if ( savefile ) {
		m_savefilename = savefile;
	}
	else{
		m_savefilename = file;
	}

	doc = xmlParseFile( file ); // TODO error checking!

	if ( !doc ) {
		return false;
	}
	else {
		context = xmlXPathNewContext( doc );
		return true;
	}
}

bool XmlTagBuilder::SaveXmlDoc( void ){
	return SaveXmlDoc( m_savefilename.c_str() );
}

bool XmlTagBuilder::SaveXmlDoc( const char* file ){
	/* Writes the XML document

	   returns TRUE if the document was saved successfully or FALSE when saving failed
	 */

	xmlSaveNoEmptyTags = 1;

	if ( xmlSaveFile( file, doc ) != -1 ) {
		return true;
	}
	return false;
}

bool XmlTagBuilder::AddShaderNode( const char* shader, TextureType textureType, NodeShaderType nodeShaderType ){
	/* Adds a shader node

	   char* shader - the name of the shader or texture (without trailing .tga or something)

	   returns TRUE if the node was added successfully or FALSE when failed
	 */

	xmlNodeSetPtr nodePtr = NULL;
	xmlXPathObjectPtr xpathPtr = NULL;

	switch ( textureType )
	{
	case STOCK:
		xpathPtr = XpathEval( "/root/stock" );
		break;
	case CUSTOM:
		xpathPtr = XpathEval( "/root/custom" );
	};

	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return false;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
		xmlNodePtr newnode = {}, newtext;
		xmlNodePtr nodeParent = nodePtr->nodeTab[0];

		// create a new node and set the node attribute (shader path)
		switch ( nodeShaderType )
		{
		case SHADER:
			newnode = xmlNewNode( NULL, (xmlChar*)"shader" );
			break;
		case TEXTURE:
			newnode = xmlNewNode( NULL, (xmlChar*)"texture" );
		};

		newnode = xmlDocCopyNode( newnode, doc, 1 );
		xmlSetProp( newnode, (xmlChar*)"path", (xmlChar*)shader );
		xmlNodeSetContent( newnode, (xmlChar*)"\n    " );

		if ( nodePtr->nodeTab[0]->children->next == NULL ) { // there are no shaders yet
			// add spaces
			newtext = xmlNewText( (xmlChar*)"  " );
			xmlAddChild( nodeParent->children, newtext );

			// add the new node
			xmlAddNextSibling( nodeParent->children, newnode );

			// append a new line
			newtext = xmlNewText( (xmlChar*)"\n  " );
			xmlAddNextSibling( nodeParent->children->next, newtext );
		}
		else {
			// add the node
			xmlAddNextSibling( nodeParent->children, newnode );

			// append a new line and spaces
			newtext = xmlNewText( (xmlChar*)"\n    " );
			xmlAddNextSibling( nodeParent->children->next, newtext );
		}

		xmlXPathFreeObject( xpathPtr );
		return true;
	}
	else {
		xmlXPathFreeObject( xpathPtr );
		return false;
	}
}

bool XmlTagBuilder::DeleteShaderNode( const char* shader ){
	/* Deletes a shader node

	   char* shader - the name of the shader or texture (without trailing .tga or something)

	   returns TRUE if the node was deleted successfully or FALSE when failed
	 */

	char buffer[256];
	char* expression = GetTagsXpathExpression( buffer, shader, EMPTY );
	xmlXPathObjectPtr xpathPtr = XpathEval( expression );

	xmlNodeSetPtr nodePtr;
	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return false;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
		xmlNodePtr ptrContent = nodePtr->nodeTab[0];
		xmlNodePtr ptrWhitespace = nodePtr->nodeTab[0]->prev;

		// delete the node
		xmlUnlinkNode( ptrContent );
		xmlFreeNode( ptrContent );

		// delete leading whitespace node
		xmlUnlinkNode( ptrWhitespace );
		xmlFreeNode( ptrWhitespace );
		xmlXPathFreeObject( xpathPtr );
		return true;
	}
	xmlXPathFreeObject( xpathPtr );
	return false;
}

bool XmlTagBuilder::CheckShaderTag( const char* shader ){
	/* Checks whether there exists an entry for a shader/texture with at least one tag

	   char* shader  - the name of the shader or texture (without trailing .tga or something)

	   returns TRUE if the shader is already stored in the XML tag file and has at least one tag
	 */

	// build the XPath expression to search for
	char buffer[256];
	strcpy( buffer, "/root/*/*[@path='" );
	strcat( buffer, shader );
	strcat( buffer, "']" );

	char* expression = buffer;

	xmlXPathObjectPtr xpathPtr = XpathEval( expression );
	xmlNodeSetPtr nodePtr;
	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return false;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
		xmlXPathFreeObject( xpathPtr );
		return true;
	}
	else {
		xmlXPathFreeObject( xpathPtr );
		return false;
	}
}

bool XmlTagBuilder::CheckShaderTag( const char* shader, const char* content ){
	/* Checks whether a tag with content already exists

	   char* shader  - the name of the shader or texture (without trailing .tga or something)
	   char* content - the node content (a tag name)

	   returns TRUE if the tag with content already exists or FALSE if not
	 */

	// build the XPath expression to search for
	// example expression: "/stock/*[@path='textures/alpha/barb_wire'][child::tag='Alpha']";

	char buffer[256];
	strcpy( buffer, "/root/*/*[@path='" );
	strcat( buffer, shader );
	strcat( buffer, "'][child::tag='" );
	strcat( buffer, content );
	strcat( buffer, "']" );

	char* expression = buffer;

	xmlXPathObjectPtr xpathPtr = XpathEval( expression );
	xmlNodeSetPtr nodePtr;
	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return false;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
		xmlXPathFreeObject( xpathPtr );
		return true;
	}
	else {
		xmlXPathFreeObject( xpathPtr );
		return false;
	}
}

bool XmlTagBuilder::AddShaderTag( const char* shader, const char* content, NodeTagType nodeTagType ){
	/* Adds a tag node to an existing shader/texture node if there's no tag with the same content yet

	   char* shader  - the name of the shader or texture (without trailing .tga or something)
	   char* content - the node content (a tag name)

	   returns TRUE if the node was added successfully or FALSE when failed
	 */

	// build the XPath expression
	char buffer[256];
	char* expression = GetTagsXpathExpression( buffer, shader, EMPTY );

	xmlXPathObjectPtr xpathPtr = XpathEval( expression );
	xmlNodeSetPtr nodePtr;
	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return false;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { // node was found
		xmlNodePtr newnode = xmlNewNode( NULL, (xmlChar*)"tag" );
		xmlNodePtr nodeParent = nodePtr->nodeTab[0];
		newnode = xmlDocCopyNode( newnode, doc, 1 );
		xmlNodeSetContent( newnode, (xmlChar*)content );

		if ( nodePtr->nodeTab[0]->children->next == NULL ) { // shader node has NO children
			// add spaces
			xmlNodePtr newtext = xmlNewText( (xmlChar*)"  " );
			xmlAddChild( nodeParent->children, newtext );

			// add new node
			xmlAddNextSibling( nodeParent->children, newnode );

			// append a new line + spaces
			newtext = xmlNewText( (xmlChar*)"\n    " );
			xmlAddNextSibling( nodeParent->children->next, newtext );
		}
		else { // shader node has children already - the new node will be the first sibling
			xmlAddNextSibling( nodeParent->children, newnode );
			xmlNodePtr newtext = xmlNewText( (xmlChar*)"\n      " );
			xmlAddNextSibling( nodeParent->children->next, newtext );
		}
		xmlXPathFreeObject( xpathPtr );
		return true;
	}
	else {
		xmlXPathFreeObject( xpathPtr );
		return false;
	}
}

//int XmlTagBuilder::RenameShaderTag(const char* oldtag, const char* newtag)
int XmlTagBuilder::RenameShaderTag( const char* oldtag, CopiedString newtag ){
	/* Replaces tag node contents

	   char* oldtag - the <tag></tag> node content that sould be changed
	   char* newtag - the new <tag></tag> node content

	   returns the number of renamed shaders
	 */

	int num = 0;

	// build the XPath expression
	char expression[256];
	strcpy( expression, "/root/*/*[child::tag='" );
	strcat( expression, oldtag );
	strcat( expression, "']/*" );

	xmlXPathObjectPtr result = xmlXPathEvalExpression( (xmlChar*)expression, context );
	if ( !result ) {
		return 0;
	}
	xmlNodeSetPtr nodePtr = result->nodesetval;

	for ( int i = 0; i < nodePtr->nodeNr; i++ )
	{
		xmlNodePtr ptrContent = nodePtr->nodeTab[i];
		char* content = (char*)xmlNodeGetContent( ptrContent );

		if ( strcmp( content, oldtag ) == 0 ) { // found a node with old content?
			xmlNodeSetContent( ptrContent, (xmlChar*)newtag.c_str() );
			num++;
		}
	}

	SaveXmlDoc();
	xmlXPathFreeObject( result ); // CHANGED
	return num;
}

bool XmlTagBuilder::DeleteShaderTag( const char* shader, const char* tag ){
	/* Deletes a child node of a shader

	   char* shader  - the name of the shader or texture (without trailing .tga or something)
	   char* tag     - the tag being deleted

	   returns TRUE if the node was deleted successfully or FALSE when failed
	 */

	char buffer[256];
	char* expression = GetTagsXpathExpression( buffer, shader, TAG );
	xmlXPathObjectPtr xpathPtr = XpathEval( expression );
	xmlNodeSetPtr nodePtr;
	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return false;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
		for ( int i = 0; i < nodePtr->nodeNr; i++ )
		{
			xmlNodePtr ptrContent = nodePtr->nodeTab[i];
			char* content = (char*)(xmlChar*)xmlNodeGetContent( ptrContent );

			if ( strcmp( content, tag ) == 0 ) { // find the node
				xmlNodePtr ptrWhitespace = nodePtr->nodeTab[i]->prev;
				// delete the node
				xmlUnlinkNode( ptrContent );
				xmlFreeNode( ptrContent );

				// delete leading whitespace node
				xmlUnlinkNode( ptrWhitespace );
				xmlFreeNode( ptrWhitespace );
				xmlXPathFreeObject( xpathPtr );
				return true;
			}
		}
	}
	xmlXPathFreeObject( xpathPtr );
	return false;
}

bool XmlTagBuilder::DeleteTag( const char* tag ){
	/* Deletes a tag from all shaders

	   char* tag - the tag being deleted from all shaders

	   returns TRUE if the tag was deleted successfully or FALSE when failed
	 */

	char expression[256];
	strcpy( expression, "/root/*/*[child::tag='" );
	strcat( expression, tag );
	strcat( expression, "']" );

	std::set<CopiedString> dellist;
	TagSearch( expression, dellist );
	std::set<CopiedString>::iterator iter;

	for ( iter = dellist.begin(); iter != dellist.end(); iter++ )
	{
		DeleteShaderTag( iter->c_str(), tag );
	}
	SaveXmlDoc();

	return true;
}

void XmlTagBuilder::GetShaderTags( const char* shader, std::vector<CopiedString>& tags ){
	/* Gets the tags from a shader

	   char* shader - the name of the shader

	   returns a vector containing the tags
	 */

	char const *expression;

	if ( shader == NULL ) { // get all tags from all shaders
		expression = "/root/*/*/tag";
	}
	else {
		char buffer[256];
		expression = GetTagsXpathExpression( buffer, shader, TAG );
	}

	xmlXPathObjectPtr xpathPtr = XpathEval( expression );
	xmlNodeSetPtr nodePtr;
	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
		for ( int i = 0; i < nodePtr->nodeNr; i++ )
		{
			tags.push_back( (CopiedString)(char*)xmlNodeGetContent( nodePtr->nodeTab[i] ) );
		}
	}
	xmlXPathFreeObject( xpathPtr );
}

void XmlTagBuilder::GetUntagged( std::set<CopiedString>& shaders ){
	/* Gets all textures and shaders listed in the xml file that don't have any tag

	   returns a set containing the shaders (with path)
	 */

	char const *expression = "/root/*/*[not(child::tag)]";

	xmlXPathObjectPtr xpathPtr = XpathEval( expression );
	xmlNodeSetPtr nodePtr;
	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
		xmlNodePtr ptr;

		for ( int i = 0; i < nodePtr->nodeNr; i++ )
		{
			ptr = nodePtr->nodeTab[i];
			shaders.insert( (char*)xmlGetProp( ptr, (xmlChar*)"path" ) );
		}
	}

	xmlXPathFreeObject( xpathPtr );
}

void XmlTagBuilder::GetAllTags( std::set<CopiedString>& tags ){
	/* Gets a list of all tags that are used (assigned to any shader)

	   returns a set containing all used tags
	 */

	char const *expression = "/root/*/*/tag";

	xmlXPathObjectPtr xpathPtr = XpathEval( expression );
	xmlNodeSetPtr nodePtr;
	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
		for ( int i = 0; i < nodePtr->nodeNr; i++ )
		{
			tags.insert( (CopiedString)(char*)xmlNodeGetContent( nodePtr->nodeTab[i] ) );
		}
	}

	xmlXPathFreeObject( xpathPtr );
}

void XmlTagBuilder::TagSearch( const char* expression, std::set<CopiedString>& paths ){
	/* Searches shaders by tags

	   char* expression - the XPath expression to search

	   returns a set containing the found shaders
	 */

	xmlXPathObjectPtr xpathPtr = XpathEval( expression );
	xmlNodeSetPtr nodePtr;
	if ( xpathPtr ) {
		nodePtr = xpathPtr->nodesetval;
	}
	else{
		return;
	}

	if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
		xmlNodePtr ptr;
		xmlChar* xmlattrib;
		for ( int i = 0; i < nodePtr->nodeNr; i++ )
		{
			ptr = nodePtr->nodeTab[i];
			xmlattrib = xmlGetProp( ptr, (xmlChar*)"path" );
			paths.insert( (CopiedString)(char*)xmlattrib );
		}
	}
	xmlXPathFreeObject( xpathPtr );
}
