#include "xmlApi.hpp"

namespace core {

ext::map < std::string, std::unique_ptr < xmlApi < object::Object >::GroupParser > > & xmlApi < object::Object >::parseFunctions ( ) {
	static ext::map < std::string, std::unique_ptr < GroupParser > > res;

	return res;
}

ext::map < std::string, std::unique_ptr < xmlApi < object::Object >::GroupComposer > > & xmlApi < object::Object >::composeFunctions ( ) {
	static ext::map < std::string, std::unique_ptr < GroupComposer > > res;

	return res;
}

void xmlApi < object::Object >::unregisterXmlReader ( const std::string & tagName, const std::string & typeName ) {
	if ( ! parseFunctions ( ).erase ( tagName ) ) {
		std::string groupName = ext::to_string < object::Object > ( );

		throw::exception::CommonException ( "Parse callback of " + typeName + " identified as " + tagName + " not registered in group " + groupName + "." );
	}
}

void xmlApi < object::Object >::registerXmlReader ( std::string tagName, const std::string & typeName, std::unique_ptr < GroupParser > entry ) {
	bool res = parseFunctions ( ).insert ( std::make_pair ( std::move ( tagName ), std::move ( entry ) ) ).second;
	if ( ! res ) {
		std::string groupName = ext::to_string < object::Object > ( );

		throw::exception::CommonException ( "Parse callback of " + typeName + " already registered in group " + groupName + "." );
	}
}

void xmlApi < object::Object >::unregisterXmlWriter ( const std::string & type, const std::string & typeName ) {
	if ( ! composeFunctions ( ).erase ( type ) ) {
		std::string groupName = ext::to_string < object::Object > ( );

		throw::exception::CommonException ( "Compose callback of " + typeName + " not registered in group " + groupName + "." );
	}
}

void xmlApi < object::Object >::registerXmlWriter ( std::string type, const std::string & typeName, std::unique_ptr < GroupComposer > entry ) {
	bool res = composeFunctions ( ).insert ( std::make_pair ( std::move ( type ), std::move ( entry ) ) ).second;
	if ( ! res ) {
		std::string groupName = ext::to_string < object::Object > ( );

		throw::exception::CommonException ( "Compose callback of " + typeName + " already registered in group " + groupName + "." );
	}
}

object::Object xmlApi < object::Object >::parseRef ( xmlApiInputContext & input ) {
	sax::FromXMLParserHelper::popToken ( input, sax::Token::TokenType::START_ELEMENT, "Ref" );
	sax::FromXMLParserHelper::popToken ( input, sax::Token::TokenType::START_ATTRIBUTE, "id" );
	int id = ext::from_string < int > ( sax::FromXMLParserHelper::popTokenData ( input, sax::Token::TokenType::CHARACTER ) );
	sax::FromXMLParserHelper::popToken ( input, sax::Token::TokenType::END_ATTRIBUTE, "id" );
	sax::FromXMLParserHelper::popToken ( input, sax::Token::TokenType::END_ELEMENT, "Ref" );
	ext::map < int, object::Object >::iterator elem = input.idToInstance ( ).find ( id );

	if ( elem == input.idToInstance ( ).end ( ) ) {
		common::Streams::err << input.idToInstance ( ) << std::endl;
		throw exception::CommonException ( "XML Inconsistent ( id not found " + ext::to_string ( id ) + " )" );
	}

	return elem->second;
}

object::Object xmlApi < object::Object >::parseUnique ( xmlApiInputContext & input ) {
	sax::FromXMLParserHelper::popToken ( input, sax::Token::TokenType::START_ELEMENT, "UniqueObject" );
	object::Object baseObject ( parse ( input ) );
	sax::FromXMLParserHelper::popToken ( input, sax::Token::TokenType::START_ELEMENT, "id" );
	unsigned id = ext::from_string < unsigned > ( sax::FromXMLParserHelper::popTokenData ( input, sax::Token::TokenType::CHARACTER ) );
	sax::FromXMLParserHelper::popToken ( input, sax::Token::TokenType::END_ELEMENT, "id" );
	sax::FromXMLParserHelper::popToken ( input, sax::Token::TokenType::END_ELEMENT, "UniqueObject" );
	baseObject += id;
	return baseObject;
}

object::Object xmlApi < object::Object >::parseObject ( xmlApiInputContext & input, const std::string & tagName ) {
	typename ext::map < std::string, std::unique_ptr < GroupParser > >::iterator callback = parseFunctions ( ).find ( tagName );

	if ( callback == parseFunctions ( ).end ( ) ) throw exception::CommonException ( "Parse callback for " + tagName + " tag not registered." );

	return callback->second->parse ( input );
}

object::Object xmlApi < object::Object >::parse ( xmlApiInputContext & input, const std::string & tagName ) {
	if ( /* common::GlobalData::optimizeXml && */ tagName == "Ref" ) {
		return parseRef ( input );
	} else if ( tagName == "UniqueObject" ) {
		return parseUnique ( input );
	} else {
		return parseObject ( input, tagName );
	}
}

object::Object xmlApi < object::Object >::parse ( ext::deque < sax::Token >::iterator & data ) {
	xmlApiInputContext & input = static_cast < xmlApiInputContext & > ( data );
	sax::FromXMLParserHelper::skipAttributes ( input, sax::Token::TokenType::START_ELEMENT );

	const std::string & tagName = sax::FromXMLParserHelper::getTokenData ( static_cast < ext::deque < sax::Token >::iterator & > ( input ), sax::Token::TokenType::START_ELEMENT );

	/* find out if this is a base for reference */
	bool ref = sax::FromXMLParserHelper::isToken ( input + 1, sax::Token::TokenType::START_ATTRIBUTE, "ref" );
	int id = 0;
	if ( ref ) {
		ext::deque < sax::Token >::iterator tmp = input + 2;
		id = ext::from_string < unsigned > ( sax::FromXMLParserHelper::popTokenData ( tmp, sax::Token::TokenType::CHARACTER ) );
	}

	object::Object res = parse ( input, tagName );

	/* if object is a base of reference, register it */
	if ( ref )
		input.idToInstance ( ).insert ( std::make_pair ( id, res ) );

	return res;
}

bool xmlApi < object::Object >::first ( const ext::deque < sax::Token >::const_iterator & input ) {
	const std::string & tagName = sax::FromXMLParserHelper::getTokenData ( input, sax::Token::TokenType::START_ELEMENT );

	if ( tagName == "Ref" ) return true;

	typename ext::map < std::string, std::unique_ptr < GroupParser > >::iterator callback = parseFunctions ( ).find ( tagName );

	return callback != parseFunctions ( ).end ( );
}

std::string xmlApi < object::Object >::xmlTagName ( ) {
	std::string target = ext::to_string < object::Object > ( );

	throw exception::CommonException ( "Type " + target + " does not have xmlTagName." );
}

void xmlApi < object::Object >::composeRef ( xmlApiOutputContext & output, typename ext::map < object::Object, int >::iterator & elem ) {
	output.emplace_back ( "Ref", sax::Token::TokenType::START_ELEMENT );
	output.emplace_back ( "id", sax::Token::TokenType::START_ATTRIBUTE );
	output.emplace_back ( ext::to_string ( elem->second ), sax::Token::TokenType::CHARACTER );
	output.emplace_back ( "id", sax::Token::TokenType::END_ATTRIBUTE );
	output.emplace_back ( "Ref", sax::Token::TokenType::END_ELEMENT );
}

void xmlApi < object::Object >::composeUnique ( xmlApiOutputContext & output, const object::Object & data ) {
	output.emplace_back ( "UniqueObject", sax::Token::TokenType::START_ELEMENT );
	composeObject ( output, data );
	output.emplace_back ( "id", sax::Token::TokenType::START_ELEMENT );
	output.emplace_back ( ext::to_string ( data.getId ( ) ), sax::Token::TokenType::CHARACTER );
	output.emplace_back ( "id", sax::Token::TokenType::END_ELEMENT );
	output.emplace_back ( "UniqueObject", sax::Token::TokenType::END_ELEMENT );
}

void xmlApi < object::Object >::composeObject ( xmlApiOutputContext & output, const object::Object & data ) {
	const auto & content = data.getData ( );
	std::string type = ext::to_string ( ext::type_index ( typeid ( content ) ) );
	typename ext::map < std::string, std::unique_ptr < GroupComposer > >::iterator callback = composeFunctions ( ).find ( type );

	if ( callback == composeFunctions ( ).end ( ) ) throw exception::CommonException ( "Compose callback for " + type + " tag not registered." );

	/* encode referenced object */
	callback->second->compose ( output, data );
}

void xmlApi < object::Object >::compose ( ext::deque < sax::Token > & data, const object::Object & object ) {
	xmlApiOutputContext & output = ( xmlApiOutputContext & ) data;

	typename ext::map < object::Object, int >::iterator elem = output.instanceToId ( ).find ( object );

	/* get current position in token stream */
	size_t pos = output.size();
	int id = 0;
	if ( common::GlobalData::optimizeXml && elem == output.instanceToId ( ).end ( ) ) {
		id = output.idMax ( )++;
	}

	if ( common::GlobalData::optimizeXml && elem != output.instanceToId ( ).end ( ) ) {
		composeRef ( output, elem );
	} else if ( object.getId ( ) != 0 ) {
		composeUnique ( output, object );
	} else {
		composeObject ( output, object );
	}

	/* emplace referenced attribute */
	if ( common::GlobalData::optimizeXml && elem == output.instanceToId ( ).end ( ) ) {
		output.emplace ( output.begin ( ) + pos + 1, "ref", sax::Token::TokenType::START_ATTRIBUTE );
		output.emplace ( output.begin ( ) + pos + 2, ext::to_string ( id ), sax::Token::TokenType::CHARACTER );
		output.emplace ( output.begin ( ) + pos + 3, "ref", sax::Token::TokenType::END_ATTRIBUTE );

		output.instanceToId ( ).insert ( std::make_pair ( object, id ) );
	}
}

} /* namespace core */
