#include "GameScreen.h"
#include "Object.h"
#include "Explosion.h"
#include "ModelStore.h"
#include "SceneReader.h"
#include "Weapon.h"
#include "WeaponDescription/WeaponDescription.h"
#include "WeaponDescription/WeaponDescription-pimpl.h"
#include "WeaponDescription/WeaponDescription-pskel.h"

#include <Utility/Log.h>
#include <Utility/NativeAssetFile.h>
#include <Renderer/Camera.h>
#include <Renderer/Scene.h>
#include <Renderer/SimpleObjectRenderer.h>
#include <Renderer/OutlinedObjectRenderer.h>
#include <Renderer/ShadowRenderer.h>
#include <Renderer/Billboard.h>
#include <Renderer/BillboardRenderer.h>
#include <Renderer/PngImage.h>
#include <Physics/PhysicalObject.h>
#include <Physics/World.h>

#include <GLES/gl.h>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <stdexcept>

//////////////////////////////////////////////////////////////
GameScreen::GameScreen( AAssetManager& Manager, const Dubious::Math::Size& Dimensions, const std::string& Path )
	: m_Dimensions( Dimensions )
	, m_FrameCount( 0 )
	, m_FrameTimer( 0 )
	, m_Score( 0 )
{
	LOG_INFO( "GameScreen Constructor running" );

	m_pOutlinedRenderer.reset( new Renderer::OutlinedObjectRenderer() );
	m_pRenderer.reset( new Renderer::SimpleObjectRenderer() );
	m_pShadowRenderer.reset( new Renderer::ShadowRenderer() );
	m_pBillboardRenderer.reset( new Renderer::BillboardRenderer() );
	m_pModelStore.reset( new ModelStore() );

	m_pWorld.reset( new Physics::World() );
	m_pWorld->SetGravity( Math::Vector( 0, -9.8f, 0 ) );

	m_pScene.reset( new Renderer::Scene() );
	m_pScene->SceneLight().Position = Math::Vector( 10, 20, 10 );
	m_pScene->SceneLight().Ambient = Renderer::Color( 0.1f, 0.1f, 0.1f, 1.0f );
	m_pScene->SceneLight().Diffuse = Renderer::Color( 0.7f, 0.7f, 0.7f, 1.0f );
	m_pScene->SceneLight().Specular = Renderer::Color( 0.1f, 0.1f, 0.1f, 1.0f );
	m_pScene->BillboardRenderer() = m_pBillboardRenderer;

	ResetScreen( Manager, Path );
	ReadWeapons( Manager );

	m_LoopTimer.Start();
}

//////////////////////////////////////////////////////////////
GameScreen::~GameScreen()
{
	LOG_INFO( "GameScreen Destructor running" );
}

//////////////////////////////////////////////////////////////
void GameScreen::ResetScreen( AAssetManager& Manager, const std::string& Path )
{
	m_pScene->RemoveAllObjects();
	m_pScene->RemoveAllBillboards();
	m_pWorld->RemoveAllObjects();

	SceneReader Reader( m_pModelStore );
	Reader.ReadScene( Manager, Utility::FilePath(Path),
					  m_pFloor, m_Objects );

	m_pFloor->Visible()->ObjectRenderer() = m_pOutlinedRenderer;
	m_pFloor->Visible()->ShadowRenderer() = m_pShadowRenderer;
	m_pScene->AddObject( m_pFloor->Visible() );
	m_pWorld->AddObject( m_pFloor->Physics() );
	std::for_each( m_Objects.begin(), m_Objects.end(), [this] (ObjectPtr pObject) {
		pObject->Visible()->ObjectRenderer() = m_pOutlinedRenderer;
		pObject->Visible()->ShadowRenderer() = m_pShadowRenderer;
		m_pScene->AddObject( pObject->Visible() );
		m_pWorld->AddObject( pObject->Physics() );
	});
	LOG_INFO( "The Scene contains " << m_Objects.size() << " Objects." );

	m_Score = 0;

	std::shared_ptr<Utility::NativeAssetFile> pImageFile( new Utility::NativeAssetFile( Manager, Utility::FilePath( "Splosion0.png" ) ) );
	m_pExplosionPng0.reset( new Renderer::PngImage( pImageFile ) );
	pImageFile.reset( new Utility::NativeAssetFile( Manager, Utility::FilePath( "Splosion1.png" ) ) );
	m_pExplosionPng1.reset( new Renderer::PngImage( pImageFile ) );

	m_pCamera = Renderer::CameraPtr( new Renderer::Camera( Math::RectangleYUp( Math::Point(), m_Dimensions ),  80.0f ) );
	m_pCamera->ZAxisOffset() = 40;
}

//////////////////////////////////////////////////////////////
namespace
{

//////////////////////////////////////////////////////////////
WeaponPtr ReadWeapon( AAssetManager& Manager, const std::string& WeaponFilePath )
{
	Utility::NativeAssetFile File( Manager, Utility::FilePath(WeaponFilePath) );
	std::string WeaponFile = File.Read();

	WeaponDescription_paggr WeaponAggr;
    xml_schema::document_pimpl pDoc( WeaponAggr.root_parser(), WeaponAggr.root_name() );
    WeaponAggr.pre();
    pDoc.parse( WeaponFile.c_str(), WeaponFile.size(), true );
    const WeaponDescriptionType& WeaponType( WeaponAggr.post() );

    WeaponPtr pRetVal( new Weapon );

    pRetVal->Icon() = WeaponType.Icon();
    pRetVal->Speed() = Weapon::Range( WeaponType.Speed().Initial(), WeaponType.Speed().Up(), WeaponType.Speed().Down() );
    pRetVal->Mass() = Weapon::Range( WeaponType.Mass().Initial(), WeaponType.Mass().Up(), WeaponType.Mass().Down() );
    pRetVal->ProjectileCount() = Weapon::Range( WeaponType.ProjectileCount().Initial(), WeaponType.ProjectileCount().Up(), WeaponType.ProjectileCount().Down() );

	return pRetVal;
}

}

//////////////////////////////////////////////////////////////
void GameScreen::ReadWeapons( AAssetManager& Manager )
{
	m_WeaponStore.push_back( ReadWeapon( Manager, "Weapons/SingleShot.xml" ) );
	m_WeaponStore.push_back( ReadWeapon( Manager, "Weapons/Shotgun.xml" ) );

	m_pWeapon = m_WeaponStore[0];
}

//////////////////////////////////////////////////////////////
void GameScreen::OnDraw()
{
    if (!m_pCamera) {
		return;
	}
	unsigned long Elapsed = m_LoopTimer.Elapsed();
	++m_FrameCount;
	m_FrameTimer += Elapsed;
	if( m_FrameTimer > 1000 ){
		LOG_INFO( "Frame Rate: " << m_FrameCount );
		m_FrameCount = 0;
		m_FrameTimer = 0;

		LOG_INFO( "Bullet: " << m_PhysicsBullet << ", Maintenance: " << m_PhysicsMaint << ", Handling: " << m_PhysicsHandling );
		m_PhysicsBullet = 0;
		m_PhysicsMaint = 0;
		m_PhysicsHandling = 0;
	}
	m_LoopTimer.Restart();

	m_pScene->Render( m_pCamera );
}

//////////////////////////////////////////////////////////////
void GameScreen::OnPhysics( float Elapsed )
{
	m_pWorld->StepSimulation( Elapsed, m_PhysicsBullet, m_PhysicsMaint, m_PhysicsHandling );

	int PossibleScore = 0;
	std::vector<ObjectPtr> DeadObjects;
	std::for_each( m_Objects.begin(), m_Objects.end(), [&DeadObjects, &PossibleScore] (ObjectPtr pObject) {
		pObject->Update();
		if (pObject->Physics()->GetCoordinateSpace().Position().Y() < -10) {
			DeadObjects.push_back( pObject );
		}
		else {
			PossibleScore += pObject->Value();
		}
	});
	std::for_each( DeadObjects.begin(), DeadObjects.end(), [this] (ObjectPtr pObject) {
		ExplosionPtr pExplosion( new Explosion( m_pExplosionPng0, m_pExplosionPng1, 1.0 ) );
		pExplosion->SetPosition( pObject->GetPosition() );
		m_Explosions.push_back( pExplosion );
		m_pScene->AddBillboard( pExplosion->Billboard() );
		m_pScene->RemoveObject( pObject->Visible() );
		m_pWorld->RemoveObject( pObject->Physics() );
		m_Objects.remove( pObject );
		m_Score += pObject->Value();
	});

	ExplosionPtrList Explosions = m_Explosions;
	std::for_each( Explosions.begin(), Explosions.end(), [this, Elapsed]( ExplosionPtr pExplosion) {
		pExplosion->Update( Elapsed );
		if (pExplosion->Expired()) {
			m_pScene->RemoveBillboard( pExplosion->Billboard() );
			m_Explosions.remove( pExplosion );
		}
	});

	m_PossibleScore = PossibleScore;
}

//////////////////////////////////////////////////////////////
void GameScreen::AddObject( ObjectPtr pObject )
{
	m_Objects.push_back( pObject );
	m_pScene->AddObject( pObject->Visible() );
	m_pWorld->AddObject( pObject->Physics() );
}

//////////////////////////////////////////////////////////////
void GameScreen::OnResetGL()
{
	LOG_INFO( "OnResetGL" );
	if (m_pRenderer) {
		m_pRenderer->ResetGL();
	}
	if (m_pOutlinedRenderer) {
		m_pOutlinedRenderer->ResetGL();
	}
	if (m_pBillboardRenderer) {
		m_pBillboardRenderer->ResetGL();
	}
	// The shadow renderer doesn't store any state
	// so there's nothing to reset.
}

//////////////////////////////////////////////////////////////
void GameScreen::OnMoveCamera( const Math::Point& Offset )
{
	const float REDUCER = 100.0f;

	Math::Rotation YAxis( Math::Vector( 0, 1.0f, 0 ), Offset.X()/REDUCER );
	m_pCamera->CoordinateSpace().Rotate( YAxis );

	// Rotation around the local XAxis is limited so that the camera
	// doesn't fall below 0 and doesn't go above about 80 degrees
	Math::Vector MaxY( 0, m_pCamera->ZAxisOffset(), 0 );
	Math::Vector X, Y, Z;
	m_pCamera->CoordinateSpace().Axes( X, Y, Z );
	Z.SetLength(m_pCamera->ZAxisOffset());
	if (Offset.Y() > 0) {
		if (Z.Y() > 0) {
			Math::LocalRotation XAxis( Math::Vector( 1.0f, 0, 0 ), Offset.Y()/REDUCER );
			m_pCamera->CoordinateSpace().Rotate( XAxis );
		}
	}
	else if (Offset.Y() < 0) {
		if (MaxY.Dot( Z ) < (m_pCamera->ZAxisOffset()*m_pCamera->ZAxisOffset()) * 0.9f) {
			Math::LocalRotation XAxis( Math::Vector( 1.0f, 0, 0 ), Offset.Y()/REDUCER );
			m_pCamera->CoordinateSpace().Rotate( XAxis );
		}
	}
}

//////////////////////////////////////////////////////////////
void GameScreen::OnZoomCamera( int Span )
{
	float Delta = Span/-5.0f;
	if (m_pCamera->ZAxisOffset() + Delta < 10) {
		return;
	}
	else if (m_pCamera->ZAxisOffset() + Delta > 200) {
		return;
	}
	m_pCamera->ZAxisOffset() += Delta;
}

//////////////////////////////////////////////////////////////
void GameScreen::OnFireBtn( AAssetManager& Manager, float SliderPos, const Math::Point& Target )
{
	m_pWeapon->SliderPosition( SliderPos );
	m_pWeapon->Fire( *this, Manager, Target );
	--m_Score;
}

//////////////////////////////////////////////////////////////
void GameScreen::OnWeaponSelect( int Index )
{
	if (Index < 0 || Index > m_WeaponStore.size()-1) {
		throw std::runtime_error( "Invalid Weapon Index specified" );
	}
	LOG_INFO( "Setting weapon to index " << Index );
	m_pWeapon = m_WeaponStore[Index];
}


