/* 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/graphics/GraphicsDrawer.h"

#include "hpl1/engine/graphics/GfxObject.h"
#include "hpl1/engine/graphics/LowLevelGraphics.h"
#include "hpl1/engine/graphics/Material.h"
#include "hpl1/engine/graphics/MaterialHandler.h"
#include "hpl1/engine/resources/FrameBitmap.h"
#include "hpl1/engine/resources/ResourceImage.h"
#include "hpl1/engine/resources/TextureManager.h"
#include "hpl1/engine/system/low_level_system.h"

#include "hpl1/engine/math/Math.h"

#include "hpl1/engine/resources/ImageManager.h"
#include "hpl1/engine/resources/Resources.h"

namespace hpl {

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

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

cGraphicsDrawer::cGraphicsDrawer(iLowLevelGraphics *apLowLevelGraphics, cMaterialHandler *apMaterialHandler,
								 cResources *apResources) {
	mpLowLevelGraphics = apLowLevelGraphics;
	mpMaterialHandler = apMaterialHandler;
	mpResources = apResources;
}

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

cGraphicsDrawer::~cGraphicsDrawer() {
	ClearBackgrounds();

	STLDeleteAll(mlstGfxObjects);
}

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

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

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

iMaterial *cGfxBufferObject::GetMaterial() const {
	return mpObject->GetMaterial();
}

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

bool cGfxBufferCompare::operator()(const cGfxBufferObject &aObjectA, const cGfxBufferObject &aObjectB) const {
	if (aObjectA.GetZ() != aObjectB.GetZ()) {
		return aObjectA.GetZ() < aObjectB.GetZ();
	} else if (aObjectA.GetMaterial()->GetTexture(eMaterialTexture_Diffuse) !=
			   aObjectB.GetMaterial()->GetTexture(eMaterialTexture_Diffuse)) {
		return aObjectA.GetMaterial()->GetTexture(eMaterialTexture_Diffuse) >
			   aObjectB.GetMaterial()->GetTexture(eMaterialTexture_Diffuse);
	} else if (aObjectA.GetMaterial()->GetType(eMaterialRenderType_Diffuse) !=
			   aObjectB.GetMaterial()->GetType(eMaterialRenderType_Diffuse)) {
		return aObjectA.GetMaterial()->GetType(eMaterialRenderType_Diffuse) >
			   aObjectB.GetMaterial()->GetType(eMaterialRenderType_Diffuse);
	} else {
	}
	return false;
}

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

static void FlushImage(cGfxObject *apObject) {
	if (apObject->IsImage()) {
		cResourceImage *pImage = apObject->GetMaterial()->GetImage(eMaterialTexture_Diffuse);
		pImage->GetFrameBitmap()->FlushToTexture();
	}
}

void cGraphicsDrawer::DrawGfxObject(cGfxObject *apObject, const cVector3f &avPos,
									const cVector2f &avSize, const cColor &aColor,
									bool abFlipH, bool abFlipV, float afAngle) {
	FlushImage(apObject);

	cGfxBufferObject BuffObj;
	BuffObj.mpObject = apObject;
	BuffObj.mvTransform = avPos;
	BuffObj.mvSize = avSize;
	BuffObj.mColor = aColor;
	BuffObj.mbFlipH = abFlipH;
	BuffObj.mbFlipV = abFlipV;
	BuffObj.mfAngle = afAngle;

	BuffObj.mbIsColorAndSize = true;

	m_setGfxBuffer.insert(BuffObj);
}

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

void cGraphicsDrawer::DrawGfxObject(cGfxObject *apObject, const cVector3f &avPos) {
	FlushImage(apObject);

	cGfxBufferObject BuffObj;
	BuffObj.mpObject = apObject;
	BuffObj.mvTransform = avPos;
	BuffObj.mbIsColorAndSize = false;

	m_setGfxBuffer.insert(BuffObj);
}
//-----------------------------------------------------------------------

void cGraphicsDrawer::DrawAll() {
	// Set all states
	mpLowLevelGraphics->SetDepthTestActive(false);
	mpLowLevelGraphics->SetIdentityMatrix(eMatrix_ModelView);

	mpLowLevelGraphics->SetOrthoProjection(mpLowLevelGraphics->GetVirtualSize(), -1000, 1000);

	int lIdxAdd = 0;
	iMaterial *pPrevMat = NULL;
	iMaterial *pMat = NULL;
	const cGfxBufferObject *pObj = NULL;
	tGfxBufferSetIt ObjectIt = m_setGfxBuffer.begin();

	if (ObjectIt != m_setGfxBuffer.end())
		pMat = ObjectIt->GetMaterial();

	while (ObjectIt != m_setGfxBuffer.end()) {
		if (pMat->StartRendering(eMaterialRenderType_Diffuse, NULL, NULL) == false) {
			ObjectIt++;
			if (ObjectIt != m_setGfxBuffer.end())
				pMat = ObjectIt->GetMaterial();

			continue;
		}

		do {
			pObj = &(*ObjectIt);
			if (pObj->mbIsColorAndSize) {
				cVector3f vPos[4];
				float fW = pObj->mvSize.x * 0.5f;
				float fH = pObj->mvSize.y * 0.5f;
				cMatrixf mtxTrans = cMath::MatrixTranslate(pObj->mvTransform + cVector3f(fW, fH, 0));
				vPos[0] = cVector3f(-fW, -fH, 0);
				vPos[1] = cVector3f(fW, -fH, 0);
				vPos[2] = cVector3f(fW, fH, 0);
				vPos[3] = cVector3f(-fW, fH, 0);

				if (pObj->mfAngle != 0) {
					cMatrixf mtxRot = cMath::MatrixRotateZ(pObj->mfAngle);
					vPos[0] = cMath::MatrixMul(mtxRot, vPos[0]);
					vPos[1] = cMath::MatrixMul(mtxRot, vPos[1]);
					vPos[2] = cMath::MatrixMul(mtxRot, vPos[2]);
					vPos[3] = cMath::MatrixMul(mtxRot, vPos[3]);
				}

				vPos[0] = cMath::MatrixMul(mtxTrans, vPos[0]);
				vPos[1] = cMath::MatrixMul(mtxTrans, vPos[1]);
				vPos[2] = cMath::MatrixMul(mtxTrans, vPos[2]);
				vPos[3] = cMath::MatrixMul(mtxTrans, vPos[3]);

				if (pObj->mbFlipH) {
					mpLowLevelGraphics->AddVertexToBatch_Size2D(pObj->mpObject->GetVtxPtr(0),
																&vPos[0],
																&pObj->mColor,
																0, 0);
					mpLowLevelGraphics->AddVertexToBatch_Size2D(pObj->mpObject->GetVtxPtr(1),
																&vPos[1],
																&pObj->mColor,
																0, 0);
					mpLowLevelGraphics->AddVertexToBatch_Size2D(pObj->mpObject->GetVtxPtr(2),
																&vPos[2],
																&pObj->mColor,
																0, 0);
					mpLowLevelGraphics->AddVertexToBatch_Size2D(pObj->mpObject->GetVtxPtr(3),
																&vPos[3],
																&pObj->mColor,
																pObj->mvSize.x, pObj->mvSize.y);
				} else {
					mpLowLevelGraphics->AddVertexToBatch_Size2D(pObj->mpObject->GetVtxPtr(0),
																&vPos[0],
																&pObj->mColor,
																0, 0);
					mpLowLevelGraphics->AddVertexToBatch_Size2D(pObj->mpObject->GetVtxPtr(1),
																&vPos[1],
																&pObj->mColor,
																0, 0);
					mpLowLevelGraphics->AddVertexToBatch_Size2D(pObj->mpObject->GetVtxPtr(2),
																&vPos[2],
																&pObj->mColor,
																0, 0);
					mpLowLevelGraphics->AddVertexToBatch_Size2D(pObj->mpObject->GetVtxPtr(3),
																&vPos[3],
																&pObj->mColor,
																0, 0);
				}

				for (int i = 0; i < 4; i++)
					mpLowLevelGraphics->AddIndexToBatch(lIdxAdd + i);
			} else {
				for (int i = 0; i < (int)pObj->mpObject->GetVertexVec()->size(); i++) {
					mpLowLevelGraphics->AddVertexToBatch(pObj->mpObject->GetVtxPtr(i),
														 &pObj->mvTransform);
					mpLowLevelGraphics->AddIndexToBatch(lIdxAdd + i);
				}
			}
			lIdxAdd += (int)pObj->mpObject->GetVertexVec()->size();

			pPrevMat = pMat;
			ObjectIt++;

			if (ObjectIt == m_setGfxBuffer.end()) {
				pMat = NULL;
				break;
			} else {
				pMat = ObjectIt->GetMaterial();
			}
		} while (pMat->GetType(eMaterialRenderType_Diffuse) ==
					 pPrevMat->GetType(eMaterialRenderType_Diffuse) &&
				 pMat->GetTexture(eMaterialTexture_Diffuse) ==
					 pPrevMat->GetTexture(eMaterialTexture_Diffuse));

		lIdxAdd = 0;

		do {
			mpLowLevelGraphics->FlushQuadBatch(pPrevMat->GetBatchFlags(eMaterialRenderType_Diffuse), false);
		} while (pPrevMat->NextPass(eMaterialRenderType_Diffuse));

		mpLowLevelGraphics->ClearBatch();

		pPrevMat->EndRendering(eMaterialRenderType_Diffuse);
	}

	// Clear the buffer of objects.
	m_setGfxBuffer.clear();

	// Reset all states
	mpLowLevelGraphics->SetDepthTestActive(true);
}

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

cGfxObject *cGraphicsDrawer::CreateGfxObject(const tString &asFileName, const tString &asMaterialName,
											 bool abAddToList) {
	cResourceImage *pImage = mpResources->GetImageManager()->CreateImage(asFileName);
	if (pImage == NULL) {
		error("Couldn't load image '%s'", asFileName.c_str());
		return NULL;
	}

	iMaterial *pMat = mpMaterialHandler->Create(asMaterialName, eMaterialPicture_Image);
	if (pMat == NULL) {
		error("Couldn't create material '%s'", asMaterialName.c_str());
		return NULL;
	}
	// mpResources->GetImageManager()->FlushAll();

	pMat->SetImage(pImage, eMaterialTexture_Diffuse);

	cGfxObject *pObject = hplNew(cGfxObject, (pMat, asFileName, true));

	if (abAddToList)
		mlstGfxObjects.push_back(pObject);

	return pObject;
}

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

cGfxObject *cGraphicsDrawer::CreateGfxObject(Bitmap2D *apBmp, const tString &asMaterialName,
											 bool abAddToList) {
	cResourceImage *pImage = mpResources->GetImageManager()->CreateFromBitmap("", apBmp);
	if (pImage == NULL) {
		error("Couldn't create image");
		return NULL;
	}

	iMaterial *pMat = mpMaterialHandler->Create(asMaterialName, eMaterialPicture_Image);
	if (pMat == NULL) {
		error("Couldn't create material '%s'", asMaterialName.c_str());
		return NULL;
	}
	// mpResources->GetImageManager()->FlushAll();

	pMat->SetImage(pImage, eMaterialTexture_Diffuse);

	cGfxObject *pObject = hplNew(cGfxObject, (pMat, "", true));

	if (abAddToList)
		mlstGfxObjects.push_back(pObject);

	return pObject;
}

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

cGfxObject *cGraphicsDrawer::CreateGfxObjectFromTexture(const tString &asFileName, const tString &asMaterialName,
														bool abAddToList) {
	iTexture *pTex = mpResources->GetTextureManager()->Create2D(asFileName, false);
	if (pTex == NULL) {
		error("Couldn't create texture '%s'", asFileName.c_str());
		return NULL;
	}

	iMaterial *pMat = mpMaterialHandler->Create(asMaterialName, eMaterialPicture_Texture);
	if (pMat == NULL) {
		error("Couldn't create material '%s'", asMaterialName.c_str());
		return NULL;
	}
	// mpResources->GetImageManager()->FlushAll();

	pMat->SetTexture(pTex, eMaterialTexture_Diffuse);

	cGfxObject *pObject = hplNew(cGfxObject, (pMat, asFileName, false));

	if (abAddToList)
		mlstGfxObjects.push_back(pObject);

	return pObject;
}

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

void cGraphicsDrawer::DestroyGfxObject(cGfxObject *apObject) {
	STLFindAndDelete(mlstGfxObjects, apObject);
}

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

cBackgroundImage *cGraphicsDrawer::AddBackgroundImage(const tString &asFileName,
													  const tString &asMaterialName,
													  const cVector3f &avPos,
													  bool abTile, const cVector2f &avSize, const cVector2f &avPosPercent, const cVector2f &avVel) {
	cResourceImage *pImage = mpResources->GetImageManager()->CreateImage(asFileName);
	if (pImage == NULL) {
		error("Couldn't load image '%s'", asFileName.c_str());
		return NULL;
	}

	iMaterial *pMat = mpMaterialHandler->Create(asMaterialName, eMaterialPicture_Image);
	if (pMat == NULL) {
		error("Couldn't create material '%s'", asMaterialName.c_str());
		return NULL;
	}
	// mpResources->GetImageManager()->FlushAll();

	pMat->SetImage(pImage, eMaterialTexture_Diffuse);

	cBackgroundImage *pBG = hplNew(cBackgroundImage, (pMat, avPos, abTile, avSize, avPosPercent, avVel));

	m_mapBackgroundImages.insert(tBackgroundImageMap::value_type(avPos.z, pBG));

	return pBG;
}

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

void cGraphicsDrawer::UpdateBackgrounds() {
	tBackgroundImageMapIt it = m_mapBackgroundImages.begin();
	for (; it != m_mapBackgroundImages.end(); it++) {
		it->second->Update();
	}
}

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

void cGraphicsDrawer::DrawBackgrounds(const cRect2f &aCollideRect) {
	mpLowLevelGraphics->SetIdentityMatrix(eMatrix_ModelView);
	mpLowLevelGraphics->SetDepthTestActive(true);
	mpLowLevelGraphics->SetDepthWriteActive(false);
	mpLowLevelGraphics->SetAlphaTestFunc(eAlphaTestFunc_Greater, 0.1f);
	mpLowLevelGraphics->SetDepthTestFunc(eDepthTestFunc_LessOrEqual);

	tBackgroundImageMapIt it = m_mapBackgroundImages.begin();
	for (; it != m_mapBackgroundImages.end(); it++) {
		it->second->Draw(aCollideRect, mpLowLevelGraphics);
	}

	mpLowLevelGraphics->SetAlphaTestFunc(eAlphaTestFunc_Greater, 0.05f);
	mpLowLevelGraphics->SetDepthWriteActive(true);
}

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

void cGraphicsDrawer::ClearBackgrounds() {
	tBackgroundImageMapIt it = m_mapBackgroundImages.begin();
	for (; it != m_mapBackgroundImages.end(); it++) {
		hplDelete(it->second);
	}
	m_mapBackgroundImages.clear();
}

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

} // namespace hpl
