/* 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/scene/ImageEntity.h"
#include "hpl1/engine/graphics/Graphics.h"
#include "hpl1/engine/graphics/GraphicsTypes.h"
#include "hpl1/engine/graphics/ImageEntityData.h"
#include "hpl1/engine/graphics/RenderObject2D.h"
#include "hpl1/engine/graphics/Renderer2D.h"
#include "hpl1/engine/impl/tinyXML/tinyxml.h"
#include "hpl1/engine/math/Math.h"
#include "hpl1/engine/resources/ImageEntityManager.h"
#include "hpl1/engine/resources/Resources.h"
#include "hpl1/engine/system/String.h"
#include "hpl1/engine/system/low_level_system.h"

namespace hpl {

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

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

cImageEntity::cImageEntity(tString asName, cResources *apResources, cGraphics *apGraphics,
						   bool abAutoDeleteData)
	: iEntity2D(asName) {
	mvSize = cVector2f(-1, -1);

	mbFlipH = false;
	mbFlipV = false;

	mpAnimation = NULL;
	mfFrameNum = 0;
	mfAnimSpeed = 0.5f;
	mlFrame = 0;
	mlLastFrame = -1;

	mbFlashing = false;

	mfAlpha = 1.0f;

	mpEntityData = NULL;

	mpResources = apResources;
	mpGraphics = apGraphics;

	UpdateBoundingBox();

	msTempString = "Default";

	mbAnimationPaused = false;
	mbRotationHasChanged = true;

	mbAutoDeleteData = abAutoDeleteData;

	mbLoopAnimation = true;
}

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

cImageEntity::~cImageEntity() {
	if (mbAutoDeleteData) {
		if (mpEntityData)
			mpResources->GetImageEntityManager()->Destroy(mpEntityData);
	}
}

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

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

//-----------------------------------------------------------------------
int cImageEntity::GetMaxFrameNum() {
	if (mpAnimation == NULL)
		return 0;

	// One for the -1, one for the frame jump, and one because we start with 0.
	return (int)mpAnimation->mvFrameNums.size() - 3;
}

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

bool cImageEntity::SetAnimation(const tString &asName, bool abLoop) {
	mbLoopAnimation = abLoop;

	if (mpAnimation->msName == asName)
		return true;

	cImageAnimation *pAnim = mpEntityData->GetAnimationByName(asName);
	if (pAnim == NULL)
		return false;

	mpAnimation = pAnim;
	mfFrameNum = 0;
	return true;
}

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

const tString &cImageEntity::GetCurrentAnimation() const {
	if (mpAnimation == NULL) {
		return msTempString;
	}

	return mpAnimation->msName;
}
//-----------------------------------------------------------------------

void cImageEntity::StopAnimation() {
	for (int i = 0; i < (int)mpAnimation->mvFrameNums.size(); i++) {
		if (mpAnimation->mvFrameNums[i] == -1) {
			mlFrame = i;
			mlLastFrame = i;
			mfFrameNum = (float)i;
			break;
		}
	}
	if (mbLoopAnimation)
		mbLoopAnimation = false;
}

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

void cImageEntity::SetSize(const cVector2f &avSize) {
	if (avSize.x == mvSize.x && avSize.y == mvSize.y)
		return;

	mvSize.x = avSize.x == 0 ? 0.001f : avSize.x;
	mvSize.y = avSize.y == 0 ? 0.001f : avSize.y;

	mbSizeHasChanged = true;
	mbRotationHasChanged = true;
}

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

void cImageEntity::SetFlipH(bool abX) {
	if (mbFlipH == abX)
		return;

	mbFlipH = abX;
	mbRotationHasChanged = true;
}

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

void cImageEntity::SetFlipV(bool abX) {
	if (mbFlipV == abX)
		return;

	mbFlipV = abX;
	mbRotationHasChanged = true;
}

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

void cImageEntity::UpdateLogic(float afTimeStep) {
	if (mbFlashing) {
		float fAlpha = GetAlpha();
		if (mfFlashAdd < 0) {
			fAlpha += mfFlashAdd * 7;
			if (fAlpha < 0) {
				fAlpha = 0;
				mfFlashAdd = -mfFlashAdd;
			}
			SetAlpha(fAlpha);
		} else {
			fAlpha += mfFlashAdd;
			if (fAlpha > 1) {
				fAlpha = 1;
				mbFlashing = false;
			}
			SetAlpha(fAlpha);
		}
	}

	if (mpAnimation && !mbAnimationPaused) {
		if (mpAnimation->mvFrameNums[(int)mfFrameNum] != -1) {
			mfFrameNum += mfAnimSpeed * mpAnimation->mfSpeed;

			int lFrameNum = (int)mfFrameNum;

			if (mpAnimation->mvFrameNums[lFrameNum] == -1) {
				if (mbLoopAnimation) {
					float fTemp = (float)mpAnimation->mvFrameNums[lFrameNum + 1];

					mfFrameNum = fTemp + cMath::GetFraction(mfFrameNum);
				}
			}
		}
	}
}

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

bool cImageEntity::AnimationIsPlaying() {
	int lFrameNum = (int)mfFrameNum;
	return mpAnimation->mvFrameNums[lFrameNum] != -1;
}

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

void cImageEntity::Render() {
	if (!mbIsActive)
		return;

	/*Update frame stuff if needed here*/

	mvTransform = GetWorldPosition();

	/// Get the frame to be used //////////////
	if (mpEntityData->GetFrameNum() == 1) {
		mlFrame = 0;
		mlLastFrame = 0;
	} else {
		mlLastFrame = mlFrame;
		mlFrame = mpAnimation->mvFrameNums[(int)mfFrameNum];

		if (mlFrame == -1)
			mlFrame = mpAnimation->mvFrameNums[((int)mfFrameNum) - 1];

		/// If Frame has changed update vertexes
		if (mlLastFrame != mlFrame) {
			tVertexVec *pVtx = &mpEntityData->GetImageFrame(mlFrame)->mvVtx;

			for (int i = 0; i < (int)pVtx->size(); i++) {
				mvVtx[i].tex.x = (*pVtx)[i].tex.x;
				mvVtx[i].tex.y = (*pVtx)[i].tex.y;
				mvVtx[i].tex.z = (*pVtx)[i].tex.z;
			}
		}
	}

	if (mbSizeHasChanged) {
		for (int i = 0; i < (int)mvBaseVtx.size(); i++) {
			// Slow as hell!! Change this perhaps?
			// This also only works on square meshes...
			mvBaseVtx[i].pos.x = ABS(mvBaseVtx[i].pos.x) / mvBaseVtx[i].pos.x;
			mvBaseVtx[i].pos.y = ABS(mvBaseVtx[i].pos.y) / mvBaseVtx[i].pos.y;

			mvBaseVtx[i].pos.x *= mvSize.x / 2;
			mvBaseVtx[i].pos.y *= mvSize.y / 2;
			mvBaseVtx[i].pos.z = 0;
		}
	}

	/// If rotation or frame has changed, update the vertexes
	if (mbRotationHasChanged || mfCurrentAngle != GetWorldRotation().z) {
		mbRotationHasChanged = false;
		mfCurrentAngle = GetWorldRotation().z;

		float fSin = sin(mfCurrentAngle);
		float fCos = cos(mfCurrentAngle);

		float fNormZ = 3;
		if (mbFlipV)
			fNormZ = 1;
		if (mbFlipH)
			fNormZ *= -1;

		for (int i = 0; i < (int)mvVtx.size(); i++) {
			mvVtx[i].pos.x = mvBaseVtx[i].pos.x * fCos - mvBaseVtx[i].pos.y * fSin;
			mvVtx[i].pos.y = mvBaseVtx[i].pos.x * fSin + mvBaseVtx[i].pos.y * fCos;

			if (mbFlipH)
				mvVtx[i].pos.x = -mvVtx[i].pos.x;
			if (mbFlipV)
				mvVtx[i].pos.y = -mvVtx[i].pos.y;

			mvVtx[i].norm.x = fCos;
			mvVtx[i].norm.y = fSin;
			mvVtx[i].norm.z = fNormZ;
		}
	}
	cRenderObject2D _obj = cRenderObject2D(
		mpEntityData->GetImageFrame(mlFrame)->mpMaterial,
		&mvVtx, &mvIdxVec,
		ePrimitiveType_Quad, GetWorldPosition().z,
		mBoundingBox, NULL, &mvTransform);
	// Add the render object.
	mpGraphics->GetRenderer2D()->AddObject(_obj);
}

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

bool cImageEntity::LoadData(TiXmlElement *apRootElem) {
	tString sDataName = cString::ToString(apRootElem->Attribute("DataName"), "");

	mbFlipH = cString::ToBool(apRootElem->Attribute("FlipH"), false);
	mbFlipV = cString::ToBool(apRootElem->Attribute("FlipV"), false);
	mvRotation.z = cMath::ToRad(cString::ToFloat(apRootElem->Attribute("Angle"), 0));
	mvSize.x = cString::ToFloat(apRootElem->Attribute("Width"), 2);
	mvSize.y = cString::ToFloat(apRootElem->Attribute("Height"), 2);
	int lAnimNum = cString::ToInt(apRootElem->Attribute("AnimNum"), 0);

	return LoadEntityData(sDataName, lAnimNum);
}

//-----------------------------------------------------------------------
bool cImageEntity::LoadEntityData(tString asDataName, int alAnimNum) {
	cImageEntityData *pImageData = mpResources->GetImageEntityManager()->CreateData(asDataName);

	if (pImageData == NULL) {
		error("Couldn't load Data '%s' for entity '%s'", asDataName.c_str(), msName.c_str());
		return false;
	}

	return LoadEntityData(pImageData, alAnimNum);
}

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

bool cImageEntity::LoadEntityData(cImageEntityData *apData, int alAnimNum) {
	mpEntityData = apData;

	if (mpEntityData->GetFrameNum() > 1) {
		mpAnimation = mpEntityData->GetAnimationByHandle(alAnimNum);
		mlFrame = mpAnimation->mvFrameNums[0];
		mlLastFrame = -1;
	} else {
		mlFrame = 0;
	}

	mvBaseVtx = mpEntityData->GetImageFrame(mlFrame)->mvVtx;

	if (mvSize.x < 0 && mvSize.y < 0) {
		mvSize = mpEntityData->GetImageSize();
	}

	mvVtx = mvBaseVtx;
	mvIdxVec = *mpEntityData->GetIndexVec();
	mbRotationHasChanged = true;
	mbSizeHasChanged = true;
	mfCurrentAngle = 0;

	return true;
}

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

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

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

bool cImageEntity::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 cImageEntity::SetAlpha(float afX) {
	if (mfAlpha != afX) {
		mfAlpha = afX;

		for (int i = 0; i < (int)mvVtx.size(); i++) {
			mvVtx[i].col.a = mfAlpha;
		}
	}
}

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

void cImageEntity::Flash(float afAdd) {
	mbFlashing = true;
	mfFlashAdd = -ABS(afAdd);
}

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