/* 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/haptic/LowLevelHaptic.h"

#include "hpl1/engine/haptic/HapticForce.h"
#include "hpl1/engine/haptic/HapticShape.h"
#include "hpl1/engine/haptic/HapticSurface.h"

#include "hpl1/engine/system/low_level_system.h"
#include "hpl1/engine/system/SystemTypes.h"

#include "hpl1/engine/math/Math.h"
#include "hpl1/engine/physics/PhysicsBody.h"
#include "hpl1/engine/scene/Camera3D.h"
#include "hpl1/engine/scene/SubMeshEntity.h"

namespace hpl {

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

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

iLowLevelHaptic::iLowLevelHaptic() {
	mpCamera = NULL;
	mvCameraOffset = 0;

	mvMinMousePos = cVector2f(-50, -50);
	mvMaxMousePos = cVector2f(50, 50);
	mvScreenSize = cVector2f(800, 600);

	mbUpdateShapes = true;
}

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

iLowLevelHaptic::~iLowLevelHaptic() {
}

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

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

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

iHapticSurface *iLowLevelHaptic::GetSurfaceFromName(const tString &asName) {
	tHapticSurfaceMapIt it = m_mapSurfaces.find(asName);
	if (it == m_mapSurfaces.end()) {
		return NULL;
	}

	iHapticSurface *pSurface = it->second;

	return pSurface;
}

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

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

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

bool iLowLevelHaptic::Init(cResources *apResources) {
	mpResources = apResources;

	if (InitLowLevel() == false)
		return false;

	/////////////////////////////////////
	// Init variables
	mvCurrentHardwarePos = GetHardwarePosition();
	mvPreviousHardwarePos = mvCurrentHardwarePos;
	mbScreenPosFirstTime = true;

	return true;
}

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

void iLowLevelHaptic::Update(float afTimeStep) {
	UpdateLowLevel(afTimeStep);

	/////////////////////////////////////
	// Hardware position calculations
	static bool bHardwareSet = false;

	mvPreviousHardwarePos = mvCurrentHardwarePos;
	mvCurrentHardwarePos = GetHardwarePosition();
	if (bHardwareSet == false) {
		mvPreviousHardwarePos = mvCurrentHardwarePos;
		bHardwareSet = true;
	}

	/////////////////////////////////////
	// Proxy screen postion calculations
	if (mpCamera) {
		mvPreviousScreenPos = mvCurrentScreenPos;

		cVector3f vProjPos = cMath::MatrixMul(mpCamera->GetViewMatrix(), GetProxyPosition());
		vProjPos = cMath::MatrixMulDivideW(mpCamera->GetProjectionMatrix(), vProjPos);

		cVector2f vPos2D((vProjPos.x + 1) * 0.5f, (-vProjPos.y + 1) * 0.5f);

		mvCurrentScreenPos = vPos2D;

		if (mbScreenPosFirstTime) {
			mvPreviousScreenPos = mvCurrentScreenPos;
			mbScreenPosFirstTime = false;
		}
	}

	/////////////////////////////////////
	// Update shape transforms
	if (mbUpdateShapes) {
		tHapticShapeListIt it = mlstShapes.begin();
		for (; it != mlstShapes.end(); ++it) {
			iHapticShape *pShape = *it;

			// If a body is attached, use it to update shape
			if (pShape->GetBody()) {
				iPhysicsBody *pBody = pShape->GetBody();
				if (pBody->GetTransformUpdateCount() != pShape->GetTransformCount()) {
					pShape->SetTransformCount(pBody->GetTransformUpdateCount());
					pShape->SetTransform(pBody->GetLocalMatrix());
				}
			} else if (pShape->GetSubMeshEntity()) {
				cSubMeshEntity *pSubEntity = pShape->GetSubMeshEntity();
				if (pSubEntity->GetTransformUpdateCount() != pShape->GetTransformCount()) {
					pShape->SetTransformCount(pSubEntity->GetTransformUpdateCount());
					pShape->SetTransform(pSubEntity->GetWorldMatrix());
				}
			}
		}
	}
}

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

cVector3f iLowLevelHaptic::GetHardwarePosDelta() {
	return mvCurrentHardwarePos - mvPreviousHardwarePos;
}

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

cVector2f iLowLevelHaptic::GetProxyScreenPos(const cVector2f &avScreenSize) {
	return mvCurrentScreenPos * avScreenSize;
}

cVector2f iLowLevelHaptic::GetProxyScreenDeltaPos(const cVector2f &avScreenSize) {
	return (mvCurrentScreenPos - mvPreviousScreenPos) * avScreenSize;
}

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

cVector2f iLowLevelHaptic::GetRelativeVirtualMousePos() {
	cVector3f vRel = GetHardwarePosDelta();
	cVector2f vPos = cVector2f(vRel.x, vRel.y) / (mvMaxMousePos - mvMinMousePos);
	vPos.y = -vPos.y;
	vPos = vPos * mvScreenSize;
	return vPos;
}

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

cVector2f iLowLevelHaptic::GetVirtualMousePos() {
	// Log("Pos: %f %f\n", mvCurrentHardwarePos.x,mvCurrentHardwarePos.y);
	cVector2f vPos = (cVector2f(mvCurrentHardwarePos.x, mvCurrentHardwarePos.y) - mvMinMousePos) /
					 (mvMaxMousePos - mvMinMousePos);
	vPos.y = 1 - vPos.y;
	vPos = vPos * mvScreenSize;
	return vPos;
}
void iLowLevelHaptic::SetVirtualMousePosBounds(const cVector2f &avMin, const cVector2f &avMax,
											   const cVector2f &avScreenSize) {
	mvMinMousePos = avMin;
	mvMaxMousePos = avMax;
	mvScreenSize = avScreenSize;
}

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

void iLowLevelHaptic::DestroyShape(iHapticShape *apShape) {
	STLFindAndDelete(mlstShapes, apShape);
}

bool iLowLevelHaptic::ShapeExists(iHapticShape *apShape) {
	tHapticShapeListIt it = mlstShapes.begin();
	for (; it != mlstShapes.end(); ++it) {
		if (apShape == *it)
			return true;
	}
	return false;
}

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

cHapticShapeIterator iLowLevelHaptic::GetShapeIterator() {
	return cHapticShapeIterator(&mlstShapes);
}

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

void iLowLevelHaptic::DestroyForce(iHapticForce *apForce) {
	STLFindAndDelete(mlstForces, apForce);
}

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

void iLowLevelHaptic::StopAllForces() {
	tHapticForceListIt it = mlstForces.begin();
	for (; it != mlstForces.end(); ++it) {
		iHapticForce *pForce = *it;
		pForce->SetActive(false);
	}
}

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

void iLowLevelHaptic::DestroyAllShapes() {
	STLDeleteAll(mlstShapes);

	// Log("Destroying all shapes!\n");
}

void iLowLevelHaptic::DestroyAll() {
	STLMapDeleteAll(m_mapSurfaces);
	STLDeleteAll(mlstForces);
	STLDeleteAll(mlstShapes);
}

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

} // namespace hpl
