//---------------------------
// Includes
//---------------------------
#include "StdAfx.h"
#include "Level.h"

#include "Painter.h"
#include "ResourceLoader.h"
#include "InputState.h"
#include "CollisionMesh.h"
#include "Image.h"
//---------------------------
// Defines
//---------------------------
//

//---------------------------
// Constructor & Destructor
//---------------------------
Level::Level(ResourceLoader* resourceLoaderPtr, const String& imagename,const char* meshName, const char* raycastMeshName)
:LevelProp(resourceLoaderPtr,imagename)
,m_RayCastCollisionMeshPtr(0)
{
	m_CollisionMeshPtr = resourceLoaderPtr->GetCollisionMesh();
	m_CollisionMeshPtr->CreateFromFile(meshName);
	m_CollisionMeshPtr->Simplify(2,5);

	m_RayCastCollisionMeshPtr = resourceLoaderPtr->GetCollisionMesh();
	m_RayCastCollisionMeshPtr->CreateFromFile(raycastMeshName);
	m_RayCastCollisionMeshPtr->Simplify(2,5);

	for(int i = 0; i < MAX_NR_OF_PITS;++i)
	{
		m_PitCollisionMeshPtrArr[i] = 0;
	}

	m_PitCollisionMeshPtrArr[0] = resourceLoaderPtr->GetCollisionMesh();
	m_PitCollisionMeshPtrArr[0]->CreateRect(905,520,100,270);

	m_PitCollisionMeshPtrArr[1] = resourceLoaderPtr->GetCollisionMesh();
	m_PitCollisionMeshPtrArr[1]->CreateRect(3520,660,100,270);
}

Level::~Level()
{
	SafeDelete(&m_ImagePtr);
	delete m_CollisionMeshPtr;

	for(int i = 0; i < MAX_NR_OF_PITS;++i)
	{
		delete m_PitCollisionMeshPtrArr[i];
		m_PitCollisionMeshPtrArr[i] = 0;
	}

	delete m_RayCastCollisionMeshPtr;
}



void Level::Paint(Painter *painterPtr, Matrix2D matTransform)
{
	painterPtr->SetTransformMatrix(m_MatWorld * matTransform);
	painterPtr->DrawImage(m_ImagePtr,0,0);

	// shows collision mesh
	/*painterPtr->SetColor(255,0,0,127);
	painterPtr->SetTransformMatrix(matTransform);
	painterPtr->FillCollisionMesh(m_CollisionMeshPtr);
	painterPtr->SetColor(0,0,255,127);
	painterPtr->FillCollisionMesh(m_RayCastCollisionMeshPtr);
	painterPtr->SetColor(0,255,0,127);
	for(int i = 0; i < MAX_NR_OF_PITS;++i)
	{
		painterPtr->FillCollisionMesh(m_PitCollisionMeshPtrArr[i]);
	}*/
}

void Level::Tick(InputState *inputStatePtr)
{
	//adjust rotionAngle: pas m_Angle aan zodat het image roteert	
	m_Angle+=m_AngularSpeed*inputStatePtr->GetDeltaTime();

	//calculate world matrix
	Matrix2D matCenter, matRotate, matPosition;

	//gebruik matCenter om het image te transleren zodat het midden van het image  op de oorsprong staat
	matCenter.SetAsTranslate(-m_ImagePtr->GetWidth()/2/*-80*/,-m_ImagePtr->GetHeight()/2);
	//Gebruik matRotate om een matrix te maken die een rotatie veroorzaakt met een hoek m_Angle
	matRotate.SetAsRotate(m_Angle);
	//Maak een matTranslate die het image op de positie m_Position zal verplaatsen
	matPosition.SetAsTranslate(m_Position);
	//calculate world matrix: Vermenigvuldig bovenstaande matrices en het resultaat wordt opgeslagen in m_MatWorld
	m_MatWorld = matCenter * matRotate * matPosition;
	//transform the collisionMesh
	m_CollisionMeshPtr->SetTransformMatrix(m_MatWorld);
	
}


void Level::CheckCollision(Character *characterPtr) const
{
	CollisionMesh *characterVMesh=characterPtr->GetVCollisionMesh();
	CollisionMesh *characterHMesh=characterPtr->GetHCollisionMesh();
	characterPtr->SetHitGround(false);
	characterPtr->SetHitWall(false);

	//check the vertical collision
	if(characterVMesh->HitTest(m_CollisionMeshPtr))
	{
		Rect2D characterBounds = characterVMesh->GetBounds();
		Rect2D collisionRect = characterVMesh->CollisionTest(m_CollisionMeshPtr);
		double depth = collisionRect.MaxY-collisionRect.MinY;

		if(fabs(characterBounds.MaxY - collisionRect.MaxY)<.1)
		{
			//the actor is dropping on the levelpart and his speeddirection is down
			Point2D newPos = characterPtr->GetPosition();
			newPos.y-=depth;
			characterPtr->SetPosition(newPos);
			Vector2D newVelocity = characterPtr->GetVelocity();
			newVelocity.y=0;
			characterPtr->SetVelocity(newVelocity);
			characterPtr->SetHitGround(true);
		}

		if(fabs(characterBounds.MinY - collisionRect.MinY)<.1)
		{
			//the actor is bumping his head while jumping
			Point2D newPos = characterPtr->GetPosition();
			newPos.y+=depth;
			characterPtr->SetPosition(newPos);
			Vector2D newVelocity = characterPtr->GetVelocity();
			newVelocity.y=0;
			characterPtr->SetVelocity(newVelocity);

		}
	}

	//check the horizontal collision
	//The character could have been moved by a vertical check
	if(characterHMesh->HitTest(m_CollisionMeshPtr))
	{
		Rect2D characterBounds = characterHMesh->GetBounds();
		Rect2D collisionRect = characterHMesh->CollisionTest(m_CollisionMeshPtr);
		double width = collisionRect.MaxX-collisionRect.MinX;
		//the actor is hitting the left of a levelpart
		if(fabs(characterBounds.MaxX - collisionRect.MaxX)<.1)
		{
			Point2D newPos = characterPtr->GetPosition();
			newPos.x-=width;
			characterPtr->SetPosition(newPos);
		}
		//the actor is hitting the right of a levelpart
		if(fabs(characterBounds.MinX - collisionRect.MinX)<.1)
		{
			Point2D newPos = characterPtr->GetPosition();
			newPos.x+=width;
			characterPtr->SetPosition(newPos);
		}
		characterPtr->SetHitWall(true);
	}
	
	for(int i = 0; i < MAX_NR_OF_PITS;++i)
	{
		if(characterVMesh->HitTest(m_PitCollisionMeshPtrArr[i]))
		{
			characterPtr->HitPit();
		}
	}

	Archer *archer = dynamic_cast<Archer *>(characterPtr);
	if(archer != NULL)
	{
		CheckArrowCollision(archer->getArrow());
	}
}

void Level::CheckArrowCollision(Arrow *arrowPtr) const
{
		CollisionMesh *arrowMesh=arrowPtr->GetCollisionMesh();
		//check the collision
		if(arrowMesh->HitTest(m_CollisionMeshPtr))
		{
			arrowPtr->Reset();
		}
	}

void Level::CheckCollision(LevelProp *levelPropPtr, InputState *inputStatePtr) const
{

	if(!levelPropPtr->UsesRayCast())
	{
		CollisionMesh *levelPropMesh=levelPropPtr->GetCollisionMesh();

		//check the vertical collision
		if(levelPropMesh->HitTest(m_CollisionMeshPtr))
		{
			Rect2D levelPropBounds = levelPropMesh->GetBounds();
			Rect2D collisionRect = levelPropMesh->CollisionTest(m_CollisionMeshPtr);
			double depth = collisionRect.MaxY-collisionRect.MinY;

			if(fabs(levelPropBounds.MaxY - collisionRect.MaxY)<.1)
			{
				//the actor is dropping on the levelpart and his speeddirection is down
				Point2D newPos = levelPropPtr->GetPosition();
				newPos.y-=depth;
				levelPropPtr->SetPosition(newPos);
				Vector2D newVelocity = levelPropPtr->GetVelocity();
				newVelocity.y=0;
				levelPropPtr->SetVelocity(newVelocity);
			}

			if(fabs(levelPropBounds.MinY - collisionRect.MinY)<.1)
			{
				//the actor is bumping his head while jumping
				Point2D newPos = levelPropPtr->GetPosition();
				newPos.y+=depth;
				levelPropPtr->SetPosition(newPos);
				Vector2D newVelocity = levelPropPtr->GetVelocity();
				newVelocity.y=0;
				levelPropPtr->SetVelocity(newVelocity);

			}
		}

	} else {
		double dTime = inputStatePtr->GetDeltaTime();
		Point2D PropPosition = levelPropPtr->GetPosition();
		Vector2D PropVelocity = levelPropPtr->GetVelocity();
		Hit hit;

		//Hier zit een probleem met de raycast waardoor die soms geen hits registreerd
		int numHits = m_RayCastCollisionMeshPtr->Raycast(PropPosition, PropVelocity, dTime, &hit, 1);
		
		if( numHits == 0 )// Move the levelProp, no hit
		{
			levelPropPtr->SetPosition(PropPosition + PropVelocity * dTime);
		}
		else//HIT!!
		{
			// We have a hit, bounce
			Vector2D velocityNormal = PropVelocity.On(hit.Normal);
			Vector2D velocityTangent = PropVelocity-velocityNormal;
			Vector2D reflectedVelocity;
			
			//The levelProp bounces of the suface and flies away
			if(velocityNormal.Length()>140) 
			{
				//Bounce with restitution
				reflectedVelocity = levelPropPtr->GetRestitution() * (velocityTangent - velocityNormal);
				// Move the levelProp, until the hit using original velocity
				levelPropPtr->SetPosition(PropPosition + (PropVelocity * hit.Time));
				inputStatePtr->SetDeltaTime(hit.Time);

				levelPropPtr->SetVelocity(reflectedVelocity);
			}

			//When the velocityNormal is very small, the levelProp will glide instead of bounce
			else
			{
				reflectedVelocity = velocityTangent;
				// Move the levelProp, until the hit using original velocity
				levelPropPtr->SetPosition(PropPosition + (PropVelocity * hit.Time));
				inputStatePtr->SetDeltaTime(hit.Time);
				levelPropPtr->SetVelocity(levelPropPtr->GetFriction() * reflectedVelocity);
				//Put the levelProp a bit farther away from the hitpoint to make sure there isn't an immediate hit afterwards
				levelPropPtr->SetPosition(PropPosition - .3*hit.Normal);
			}
		}
	}
	
}