/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * Copyright (C) 2006-2010 - Frictional Games
 *
 * This file is part of HPL1 Engine.
 */

#include "hpl1/engine/physics/Body2D.h"

#include "hpl1/engine/graphics/Mesh2d.h"
#include "hpl1/engine/math/Math.h"
#include "hpl1/engine/physics/CollideData2D.h"
#include "hpl1/engine/physics/Collider2D.h"
#include "hpl1/engine/scene/Entity2D.h"
#include "hpl1/engine/scene/Node2D.h"

namespace hpl {

//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

cBody2D::cBody2D(const tString &asName, cMesh2D *apMesh, cVector2f avSize, cCollider2D *apCollider, int alID)
	: iEntity2D(asName) {
	mvSize = avSize;
	mpMesh = apMesh;
	mpCollider = apCollider;

	mpCollMesh = mpMesh->CreateCollisonMesh(0, mvSize);
	mpBaseCollMesh = mpMesh->CreateCollisonMesh(0, mvSize);

	/*for(int i=0;i<(int)mpCollMesh->mvPos.size();i++)
	{
		Log("Pos%d: %s\n", i,mpCollMesh->mvPos[i].ToString().c_str());
	}

	for(int i=0;i<(int)mpCollMesh->mvNormal.size();i++)
	{
		Log("Norm%d: %s\n", i,mpCollMesh->mvNormal[i].ToString().c_str());
	}*/

	// Log("------------\n");

	// Set some default values to the properties
	mfMaxVel = 0;
	mfAcc = 1;

	mfAirFriction = 0.005f;
	mfGroundFriction = 0.3f;
	mfGravity = 0.4f;
	mfMaxGravityVel = 3;

	mbCollidable = false;
	mbCollides = true;
	mbMoved = false;
	mbOnGround = false;
	mbGroundFrictionX = false;
	mbGroundFrictionY = false;

	mvCollideCount = 0;

	mvMovement = 0;

	mbAttachToGround = false;
	mbAttachBodies = true;

	mpParentBody = NULL;

	mpNode = NULL;
	mlID = alID;

	mlCollideFlag = eFlagBit_0;
	mlCollideType = eFlagBit_1;
}

//-----------------------------------------------------------------------

cBody2D::~cBody2D() {
	hplDelete(mpCollMesh);
	hplDelete(mpBaseCollMesh);
}

//-----------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

void cBody2D::UpdateLogic(float afTimeStep) {
	cVector3f vStartPos = GetPosition();

	if (mbAttachToGround) {
		if (mpParentBody != NULL) {
			// What can be done is to add this to the force, and then att the end redcue the same amout
			// This will allow the body to add the velocity to it's jumps for example.
			cVector3f vMovement = mpParentBody->GetMovement(); // mpParentBody->GetPosition() - mpParentBody->GetLastPosition();
			SetPosition(GetPosition() + vMovement);
		}
	}

	cCollideData2D CollideData;
	// Update Gravity

	mbGroundFrictionX = false;

	if (mbCollides && mfGravity > 0) {
		cVector3f vTempPos = GetPosition();

		float fXSize = mvSize.x;
		cRect2f Rect(vTempPos.x - fXSize / 2, vTempPos.y + mfGravity,
					 fXSize, mvSize.y / 2);

		if (mpCollider->CollideRect(Rect, GetCollideFlag(), NULL)) {
			// We are still on ground.
			mbOnGround = true;
			mbGroundFrictionX = true;
			// Log("Standing on ground\n");
		} else {
			// If the player used to be on ground, add some extra force
			if (mbOnGround) {
				mvForce.y += mfGravity * 2;
			}

			mbOnGround = false;
			mvForce.y += mfGravity;
			if (mvForce.y > mfMaxGravityVel)
				mvForce.y = mfMaxGravityVel;
		}
	}

	// Add the Y Axis and check for collision
	AddPosXY(cVector2f(0, mvForce.y));

	if (mbCollides) {
		UpdateCollisionMesh();
		UpdateBoundingBox();
		if (mpCollider->CollideBody(this, &CollideData)) {
			if (mvLastCollidePos.y == GetPosition().y)
				mvCollideCount.y++;

			if (mvForce.y > 0) {
				if (mbAttachToGround) {
					// If collided with a  body, make it the new reference frame.
					if (CollideData.mlstBodies.size() > 0) {
						tBody2DListIt it = CollideData.mlstBodies.begin();
						if (*it != mpParentBody) {
							if (mpParentBody != NULL)
								DetachBody(mpParentBody);

							SetParentBody(*it);
							(*it)->AttachBody(this);
							// Log("Attaching!\n");
						}
					} else {
						if (mpParentBody != NULL) {
							DetachBody(mpParentBody);
							mpParentBody = NULL;
							// Log("Detaching!\n");
						}
					}
				}

				mbOnGround = true;
				mvForce.y = 0;
			}

			if (mvCollideCount.y > 0) {

				mvForce.y = 0;
			}

			mvLastCollidePos.y = GetPosition().y;
			mbGroundFrictionX = true;
		} else {
			mvCollideCount.y = 0;
			if (!mbOnGround)
				mvLastCollidePos.y = -10000;
			// mbGroundFrictionX = false;
		}

		if (mbOnGround == false && mbAttachToGround) {
			if (mpParentBody != NULL) {
				DetachBody(mpParentBody);
				mpParentBody = NULL;
				// Log("Detaching!\n");
			}
		}
	}

	// Add the X Axis and check for collision
	AddPosXY(cVector2f(mvForce.x, 0));

	if (mbCollides) {
		UpdateCollisionMesh();
		UpdateBoundingBox();
		if (mpCollider->CollideBody(this, NULL)) {
			if (mvLastCollidePos.x == GetPosition().x)
				mvCollideCount.x++;

			if (mvCollideCount.x > 0) {
				mvForce.x = 0;
			}

			mvLastCollidePos.x = GetPosition().x;
		} else {
			mvCollideCount.x = 0;
			mvLastCollidePos.x = -10000;
		}
	}

	// Update the force
	float fAngle = 0, fStrength = 0;
	cMath::GetAngleFromVector(mvForce, &fAngle, &fStrength);

	fStrength -= mfAirFriction; // This is the air friction.
								// It should be combined with the friction of the collided material.

	if (fStrength < 0)
		fStrength = 0;

	SetForce(fAngle, fStrength);

	// Don't do any friction if the body has moved.
	// if(!mbMoved) Or?
	{
		if (mbGroundFrictionX && mvForce.x != 0) {
			// Log("GroundFriction!\n");
			// Log("Force: %s\n",mvForce.ToString().c_str());
			if (mvForce.x > 0) {
				mvForce.x -= mfGroundFriction;
				if (mvForce.x < 0)
					mvForce.x = 0;
			} else {
				mvForce.x += mfGroundFriction;
				if (mvForce.x > 0)
					mvForce.x = 0;
			}
			// Log("Force: %s\n",mvForce.ToString().c_str());
		}
	}

	mvMovement = GetPosition() - vStartPos;

	/*tBody2DListIt BodyIt = mlstAttachedBodies.begin();
	for(;BodyIt != mlstAttachedBodies.end();BodyIt++)
	{
		cBody2D* pBody = *BodyIt;

		pBody->SetPosition(pBody->GetPosition() + vMovement);
	}*/

	if (mpNode) {
		mpNode->SetPosition(GetWorldPosition());
	}

	mbMoved = false;
}

//-----------------------------------------------------------------------

void cBody2D::Move(float afValue) {
	float fAngle = 0, fStrength = 0;

	cVector2f vForwardVec = cMath::GetVectorFromAngle2D(mvRotation.z, 1);
	cVector2f vMovement = cMath::ProjectVector2D(mvForce, vForwardVec);
	cMath::GetAngleFromVector(vMovement, &fAngle, &fStrength);

	if (fStrength < mfMaxVel) {
		float fTempAcc = mfAcc;

		if (fStrength + fTempAcc > mfMaxVel) {
			fTempAcc -= (fStrength + fTempAcc) - mfMaxVel;
		}

		mvForce += vForwardVec * fTempAcc;

		mbMoved = true;
	}
}

//-----------------------------------------------------------------------

void cBody2D::AddForce(float afAngle, float afStrength) {
	cVector2f vForce = cMath::GetVectorFromAngle2D(afAngle, afStrength);

	AddForce(vForce);
}

//-----------------------------------------------------------------------

void cBody2D::AddForce(const cVector2f &avForce) {
	mvForce += avForce;
}

//-----------------------------------------------------------------------

void cBody2D::SetForce(float afAngle, float afStrength) {
	cVector2f vForce = cMath::GetVectorFromAngle2D(afAngle, afStrength);
	SetForce(vForce);
}

//-----------------------------------------------------------------------

void cBody2D::SetForce(const cVector2f &avForce) {
	mvForce = avForce;
}

//-----------------------------------------------------------------------

const cRect2f &cBody2D::GetBoundingBox() {
	return mBoundingBox;
}

//-----------------------------------------------------------------------

bool cBody2D::UpdateBoundingBox() {
	cVector2f vSize;

	/*if(mvRotation.z != 0)
	{
		//Only Temp...
		float fMaxSize = sqrt(mvSize.x*mvSize.x + mvSize.y*mvSize.y);

		vSize.x = fMaxSize;
		vSize.y = fMaxSize;
	}
	else*/
	{
		vSize = mvSize;
	}

	mBoundingBox = cRect2f(cVector2f(GetWorldPosition().x - vSize.x / 2,
									 GetWorldPosition().y - vSize.y / 2),
						   vSize);

	return true;
}

//-----------------------------------------------------------------------

void cBody2D::UpdateCollisionMesh() {
	cVector2f vPos(GetPosition().x, GetPosition().y);
	for (int i = 0; i < (int)mpCollMesh->mvPos.size(); i++) {
		mpCollMesh->mvPos[i] = vPos + mpBaseCollMesh->mvPos[i];
	}
}

//-----------------------------------------------------------------------

cCollisionMesh2D *cBody2D::GetCollisionMesh() {
	return mpCollMesh;
}

//-----------------------------------------------------------------------

void cBody2D::AttachBody(cBody2D *apBody) {
	mlstAttachedBodies.push_back(apBody);
}

//-----------------------------------------------------------------------

void cBody2D::DetachBody(cBody2D *apBody) {
	tBody2DListIt it = mlstAttachedBodies.begin();
	for (; it != mlstAttachedBodies.end(); it++) {
		if (*it == apBody) {
			mlstAttachedBodies.erase(it);
			break;
		}
	}
}

//-----------------------------------------------------------------------

void cBody2D::SetParentBody(cBody2D *apBody) {
	mpParentBody = apBody;
}

//-----------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
//////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------

void cBody2D::AddPosXY(cVector2f avPosAdd) {
	cVector3f vPos = GetPosition();
	vPos += avPosAdd;
	SetPosition(vPos);
}

//-----------------------------------------------------------------------
} // namespace hpl
