/*
  File name: GameObjectsManager.cpp
  Copyright: (C) 2007 - 2009 Tomasz Kazmierczak

  Creation date: 23.04.2007
  Last modification date: 13.06.2009

  This file is part of Cytadela

 *   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/>.  *

*/

#include "GameObjectsManager.h"
#include "Enemy.h"
#include <cassert>

GameObjectsManager::~GameObjectsManager()
{
	while(not mGameObjects.empty()) {
		if(mGameObjects.begin()->second)
			delete mGameObjects.begin()->second;
		mGameObjects.erase(mGameObjects.begin());
	}
	if(mMapData != 0)
		delete[] mMapData;
}

GameObject *GameObjectsManager::createGameObject(GameObjectType type, Mesh3Dg *mesh)
{
	assert(mesh != 0);

	GameObject *object;
	switch(type) {
		case GOTYPE_WALL:
			object = new GOWall(mesh);
			break;
		case GOTYPE_DOOR:
			object = new GODoor(mesh);
			break;
		case GOTYPE_SPRITE:
			object = new GOSprite(mesh);
			break;
		case GOTYPE_ENEMY:
			object = new Enemy(mesh);
			break;
		default:
			 return 0;
	}
	if(object == 0)
		return 0;
	if(not addGameObject(object)) {
		delete object;
		return 0;
	}
	return object;
}

TemporaryObject *GameObjectsManager::createTempGameObject(Mesh3Dg *mesh, uint32_t time)
{
	assert(mesh != 0);

	TemporaryObject *object = new TemporaryObject(mesh, time);
	if(object == 0)
		return 0;

	if(not addGameObject((GameObject *)object)) {
		delete object;
		return 0;
	}
	return object;
}

TemporaryObject *GameObjectsManager::createTempGameObject(Mesh3Dg *mesh, float range, Vector3Dg direction)
{
	assert(mesh != 0);

	TemporaryObject *object = new TemporaryObject(mesh, range, direction);
	if(object == 0)
		return 0;

	if(not addGameObject((GameObject *)object)) {
		delete object;
		return 0;
	}
	return object;
}

bool GameObjectsManager::destroyGameObject(GameObject *object)
{
	assert(object != 0);

	if(not remGameObject(object))
		return false;

	delete object;
	return true;
}

bool GameObjectsManager::addGameObject(GameObject *object)
{
	//first check if there is already an object with this uid
	GameObjectsMap::iterator i = mGameObjects.find(object->getMesh()->getUID());
	//if there is
	if(i != mGameObjects.end())
		return false;

	mGameObjects[object->getMesh()->getUID()] = object;
	return true;
}

bool GameObjectsManager::remGameObject(GameObject *object)
{
	//first check if there is such object
	GameObjectsMap::iterator i = mGameObjects.find(object->getMesh()->getUID());
	//if there isn't
	if(i == mGameObjects.end())
		return false;

	mGameObjects.erase(i);
	return true;
}

MapDataPtr GameObjectsManager::allocMapData(uint32_t size)
{
	if(mMapData != 0)
		delete[] mMapData;

	mMapDataSize = size;
	mMapData = new MapDataType[size];
	return mMapData;
}

GameObject *GameObjectsManager::findGameObject(Mesh3DgUID muid)
{
	GameObjectsMap::iterator i = mGameObjects.find(muid);
	if(i != mGameObjects.end())
		return mGameObjects[muid];
	else return 0;
}

GameObject *GameObjectsManager::getGameObject(Mesh3DgUID muid)
{
#if defined(DEBUG)
	//first check if there is an object with this uid
	assert(findGameObject(muid) != 0);
#endif

	return mGameObjects[muid];
}

/*
 * GameObject
 */
GameObject::GameObject(GameObjectType type, Mesh3Dg *mesh) :
	mType(type),
	mPlayerCollision(PCTYPE_NONE),
	mAmmoCollision(ACTYPE_NONE)
{
	setParent((Object3Dg *)mesh);
	mesh->setChild((Object3Dg *)this);
}

/*
 * GOWall
 */
GOWall::GOWall(Mesh3Dg *mesh) :
	GameObject(GOTYPE_WALL, mesh),
	mSlotMesh(0),
	mBasicSlotMaterial(0),
	mFlags(0)
{
	memset(mBloodMaterials, 0, sizeof(mBloodMaterials));
}

void GOWall::setBloodMaterialNumbers(uint32_t *numbers)
{
	for(int i = 0; i < 3; i++)
		mBloodMaterials[i] = numbers[i];
}

void GOWall::setBlood(uint8_t type)
{
	assert(type < 3);
	if(mBloodMaterials[type] == 0)
		return;
	getMesh()->materialNumber = mBloodMaterials[type];
	if(mSlotMesh != 0)
		mSlotMesh->materialNumber =  mBasicSlotMaterial + 91 + (type*3);
}

/*
 * GOSprite
 */
GOSprite::GOSprite(Mesh3Dg *mesh) :
	GameObject(GOTYPE_SPRITE, mesh),
	mSubType(GOTYPE_SPRITE),
	mHP(0)
{
}

GOSprite::GOSprite(Mesh3Dg *mesh, GameObjectType subType) :
	GameObject(GOTYPE_SPRITE, mesh),
	mSubType(subType),
	mHP(0)
{
}

/*
 * TemporaryObject
 */
TemporaryObject::TemporaryObject(Mesh3Dg *mesh, uint32_t time) :
	GOSprite(mesh),
	mLifeTime(time),
	mTimeCounter(0),
	mAnimStartDelay(0),
	mVelocityDir(0.0f, 0.0f, 0.0f),
	mRange(0.0f),
	mDistaince(0.0f),
	mUserData(0)
{
	assert(mesh != 0);
	mOrigin = mesh->geomCenter();
}

TemporaryObject::TemporaryObject(Mesh3Dg *mesh, float range, Vector3Dg direction) :
	GOSprite(mesh),
	mLifeTime(0),
	mTimeCounter(0),
	mAnimStartDelay(0),
	mVelocityDir(direction),
	mRange(range),
	mDistaince(0.0f),
	mUserData(0)
{
	assert(mesh != 0);
	mOrigin = mesh->geomCenter();
}

bool TemporaryObject::update(uint32_t time)
{
	mTimeCounter += time;
	if(mTimeCounter <= mAnimStartDelay)
		return true;
	else {
		getMesh()->setProperty(MRP_RENDERABLE, true);
		mTimeCounter -= mAnimStartDelay;
		mAnimStartDelay = 0;
	}

	Mesh3Dg *mesh = getMesh();
	MeshAnim3Dg *animation = &mesh->animation;

	if(animation->texAnimTime) {
		//set next material
		mesh->materialNumber = animation->beginMtl +
			(mTimeCounter * animation->numMtls) / animation->texAnimTime;
	}

	if(mTimeCounter < mLifeTime)
		return true;
	else return false;
}

bool TemporaryObject::update(uint32_t time, float distaince)
{
	mDistaince += distaince;
	mTimeCounter += time;
	if(mTimeCounter <= mAnimStartDelay)
		return true;
	else {
		getMesh()->setProperty(MRP_RENDERABLE, true);
		mTimeCounter -= mAnimStartDelay;
		mAnimStartDelay = 0;
	}

	Mesh3Dg *mesh = getMesh();
	MeshAnim3Dg *animation = &mesh->animation;

	if(animation->texAnimTime) {
		//if texture display time went out of range then substract
		//as long as it gets back into the range
		while(mTimeCounter >= animation->texAnimTime)
			mTimeCounter -= animation->texAnimTime;
		//set next material
		mesh->materialNumber = animation->beginMtl +
			(mTimeCounter * animation->numMtls) / animation->texAnimTime;
	}

	if(mDistaince < mRange)
		return true;
	else return false;
}
