// Author : Chris Tolley
// Date   : 8/16/2004
// BugCam.cpp contains the class definition for a camera which follows a bug 
// around and allows the user to control that bug.

#pragma warning ( disable : 4786 )
#pragma warning ( disable : 4305 )

#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <math.h>
#include <string>
#include <vector>

using namespace std;

#include "SurvivalEngine.h"
#include "QuadTree.h"
#include "quadTreeTerrain.h"
#include "InputSystem.h"
#include "BugCam.h"
#include "WaterPool.h"
#include "ParticleSystem.h"
#include "logger.h"
#include "ScreenWriter.h"
#include "carcus.h"

// Some constants to help us out
// The ms3d file where the targeter model is stored
const char* TARGETER_MODEL = "models/Targeter.ms3d";
// The offsets from the bug for the camera
const int	X_OFF_SET	= 0;
const int	Y_OFF_SET	= 30;
const int	Z_OFF_SET	= -20;	

// Constructor
BugCam::BugCam()
: m_Input( NULL ), m_qtTerrain( NULL ), m_qtTree( NULL ), m_pEngine( NULL ),
  m_fPercent( 0 ), m_bReproducing( false )
{
	// Set up the patrol path
	for( int n = 0; n < PATROL_PATH_LENGTH; ++n )
	{
		m_ppPath.Path_[n] = rand() % 256;
	}// End for n

	// Set the threshold values to the starting threshold value
	unsigned char startThreshold = 128;
	m_tvThresholds.Drink_     = startThreshold;
	m_tvThresholds.Eat_       = startThreshold;
	m_tvThresholds.Fight_     = startThreshold;
	m_tvThresholds.Reproduce_ = 0;//startThreshold;
	m_tvThresholds.Rest_      = startThreshold;
}// End BugCam::Constructor


// Destructor
BugCam::~BugCam()
{}


// Initializes the camera
void BugCam::Initialize( CInputSystem* input, quadTreeTerrain* terrain,
						 QuadTree* tree, SurvivalEngine* engine, 
						 string species )
{
	// Initialize the targeter
	m_tTargeter.Initialize( TARGETER_MODEL );

	// Set all the variables up
	m_Input		= input;
	m_qtTerrain = terrain;
	m_qtTree	= tree;
	m_pEngine   = engine;
	m_sSpecies	= species;
}// End BugCam::Initialize


// Sets the smartpointer to a bug
bool BugCam::SetFocus( SmartPointer& bug )
{
	// Make sure bug is valid, a bug, and of the species that this camera
	// is supposed to target
	if( bug.isValid() && bug->GetType() == "bug" &&
		reinterpret_cast< Bug* >( bug.Get() )->GetSpecies() == m_sSpecies )
	{
		// If the old target is valid
		if( m_spBug.isValid() )
		{
			// Remove it from the object map
			m_qtTree->Remove( m_spBug );
		}// End if

		// Set the bug's state to Resting
		reinterpret_cast< Bug* >( bug.Get() )->SetState( RESTING );

		// Set the bug as the new target
		m_spBug = bug;

		// Insert the bug into the object map
		m_qtTree->Insert( m_spBug );

		// Return true
		return true;
	}// End if
	else
		// Otherwise return false
		return false;
}// End BugCam::SetFocus


// Updates the camera
void BugCam::Update( float elapsedSec )
{
	// Add the amount of elapsed seconds to the percentage
	m_fPercent += elapsedSec;

	// Get the mouse and keyboard from the input system
	CMouse* mouse	 = m_Input->GetMouse();
	CKeyboard* board = m_Input->GetKeyboard();

	// If is is time for the bug in focus to die and the game is still on
	if( m_spBug->TimeToDie() && m_pEngine->GetStatus() == GAME_ON )
	{
		// Remove the old focus from the object map
		m_qtTree->Remove( m_spBug );

		// Create a carcus for the dead bug
		SmartPointer car( new carcus( m_spBug ) );
		m_pEngine->AddCarcus( car );

		// Delete the memory of the old bug
		m_spBug.Delete();

		// Get a new target
		m_spBug = m_pEngine->NewFocus();

		// If we where reproducing, then we need to stop
		m_bReproducing = false;

		// Set the state of the new bug
		reinterpret_cast< Bug* >( m_spBug.Get() )->SetState( RESTING );
	}// End if

	// Update the targeter
	m_tTargeter.Update( elapsedSec );

	// Get a pointer to the bug that we are targeting
	Bug* focus = reinterpret_cast< Bug* >( m_spBug.Get() );

	// Increase the fertility rate
	focus->SetFertility( focus->GetFertility() + elapsedSec );

 	// Update the bugs age
	focus->UpdateAge( elapsedSec );

	// Get the state of the bug in focus
	BugState state = focus->GetState();

	// A case switch to control the states of the bug
	switch( state )
	{
	case RESTING:
		// If a second has passed
		if( m_fPercent >= 1 )
		{
			// Reduce the percentage
			m_fPercent = 0;

			// Adjust the focus's stats
			focus->AdjustStats();
		}// End if

		// Check for a state change
		CheckState();
		break;
	case EATING:
//		logger::event( "BugCam::Update", "Eating", 0 );
		// If the target is still valid
		if( m_spTarget.isValid() )
		{
			// If we are at the target and a second has passed
			if( IsAtTarget() && m_fPercent >= 1 )
			{
				// Reset the percent
				m_fPercent = 0;
				
				// Adjust the stats of the bug in focus
				focus->AdjustStats();
			}// End if
		}// End if

		// Check the state
		CheckState();
		break;
	case DRINKING:
//		logger::event( "BugCam::Update", "Drinking", 0 );
		// If the target is still valid
		if( m_spTarget.isValid() )
		{
			// If we are at the target and a second has passed
			if( IsAtTarget() && m_fPercent >= 1 )
			{
				// Reset the percent
				m_fPercent = 0;

				// Adjust the stats of this bug
				focus->AdjustStats();
			}// End if
		}// End if

		// Otherwise, just check the state
		CheckState();

		break;
	case FIGHTING:
//		logger::event( "BugCam::Update", "Fighting", 0 );
		// If the target is still valid
		if( m_spTarget.isValid() )
		{
			// If we at the target
			if( IsAtTarget() )
			{
				// Calculate the number of particles to add to the system
				int numParts = 3;

				// Get the position of this bug
				float mx, my, mz;
				focus->GetPos( mx, my, mz );

				// Get the position of the target
				float tx, ty, tz;
				reinterpret_cast< Bug* >( m_spTarget.Get() )
											->GetPos( tx, ty, tz );

				// Calculate the slope of the line towards the target
				float sx = tx - mx;
				float sy = ty - my;
				float sz = tz - mz;

				// Create and add numParts particles
				for( int n = 0; n < numParts; ++n )
				{
					Particle temp;
					temp.Color_[0] = 0;
					temp.Color_[1] = 1;
					temp.Color_[2] = 0;

					temp.Position_[0] =  ( float( rand() % 100 ) / 1000 ) - .05;
					temp.Position_[1] = .2;
					temp.Position_[2] =  -1;

					matrix* local = focus->GetCoordSystem();
					local->Transform( temp.Position_ );

					temp.Direction_[0] = ( sx / ATTACK_RANGE ) + 
										float( rand() % 100 ) / 1000;
					temp.Direction_[1] = ( sy / ATTACK_RANGE ) +
										float( rand() % 100 ) / 1000;
					temp.Direction_[2] = ( sz / ATTACK_RANGE ) +
										float( rand() % 100 ) / 1000;

					temp.Age_ = 0;
					temp.MaxAge_ = MAX_PART_AGE;

					m_pEngine->AddParticle( temp );
				}// End for n

				// If a second has passed since we started the attack
				if( m_fPercent >= 1 )
				{
					// Reset percent
					m_fPercent = 0;

					// Adjust the stats of this bug
					focus->AdjustStats();
				}// End if
			}// End if
		}// End if

		// Otherwise, just check the state
		CheckState();
		break;
	case PATROLING:
//		logger::event( "BugCam::Update", "Patroling", 0 );
		// If m_fPercent is >= 1
		if( m_fPercent >= 1 )
		{
			// Reduce the percentage
			m_fPercent = 0;

			// Update thirst, hunger, and energy and make sure they are 
			// in range
			focus->AdjustStats();
		}// End if

		// Otherwise, just check the state
		CheckState();
		break;
	case REPRODUCING:
//		logger::event( "BugCam::Update", "Producing", 0 );
		// Check to see if enough time has passed 
		if( m_bReproducing && m_fPercent >= GESTATION_TIME )
		{
			// Reset percent
			m_fPercent = 0;

			// Set the reproducing flag
			m_bReproducing = false;

			// Create a new bug
			Bug* bug = new Bug( focus->GetSpecies(),
								Gene( m_tvThresholds, m_ppPath ),
								*( focus->GetCoordSystem() ),
								focus->GetListId() );

			// Add it to the engine
			m_pEngine->CreateBug( bug );

			// Delete the bug we used to create a bug
			delete bug;

			// Check the state
			CheckState();
		}// End if
		break;
	default:
		break;
	};// End switch( m_bsState )

	// If the bug is not in the reproducing state
	if( state != REPRODUCING )
	{
		// The bug is moved depending on the mouse.  Left and right movements
		// turn the bug left and right.  When the left mouse button is down
		// the bug moves forward.

		// Get the amount of physical mouse movement
		int dx = 0; int dy = 0;
		mouse->GetMovement( dx, dy );

		// Remove the target from the object map
		m_qtTree->Remove( m_spBug );

		// If we moved the mouse in the x direction
		if( dx != 0 )
		{
			// Calculate the angle of rotation
			float angle = ( ( elapsedSec * ROTATION_UNIT ) / TIME_UNIT ) * 2;

			// If dx is negative, make angle negative
			if( dx < 0 ) angle = -angle;

			// Rotate the bug
			m_spBug->Rotate( 0, angle, 0 );
		}// End if

		//If the left mouse button is down
		if( mouse->ButtonDown( 0 ) )
		{
			// Calculate the distance that the bug has moved
			float distance = ( elapsedSec * MOVEMENT_UNIT ) / TIME_UNIT;
			
			// Move the bug that distance
			m_spBug->Translate( 0, 0, distance );

			// Make sure that the bug is still on the map
			matrix* local = m_spBug->GetCoordSystem();
			int size = m_qtTerrain->GetSize();
			if( (*local)[12] < 0 )    (*local)[12] = 0;
			if( (*local)[14] < 0 )    (*local)[14] = 0;
			if( (*local)[12] > size ) (*local)[12] = size;
			if( (*local)[14] > size ) (*local)[14] = size;

			// Set up the height value of the target
			(*local)[13] = m_qtTerrain->GetHeightAt( (*local)[12],
												     (*local)[14] );
		}// End if 

		// Reinsert the bug into the object map
		m_qtTree->Insert( m_spBug );
	}// End if

	// Set up the camera now.

	// Get the target's local coordinate system
	matrix* local = m_spBug->GetCoordSystem();

	// Set up the camera coordinates
	float at[3]   = { (*local)[12] + X_OFF_SET,
					  (*local)[13] + Y_OFF_SET, 
					  (*local)[14] + Z_OFF_SET };
	float look[3] = { (*local)[12], (*local)[13], (*local)[14] };
	float up[3]	  = { 0, 1, 0 };

	// Set the camera position
	gluLookAt( at[0], at[1], at[2],
		       look[0], look[1], look[2],
			   up[0], up[1], up[2] );
}// End BugCam::Update


// Renders the target bug to the screen
void BugCam::Render()
{
	// Turn on point antialiasing
	glEnable( GL_POINT_SMOOTH );

	// Call the target's render function
	m_spBug->Render();

	// Turn off point antialiasing
	glDisable( GL_POINT_SMOOTH );

	// Get the pointer to the target bug
	Bug* focus = reinterpret_cast< Bug* >( m_spBug.Get() );

	// Disable lighting while we render the stats and the targeter
	glDisable( GL_LIGHTING );

	// Draw the target bugs stats
	float color[3] = { 1, 1, 0 };
	ScreenWriter::Render( 5, 5, color, "Energy: %d", focus->GetEnergy() );

	ScreenWriter::Render( 5, 10, color, "Thirst: %d", focus->GetThirst() );

	ScreenWriter::Render( 5, 15, color, "Hunger: %d", focus->GetHunger() );

	ScreenWriter::Render( 5, 20, color, "Fertility: %d", 
										int( focus->GetFertility() ) );

	ScreenWriter::Render( 5, 25, color, "Age: %d", int( focus->GetAge() ) );

	// Print the bug's state to the screen
	BugState state = focus->GetState();
	int x = 0;
	switch( state )
	{
	case FIGHTING:
		ScreenWriter::Render( 5, 30, color, "State: Fighting %n", &x );
		break;
	case REPRODUCING:
		ScreenWriter::Render( 5, 30, color, "State: Reproducing %n", &x );
		break;
	case DRINKING:
		ScreenWriter::Render( 5, 30, color, "State: Drinking %n", &x );
		break;
	case EATING:
		ScreenWriter::Render( 5, 30, color, "State: Eating %n", &x );
		break;
	case RESTING:
		ScreenWriter::Render( 5, 30, color, "State: Resting %n", &x );
		break;
	case PATROLING:
		ScreenWriter::Render( 5, 30, color, "State: Patroling %n", &x );
		break;
	default:
		break;
	};// End switch state
/*

	// Draw all the threshold data
	ScreenWriter::Render( 40, 5, color, "Drink: %d", m_tvThresholds.Drink_ );
	ScreenWriter::Render( 40, 10, color, "Eat: %d", m_tvThresholds.Eat_   );
	ScreenWriter::Render( 40, 15, color, "Fight: %d", m_tvThresholds.Fight_ );
	ScreenWriter::Render( 40, 20, color, "Reproduce: %d", m_tvThresholds.Reproduce_ );
	ScreenWriter::Render( 40, 25, color, "Rest: %d", m_tvThresholds.Rest_  );
*/
	// Draw the targeter
	m_tTargeter.Render( this );

	// Renable lighting
	glEnable( GL_LIGHTING );
}// End BugCam::Render


// Checks the input for the current state of the bug cam and
// sets the target if there is one needed
void BugCam::CheckState()
{
	// Get the mouse and keyboard
	CMouse* mouse    = m_Input->GetMouse();
	CKeyboard* board = m_Input->GetKeyboard();

	// Get a pointer to the bug that we are pointing to
	Bug* focus = reinterpret_cast< Bug* >( m_spBug.Get() );

	// Get the current bug state
	BugState state = focus->GetState();

	// If the f button is down
	if( board->KeyDown( DIK_F ) )
	{
		// Set the state
		focus->SetState( FIGHTING );

		// Get a target
		m_spTarget = FindTarget( "bug" );
		focus->SetTarget( m_spTarget );

		// If we were not already in the fighting state 
		// and we have a valid target
		if( state != FIGHTING && m_spTarget.isValid() )
		{
			// Calculate the distance between this bug and the target
			float mx, my, mz;
			float tx, ty, tz;

			focus->GetPos( mx, my, mz );
			m_spTarget->GetPos( tx, ty, tz );

			float enemyDist = sqrt( ( ( mx - tx ) * ( mx - tx ) ) +
								    ( ( mz - tz ) * ( mz - tz ) ) );

			// Calculate the new fight threshold
			int newFight = ( ( focus->GetEnergy() * 255 ) / ENERGY_MAX ) * .3 +
						   ( (  enemyDist * 255 ) / SITE_RADIUS ) * .7;
//			newFight = 255 - newFight;

			// Get the old fight threshold value
			int temp = m_tvThresholds.Fight_;

			// Set the fight threshold to the average of the above two values
			m_tvThresholds.Fight_ = ( ( temp + newFight ) / 2 );
		}// End if
		return;
	}// End if

	// If the e button is down
	if( board->KeyDown( DIK_E ) )
	{
		// Set the state
		focus->SetState( EATING );

		// Get a target
		m_spTarget = FindTarget( "plant" );
		focus->SetTarget( m_spTarget );

		// If we where not already in the eating state 
		// and we have a valid target
		if( state != EATING && m_spTarget.isValid() )
		{
			// Calculate the distance between this bug and the target
			float mx, my, mz;
			float tx, ty, tz;

			focus->GetPos( mx, my, mz );
			m_spTarget->GetPos( tx, ty, tz );

			float foodDist = sqrt( ( ( mx - tx ) * ( mx - tx ) ) +
								   ( ( mz - tz ) * ( mz - tz ) ) );

			// Calculate the new eating threshold value
			int Food = ( ( SITE_RADIUS - foodDist ) * 255 ) / SITE_RADIUS;
			int newEat = ( focus->GetHunger() * .7 ) + ( Food * .3 );

//			newEat = 255 - newEat;

			// Get the value of the old eating threshold
			int temp = m_tvThresholds.Eat_;

			// Set the eat threshold value to the average of the above values
			m_tvThresholds.Eat_ = ( ( newEat + temp ) / 2 );
		}// End if
		return;
	}// End if

	// If the d button is down
	if( board->KeyDown( DIK_D ) )
	{
		// Set the state
		focus->SetState( DRINKING );

		// Get a target
		m_spTarget = FindTarget( "waterpool" );
		focus->SetTarget( m_spTarget );

		// If we where not already in the drinking state
		// and if the target is valid
		if( state != DRINKING && m_spTarget.isValid() )
		{
			// Calculate the distance between this bug and the target
			float mx, my, mz;
			float tx, ty, tz;

			focus->GetPos( mx, my, mz );
			m_spTarget->GetPos( tx, ty, tz );

			float waterDist = sqrt( ( ( mx - tx ) * ( mx - tx ) ) +
								    ( ( mz - tz ) * ( mz - tz ) ) );

			// Calculate the new drinking threshold value
			int Water = ( ( SITE_RADIUS - waterDist ) * 255 ) / SITE_RADIUS;
			int newDrink = ( focus->GetThirst() * .7 ) + ( Water * .3 );
//			newDrink = 255 - newDrink;

			// Get the old drinking threshold value
			int temp = m_tvThresholds.Drink_;

			// Set the drinking threshold value to the average of the above
			m_tvThresholds.Drink_ = ( ( newDrink + temp ) / 2 );
		}// End if
		return;
	}// End if

	// If the r button is down
	if( board->KeyDown( DIK_R ) )
	{
		// See if we can actually reproduce now
		if( ! m_bReproducing					    &&
			focus->GetEnergy() >= GESTATION_COST    && 
			focus->GetFertility() >= m_pEngine->GetFertilityRate() )
		{
			// Set the state
			focus->SetState( REPRODUCING );

			// Set up the reproducing flag
			m_bReproducing = true;

			// Reset the bug's energy
			focus->SetEnergy( focus->GetEnergy() - GESTATION_COST );

			// Reset the bug's fertility
			focus->SetFertility( 0 );

			// Calculate the new reproducing threshold value
			int newRepro = ( ( focus->GetEnergy() - GESTATION_COST ) * 255 ) /
						   ( ENERGY_MAX - GESTATION_COST );
			newRepro = 255 - newRepro;

			// Get the value of the old reproducing threshold
			int temp = m_tvThresholds.Reproduce_;

			// Set the reproducing threshold to the average of the above
			m_tvThresholds.Reproduce_ = ( ( newRepro + temp ) / 2 );
		}// End if
		
		// Get a target
		SmartPointer null;
		m_spTarget = null;
		focus->SetTarget( m_spTarget );
		return;
	}// End if

	// If no buttons are down and the bug is not moving
	if( ! mouse->ButtonDown( 0 ) )
	{
		// Set the state to resting
		focus->SetState( RESTING );

		// If we where not already in the resting state
		if( state != RESTING )
		{
			// Calculate the new resting threshold value
			int newRest = ENERGY_MAX - focus->GetEnergy();

			// Get the value of the resting threshold
			int temp = m_tvThresholds.Rest_;

			// Set the resting threshold value to the average of the above
			m_tvThresholds.Rest_ = ( 255 - ( ( newRest + temp ) / 2 ) );
		}// End if

		// Set the target
		SmartPointer null;
		m_spTarget = null;
		focus->SetTarget( m_spTarget );
		return;
	}// End if

	// Set the bug to the default state
	focus->SetState( PATROLING );
	SmartPointer null;
	m_spTarget = null;
	focus->SetTarget( m_spTarget );
}// End BugCam::CheckState


// Finds the nearest target, if there is one and returns it.  type is the
// type of object that we are looking for
SmartPointer BugCam::FindTarget( string type ) const
{
	// Get all of the objects within site
	vector< SmartPointer > objects;
	float x, y, z;
	m_spBug->GetPos( x, y, z );
	m_qtTree->GetObjects( objects, SITE_RADIUS, x, z );

	// A place to store the closest target and the distance to it
	float distance = SITE_RADIUS + 1;
	SmartPointer closest;

	// For each object in site
	vector< SmartPointer >::iterator obj = objects.begin();
	for( obj; obj != objects.end(); ++obj )
	{
		// If obj is of the correct type
		if( type == (*obj)->GetType() )
		{
			// If it's a bug
			if( (*obj)->GetType() == "bug" )
			{
				// Make sure that it is a member of another species
				if( reinterpret_cast< Bug* >( obj->Get() )->GetSpecies() !=
					m_sSpecies &&
					(*obj)->GetId() != m_spBug->GetId() )
				{
					// Get the position of that bug
					float ox, oy, oz;
					(*obj)->GetPos( ox, oy, oz );

					// Calculate the distance to that bug
					float distToCur = 
						sqrt( ( ( x - ox ) * ( x - ox ) ) + 
							  ( ( z - oz ) * ( z - oz ) ) );
					
					// If the distance to the current test object is less than
					// the distance to the current target
					if( distToCur < distance )
					{
						// Set the current object at the target
						closest = (*obj);
						distance = distToCur;
					}// End if
				}// End if
			}// End if
			else
			{
				// If it is a waterpool
				if( (*obj)->GetType() == "waterpool" )
				{
					// If it has no water in it
					if( ! reinterpret_cast< WaterPool* >( obj->Get() )
														->HasWater() )
					{
						continue;
					}// End if
				}// End if
				// Get the position of that obj
				float ox, oy, oz;
				(*obj)->GetPos( ox, oy, oz );

				// Calculate the distance to that obj
				float distToCur = 
					sqrt( ( ( x - ox ) * ( x - ox ) ) + 
						  ( ( z - oz ) * ( z - oz ) ) );
				
				// If the distance to the current test object is less than
				// the distance to the current target
				if( distToCur < distance )
				{
					// Set the current object at the target
					closest = (*obj);
					distance = distToCur;
				}// End if
			}// End else
		}// End if
	}// End for obj

	// Return the closest
	return closest;
}// End BugCam::FindTarget


// Returns true if the bug in focus is at it's target
bool BugCam::IsAtTarget() const
{
	// If the target is still valid
	if( m_spTarget.isValid() )
	{
		// Get the position of the bug in focus
		float x, y, z;
		m_spBug->GetPos( x, y, z );

		// If the target is another bug
		if( m_spTarget->GetType() == "bug" )
		{
			// Return true if the target is in attack range
			return m_spTarget->InSphere( x, z, BUG_RADIUS + ATTACK_RANGE );
		}// End if
		else
		{
			// Return true if the target is at this bug
			return m_spTarget->InSphere( x, z, BUG_RADIUS );
		}// End else
	}// End if
	
	// return false
	return false;
}// End BugCam::IsAtTarget