/*
   Copyright (C) 2001-2006, William Joseph.
   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
 */

//
// parses xml tree format into internal objects
//

#include "xmlparse.h"

#include <vector>

#include "ientity.h"
#include "ibrush.h"
#include "ipatch.h"
#include "ieclass.h"
#include "eclasslib.h"

#include "xml/xmlparser.h"
#include "scenelib.h"
#include "generic/reference.h"
#include "generic/object.h"


const char *PARSE_ERROR = "XML PARSE ERROR";


inline XMLImporter* Node_getXMLImporter( scene::Node& node ){
	return NodeTypeCast<XMLImporter>::cast( node );
}


scene::Node& createPrimitive( const char* name ){
	if ( string_equal( name, "brush" ) ) {
		return GlobalBrushCreator().createBrush();
	}
	else if ( string_equal( name, "patch" ) ) {
		return GlobalPatchCreator().createPatch();
	}

	ASSERT_MESSAGE( 0, PARSE_ERROR << ": primitive type not supported: \"" << name << "\"\n" );
	scene::Node* node = 0;
	return *node;
}

class TreeXMLImporter : public XMLImporter
{
public:
virtual TreeXMLImporter& child() = 0;
};

class SubPrimitiveImporter : public TreeXMLImporter
{
XMLImporter* m_importer;
public:
SubPrimitiveImporter( XMLImporter* importer ) : m_importer( importer ){
}
void pushElement( const XMLElement& element ){
	m_importer->pushElement( element );
}
void popElement( const char* name ){
	m_importer->popElement( name );
}
std::size_t write( const char* buffer, std::size_t length ){
	return m_importer->write( buffer, length );
}
SubPrimitiveImporter& child(){
	return *this;
}
};

class PrimitiveImporter : public TreeXMLImporter
{
scene::Node& m_parent;
XMLImporter* m_importer;
char m_child[sizeof( SubPrimitiveImporter )];

SubPrimitiveImporter& subprimitive(){
	return *reinterpret_cast<SubPrimitiveImporter*>( m_child );
}
public:
PrimitiveImporter( scene::Node& parent ) : m_parent( parent ), m_importer( 0 ){
}
void pushElement( const XMLElement& element ){
	if ( string_equal( element.name(), "epair" ) ) {
		ASSERT_MESSAGE( string_equal( element.name(), "epair" ), PARSE_ERROR );
		Node_getEntity( m_parent )->setKeyValue( element.attribute( "key" ), element.attribute( "value" ) );
	}
	else
	{
		NodeSmartReference node( createPrimitive( element.name() ) );

		m_importer = Node_getXMLImporter( node );

		constructor( subprimitive(), m_importer );

		m_importer->pushElement( element );

		Node_getTraversable( m_parent )->insert( node );
	}
}
void popElement( const char* name ){
	if ( string_equal( name, "epair" ) ) {
	}
	else
	{
		m_importer->popElement( name );

		destructor( subprimitive() );
		m_importer = 0;
	}
}
std::size_t write( const char* buffer, std::size_t length ){
	return m_importer->write( buffer, length );
}
TreeXMLImporter& child(){
	return subprimitive();
}
};

class EntityImporter : public TreeXMLImporter
{
scene::Node& m_parent;
char m_node[sizeof( NodeSmartReference )];
char m_child[sizeof( PrimitiveImporter )];
EntityCreator& m_entityTable;

NodeSmartReference& node(){
	return *reinterpret_cast<NodeSmartReference*>( m_node );
}
PrimitiveImporter& primitive(){
	return *reinterpret_cast<PrimitiveImporter*>( m_child );
}

public:
EntityImporter( scene::Node& parent, EntityCreator& entityTable ) : m_parent( parent ), m_entityTable( entityTable ){
}
void pushElement( const XMLElement& element ){
	ASSERT_MESSAGE( string_equal( element.name(), "entity" ), PARSE_ERROR );
	constructor( node(), NodeSmartReference( m_entityTable.createEntity( GlobalEntityClassManager().findOrInsert( "", true ) ) ) );
	constructor( primitive(), makeReference( node().get() ) );
}
void popElement( const char* name ){
	ASSERT_MESSAGE( string_equal( name, "entity" ), PARSE_ERROR );
	NodeSmartReference entity( m_entityTable.createEntity( GlobalEntityClassManager().findOrInsert( Node_getEntity( node() )->getKeyValue( "classname" ), node_is_group( node() ) ) ) );

	{
		EntityCopyingVisitor visitor( *Node_getEntity( entity ) );
		Node_getEntity( node() )->forEachKeyValue( visitor );
	}

	if ( Node_getTraversable( entity ) != 0 && !Node_getEntity( entity )->getEntityClass().fixedsize ) {
		parentBrushes( node(), entity );
	}

	Node_getTraversable( m_parent )->insert( entity );

	destructor( primitive() );
	destructor( node() );
}
std::size_t write( const char* buffer, std::size_t length ){
	return length;
}
TreeXMLImporter& child(){
	return primitive();
}
};

class MapDoom3Importer : public TreeXMLImporter
{
scene::Node& m_root;
char m_child[sizeof( EntityImporter )];
EntityCreator& m_entityTable;

EntityImporter& getEntity(){
	return *reinterpret_cast<EntityImporter*>( m_child );
}
public:
MapDoom3Importer( scene::Node& root, EntityCreator& entityTable ) : m_root( root ), m_entityTable( entityTable ){
}
void pushElement( const XMLElement& element ){
	ASSERT_MESSAGE( string_equal( element.name(), "mapdoom3" ), PARSE_ERROR );
	constructor( getEntity(), makeReference( m_root ), makeReference( m_entityTable ) );
}
void popElement( const char* name ){
	ASSERT_MESSAGE( string_equal( name, "mapdoom3" ), PARSE_ERROR );
	destructor( getEntity() );
}
std::size_t write( const char* data, std::size_t length ){
	return length;
}
TreeXMLImporter& child(){
	return getEntity();
}
};

class TreeXMLImporterStack : public XMLImporter
{
std::vector< Reference<TreeXMLImporter> > m_importers;
public:
TreeXMLImporterStack( TreeXMLImporter& importer ){
	m_importers.push_back( makeReference( importer ) );
}
void pushElement( const XMLElement& element ){
	m_importers.back().get().pushElement( element );
	m_importers.push_back( makeReference( m_importers.back().get().child() ) );
}
void popElement( const char* name ){
	m_importers.pop_back();
	m_importers.back().get().popElement( name );
}
std::size_t write( const char* buffer, std::size_t length ){
	return ( *( m_importers.end() - 2 ) ).get().write( buffer, length );
}
};


void Map_Read( scene::Node& root, TextInputStream& in, EntityCreator& entityTable ){
	XMLStreamParser parser( in );

	MapDoom3Importer importer( root, entityTable );
	TreeXMLImporterStack stack( importer );
	parser.exportXML( stack );
}
