#include "SceneReader.h"
#include "Object.h"
#include "ModelStore.h"
#include "SceneDescription/SceneDescription.h"
#include "SceneDescription/SceneDescription-pimpl.h"
#include "SceneDescription/SceneDescription-pskel.h"

#include <Utility/NativeManager.h>
#include <Utility/NativeAssetFile.h>
#include <Renderer/Scene.h>
#include <Renderer/VisibleObject.h>
#include <Physics/World.h>

#include <algorithm>
#include <stdexcept>

SceneReader::SceneReader( ModelStorePtr pModelStore )
	: m_pModelStore( pModelStore )
{
}

SceneReader::~SceneReader()
{
}

void SceneReader::ReadScene( AAssetManager& Manager, const Utility::FilePath& ScenePath,
		   	   	   	   	     ObjectPtr& pFloor, ObjectPtrList& Objects )
{
	Utility::NativeAssetFile File( Manager, ScenePath );
	std::string SceneFile = File.Read();

	SceneDescription_paggr SceneAggr;
    xml_schema::document_pimpl pDoc( SceneAggr.root_parser(), SceneAggr.root_name() );
    SceneAggr.pre();
    pDoc.parse( SceneFile.c_str(), SceneFile.size(), true );
    std::auto_ptr<SceneDescriptionType> pScene( SceneAggr.post() );

	const BlockType& Base = pScene->Base();
	pFloor.reset( new Object( m_pModelStore->GetVisibleModel( Math::CubeVector( Base.Size().X(), Base.Size().Y(), Base.Size().Z() ) ),
							  m_pModelStore->GetShadowModel(  Math::CubeVector( Base.Size().X(), Base.Size().Y(), Base.Size().Z() ) ),
							  m_pModelStore->GetPhysicsModel( Math::CubeVector( Base.Size().X(), Base.Size().Y(), Base.Size().Z() ) ),
							  0, true ) );
	pFloor->SetPosition( Math::Vector( Base.Location().X(), Base.Location().Y(), Base.Location().Z() ) );
	if (Base.Color_present()) {
		pFloor->Visible()->BaseColor() = Renderer::Color( Base.Color().R(), Base.Color().G(), Base.Color().B(), 1.0f );
	}

	Objects.clear();
	Math::Vector Offset( 0, 0, 0 );
	Renderer::Color BaseColor( 1, 1, 1, 1 );
	for (size_t i=0; i<pScene->GroupReference().size(); ++i) {
		ReadBlocks( Objects, *pScene, pScene->GroupReference()[i], Offset, BaseColor );
	}
}

namespace
{
const GroupType& FindGroup( const SceneDescriptionType& Scene, const GroupReferenceType& Ref )
{
	for (int i=0; i<Scene.Group().size(); ++i) {
		if (Scene.Group()[i].id() == Ref.Ref()) {
			return Scene.Group()[i];
		}
	}
	throw std::runtime_error( "Invalid XML, missing Group" );
}

}

void SceneReader::ReadBlocks( ObjectPtrList& Objects, const SceneDescriptionType& Scene,
							  const GroupReferenceType& GroupReference, const Math::Vector& Offset,
							  const Renderer::Color& BaseColor )
{
	Math::Vector LocalOffset( GroupReference.Location().X(), GroupReference.Location().Y(), GroupReference.Location().Z() );
	LocalOffset += Offset;
	Renderer::Color LocalColor = BaseColor;
	if (GroupReference.Color_present()) {
		LocalColor = Renderer::Color( GroupReference.Color().R(), GroupReference.Color().G(), GroupReference.Color().B(), 1 );
	}
	const GroupType& Blocks = FindGroup( Scene, GroupReference );

	for (size_t i=0; i<Blocks.choice().size(); ++i) {
		switch (Blocks.choice()[i].choice_arm()) {
		case GroupType::choice_type::Block_tag:
			{
				const BlockType& Block = Blocks.choice()[i].Block();
				ObjectPtr pObject( new Object( m_pModelStore->GetVisibleModel( Math::CubeVector( Block.Size().X(), Block.Size().Y(), Block.Size().Z() ) ),
											   m_pModelStore->GetShadowModel( Math::CubeVector(  Block.Size().X(), Block.Size().Y(), Block.Size().Z() ) ),
											   m_pModelStore->GetPhysicsModel( Math::CubeVector( Block.Size().X(), Block.Size().Y(), Block.Size().Z() ) ),
											   Block.Mass(), true ) );
				pObject->SetPosition( LocalOffset + Math::Vector( Block.Location().X(), Block.Location().Y(), Block.Location().Z() ) );
				if (Block.Color_present()) {
					pObject->Visible()->BaseColor() = Renderer::Color( Block.Color().R(), Block.Color().G(), Block.Color().B(), 1.0f );
				}
				else {
					pObject->Visible()->BaseColor() = LocalColor;
				}
				if (Block.Value_present()) {
					pObject->Value() = Block.Value();
				}
				else {
					pObject->Value() = 1;
				}
				Objects.push_back( pObject );
			}
			break;
		case GroupType::choice_type::GroupReference_tag:
			ReadBlocks( Objects, Scene, Blocks.choice()[i].GroupReference(), LocalOffset, LocalColor );
			break;
		}
	}
}
