/* Copyright Steve Rabin, 2008. 
 * All rights reserved worldwide.
 *
 * This software is provided "as is" without express or implied
 * warranties. You may freely copy and compile this source into
 * applications you distribute provided that the copyright text
 * below is included in the resulting source code, for example:
 * "Portions Copyright Steve Rabin, 2008"
 */

#pragma warning(disable: 4995)

#include "DXUT.h"
#include "body.h"
#include "configure.h"
#include "game.h"
#include "GameError.h"
#include "world.h"
#include "time.h"
#include "database.h"
#include "msgroute.h"
#include "gameobject.h"
#include "debuglog.h"
#include "MeshInstance.h"
#include "ModelNode.h"
#include "sound.h"
#include "WorldCollData.h"

extern CSoundManager           g_DSound;               // DirectSound class
extern CMultiAnim              g_MultiAnim;            // the MultiAnim class for holding Tiny's mesh and frame hierarchy

// State machines
#include "player.h"
#include "Rover.h"

// Unit test state machines
#include "unittest1.h"
#include "unittest2a.h"
#include "unittest2b.h"
#include "unittest2c.h"
#include "unittest3a.h"
#include "unittest3b.h"
#include "unittest4.h"
#include "unittest5.h"
#include "unittest6.h"

//#define UNIT_TESTING

World::World(void)
: m_initialized(false)
{

}

World::~World(void)
{
	delete m_time;
	delete m_database;
	delete m_msgroute;
	delete m_debuglog;
}

void World::InitializeSingletons( void )
{
	//Create Singletons
	m_time = new Time();
	m_database = new Database();
	m_msgroute = new MsgRoute();
	m_debuglog = new DebugLog();
}

void World::Initialize( IDirect3DDevice9* pd3dDevice )
{
	if(!m_initialized)
	{
		m_initialized = true;
	

#ifdef UNIT_TESTING

		//Create unit test game objects
		GameObject* unittest1 = new GameObject( g_database.GetNewObjectID(), OBJECT_Ignore_Type, "UnitTest1" );
		GameObject* unittest2 = new GameObject( g_database.GetNewObjectID(), OBJECT_Ignore_Type, "UnitTest2" );
		GameObject* unittest3a = new GameObject( g_database.GetNewObjectID(), OBJECT_Ignore_Type, "UnitTest3a" );
		GameObject* unittest3b = new GameObject( g_database.GetNewObjectID(), OBJECT_Ignore_Type, "UnitTest3b" );
		GameObject* unittest4 = new GameObject( g_database.GetNewObjectID(), OBJECT_Ignore_Type, "UnitTest4" );
		GameObject* unittest5 = new GameObject( g_database.GetNewObjectID(), OBJECT_Ignore_Type, "UnitTest5" );
		GameObject* unittest6 = new GameObject( g_database.GetNewObjectID(), OBJECT_Ignore_Type, "UnitTest6" );
		
		unittest1->CreateStateMachineManager();
		unittest2->CreateStateMachineManager();
		unittest3a->CreateStateMachineManager();
		unittest3b->CreateStateMachineManager();
		unittest4->CreateStateMachineManager();
		unittest5->CreateStateMachineManager();
		unittest6->CreateStateMachineManager();
		
		g_database.Store( *unittest1 );
		g_database.Store( *unittest2 );
		g_database.Store( *unittest3a );
		g_database.Store( *unittest3b );
		g_database.Store( *unittest4 );
		g_database.Store( *unittest5 );
		g_database.Store( *unittest6 );

		//Give the unit test game objects a state machine
		unittest1->GetStateMachineManager()->PushStateMachine( *new UnitTest1( *unittest1 ), STATE_MACHINE_QUEUE_0, TRUE );
		unittest2->GetStateMachineManager()->PushStateMachine( *new UnitTest2a( *unittest2 ), STATE_MACHINE_QUEUE_0, TRUE );
		unittest3a->GetStateMachineManager()->PushStateMachine( *new UnitTest3a( *unittest3a ), STATE_MACHINE_QUEUE_0, TRUE );
		unittest3b->GetStateMachineManager()->PushStateMachine( *new UnitTest3b( *unittest3b ), STATE_MACHINE_QUEUE_0, TRUE );
		unittest4->GetStateMachineManager()->PushStateMachine( *new UnitTest4( *unittest4 ), STATE_MACHINE_QUEUE_0, TRUE );
		unittest5->GetStateMachineManager()->PushStateMachine( *new UnitTest5( *unittest5 ), STATE_MACHINE_QUEUE_0, TRUE );
		unittest6->GetStateMachineManager()->PushStateMachine( *new UnitTest6( *unittest6 ), STATE_MACHINE_QUEUE_0, TRUE );

#else

	// Create the default scene
	g_pScene = new Node(pd3dDevice);

	// Instance level.grd that was previously loaded. 
	std::tr1::shared_ptr<ModelNode> pModelNode(new ModelNode(pd3dDevice));
	HRESULT hr = pModelNode->GetInstance(L"level.grd");
	if (FAILED(hr))
    {
        ShowError();
//        delete pModelNode;
        return;
    }
	g_pScene->AddChild(pModelNode);

	// Create player. 
    char name[10] = "Player";
    g_objectIDPlayer = g_database.GetNewObjectID();
    GameObject* player = new GameObject( g_objectIDPlayer, OBJECT_Player, name );
    D3DXVECTOR3 pos(16.0f, 0.0f, 22.0f);
    player->CreateBody( 100, pos );
    player->GetBody().SetRadius(fCollRadiusPlayer);
    player->CreateMovement();
#if USE_TINY_FOR_PLAYER
	hr = player->CreateTiny( &g_MultiAnim, &g_DSound, DXUTGetGlobalTimer()->GetTime() );
	if (FAILED(hr))
	{
		ShowError();
        exit(1);
	}
#else
	hr = player->CreateInstance(pd3dDevice, L"mesh", szPlayerModel);
	if (FAILED(hr))
    {
		ShowError();
        exit(1);
    }
#endif
    player->CreateStateMachineManager();
    g_database.Store( *player );

    //Give the game object a state machine
    player->GetStateMachineManager()->PushStateMachine( *new Player( *player ), STATE_MACHINE_QUEUE_0, TRUE );

	for (int i = 1; i <= cNPC; i++)
	{
		//Create game objects
		char name[10] = "NPC";
		sprintf( name, "%s%d", name, i );
		GameObject* npc = new GameObject( g_database.GetNewObjectID(), OBJECT_NPC, name );
		pos.x = 22.5f;	pos.y = 0.0f;	pos.z = 22.5f-(i*2.f-2.f);
		npc->CreateBody( 100, pos );
#if USE_TINY_FOR_NPC
		// Use Tiny for NPC. 
        npc->GetBody().SetRadius(fCollRadiusPlayer);
		npc->CreateMovement();
		hr = npc->CreateTiny( &g_MultiAnim, &g_DSound, DXUTGetGlobalTimer()->GetTime() );
		if (FAILED(hr))
		{
			MessageBox( DXUTGetHWND(), L"Error creating Tiny.", DXUTGetWindowTitle(), MB_ICONERROR|MB_OK );
			exit(1);
		}
#else
		// Use szNPCModel for NPC. 
		npc->GetBody().SetRadius(fCollRadiusNPC);
		npc->CreateMovement();
		hr = npc->CreateInstance(pd3dDevice, L"mesh", szNPCModel);
		if (FAILED(hr))
		{
			ShowError();
			exit(1);
		}
#endif
		npc->CreateStateMachineManager();
		g_database.Store( *npc );

		//Give the game object a state machine
		npc->GetStateMachineManager()->PushStateMachine( *new Rover( *npc ), STATE_MACHINE_QUEUE_0, TRUE );
	}

    GameObject* sound = new GameObject(g_database.GetNewObjectID(), OBJECT_Sound, "Sound");
    sound->CreateStateMachineManager();
    g_database.Store(*sound);
    sound->GetStateMachineManager()->PushStateMachine( *new Sound(*sound), STATE_MACHINE_QUEUE_0, TRUE );

#if VISUALIZEWORLDCOLL
    if (g_pWorldCollData)
    {
        // Multiply by 4 because each CollQuad consists of four line segments. 
        std::tr1::shared_ptr<LineNode> pWorldCollLineNode(new LineNode(pd3dDevice, g_pWorldCollData->GetCCollQuads() * 4));
        for (int i = 0; i < g_pWorldCollData->GetCCollQuads(); ++i)
        {
            const CollQuad cq = g_pWorldCollData->GetCollQuad(i);
            // first segment 
            pWorldCollLineNode->SetVertex(i * 8 + 0, cq.point[0].x, cq.point[0].y, cq.point[0].z, 0xffffffff);
            pWorldCollLineNode->SetVertex(i * 8 + 1, cq.point[1].x, cq.point[1].y, cq.point[1].z, 0xffffffff);
            // second segment 
            pWorldCollLineNode->SetVertex(i * 8 + 2, cq.point[1].x, cq.point[1].y, cq.point[1].z, 0xffffffff);
            pWorldCollLineNode->SetVertex(i * 8 + 3, cq.point[2].x, cq.point[2].y, cq.point[2].z, 0xffffffff);
            // third segment 
            pWorldCollLineNode->SetVertex(i * 8 + 4, cq.point[2].x, cq.point[2].y, cq.point[2].z, 0xffffffff);
            pWorldCollLineNode->SetVertex(i * 8 + 5, cq.point[3].x, cq.point[3].y, cq.point[3].z, 0xffffffff);
            // fourth segment
            pWorldCollLineNode->SetVertex(i * 8 + 6, cq.point[3].x, cq.point[3].y, cq.point[3].z, 0xffffffff);
            pWorldCollLineNode->SetVertex(i * 8 + 7, cq.point[0].x, cq.point[0].y, cq.point[0].z, 0xffffffff);
        }
        g_pScene->AddChild(pWorldCollLineNode);
    }
#endif

#endif

	}
}

void World::Update()
{
	g_time.MarkTimeThisTick();
	g_database.Update();
}

void World::Animate( double dTimeDelta )
{
	g_database.Animate( dTimeDelta );
}
