/*
  File name: GameObjectsManager.h
  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/>.  *

*/

#ifndef _GAME_OBJECTS_MANAGER_H_
#define _GAME_OBJECTS_MANAGER_H_

#include <cassert>
#include <memory.h>
#include "I3Dg.h"

typedef enum {
	GOTYPE_WALL,
	GOTYPE_SPRITE,
	GOTYPE_DOOR,
	GOTYPE_ENEMY
} GameObjectType;

typedef enum {
	PCTYPE_NONE,
	PCTYPE_BUMP,
	PCTYPE_STOP,
	PCTYPE_TELEPORT
} PlayerCollisionType;

typedef enum {
	ACTYPE_NONE,
	ACTYPE_UNBREAKABLE,
	ACTYPE_EXPLODABLE,
	ACTYPE_ENEMY
} AmmoCollisionType;

typedef enum {
	DOORTYPE_UNDEFINED,
	DOORTYPE_VERTICAL_1,
	DOORTYPE_VERTICAL_2,
	DOORTYPE_HORIZONTAL,
	DOORTYPE_NUM_OF_TYPES
} DoorType;

class GameObject : public Object3Dg {
protected:
	GameObjectType      mType;
	PlayerCollisionType mPlayerCollision;
	AmmoCollisionType   mAmmoCollision;

public:
	GameObject(GameObjectType type, Mesh3Dg *mesh);

	//set
	void setPlayerCollisionType(PlayerCollisionType type) { mPlayerCollision = type; }
	void setAmmoCollisionType(AmmoCollisionType type) { mAmmoCollision = type; }
	//get
	Mesh3Dg *getMesh() { return (Mesh3Dg *)getParent(); }
	GameObjectType getType() { return mType; }
	PlayerCollisionType getPlayerCollisionType() { return mPlayerCollision; }
	AmmoCollisionType getAmmoCollisionType() { return mAmmoCollision; }
};

class GOWall : public GameObject {
private:
	enum {
		TRANSPARENT_FOR_ENEMY = 1
	};

	Mesh3Dg  *mSlotMesh;
	uint32_t  mSlotActionDataOffset;
	uint32_t  mBloodMaterials[3];
	uint32_t  mBasicSlotMaterial;
	uint8_t   mFlags;
public:
	GOWall(Mesh3Dg *mesh);

	//set
	void setSlotMesh(Mesh3Dg *mesh) { mSlotMesh = mesh; }
	void setSlotActionDataOffset(uint32_t offset) { mSlotActionDataOffset = offset; }
	void setBloodMaterialNumbers(uint32_t *numbers);
	void setBasicSlotMaterial(uint32_t mtl) { mBasicSlotMaterial = mtl; }

	void setBlood(uint8_t type);

	void setAllFlags(uint8_t flags) { mFlags = flags; }
	void setTransparentForEnemy(bool status) { mFlags |= TRANSPARENT_FOR_ENEMY; }

	//get
	Mesh3Dg *getSlotMesh() { return mSlotMesh; }
	uint32_t getSlotActionDataOffset() { return mSlotActionDataOffset; }
	const uint32_t *getBloodMaterialNumbers() { return mBloodMaterials; }
	uint32_t getBasicSlotMaterial() { return mBasicSlotMaterial; }
	bool getTransparentForEnemy() { return mFlags & TRANSPARENT_FOR_ENEMY; }
	uint8_t getAllFlags() { return mFlags; }
};

class GODoor : public GameObject {
private:
	Mesh3Dg *mFrameMesh;
	DoorType mDoorType;
public:
	GODoor(Mesh3Dg *mesh) : GameObject(GOTYPE_DOOR, mesh), mFrameMesh(0) {}

	//set
	void setFrameMesh(Mesh3Dg *mesh) { mFrameMesh = mesh; }
	void setDoorType(DoorType type) { mDoorType = type; }
	//get
	Mesh3Dg *getFrameMesh() { return mFrameMesh; }
	DoorType getDoorType() { return mDoorType; }
};

class GOSprite : public GameObject {
private:
	GameObjectType mSubType;
protected:
	int16_t mHP;
public:
	GOSprite(Mesh3Dg *mesh);
	GOSprite(Mesh3Dg *mesh, GameObjectType subType);

	//set
	void setHP(int16_t hp) { mHP = hp; }
	//get
	GameObjectType getSubType() { return mSubType; }
	int16_t getHP() { return mHP; }
	//other
	int16_t decreaseHP(int16_t points) { mHP -= points; return mHP; }
};

class TemporaryObject : public GOSprite {
protected:
	uint32_t  mLifeTime;
	uint32_t  mTimeCounter;
	uint32_t  mAnimStartDelay;
	Vector3Dg mVelocityDir;
	Vector3Dg mOrigin;
	float     mRange;
	float     mDistaince;
	void     *mUserData;

public:
	TemporaryObject(Mesh3Dg *mesh, uint32_t time);
	TemporaryObject(Mesh3Dg *mesh, float range, Vector3Dg direction);

	bool update(uint32_t time);
	bool update(uint32_t time, float distaince);
	void updateOrigin() { mOrigin = getMesh()->geomCenter(); }

	void setAnimStartDelay(uint32_t delay) { mAnimStartDelay = delay; }
	void setVelocityDirection(Vector3Dg &velocitydir) { mVelocityDir = velocitydir; }
	void setUserData(void *data) { mUserData = data; }

	Vector3Dg *getVelocityDirection() { return &mVelocityDir; }
	uint32_t getLifeTime() { return mLifeTime; }
	float getRange() { return mRange; }
	void *getUserData() { return mUserData; }
	Vector3Dg &getOrigin() { return mOrigin; }
};

typedef std::map<Mesh3DgUID, GameObject *> GameObjectsMap;
typedef Mesh3DgUID MapDataType;
typedef MapDataType *MapDataPtr;

class GameObjectsManager : public I3Dg {
protected:
	GameObjectsMap  mGameObjects;
	uint32_t        mMapDataSize;
	MapDataPtr      mMapData;

	bool addGameObject(GameObject *object);
	bool remGameObject(GameObject *object);

public:
	GameObjectsManager() : mMapDataSize(0), mMapData(0) {}
	~GameObjectsManager();

	/// @brief creates game object of a given type and places it in the objects' map
	/// You should not directly delete objects created this way - use destroyGameObject() instead
	/// If you won't destroy an object created by this function, the destructor will do it for you
	GameObject *createGameObject(GameObjectType type, Mesh3Dg *mesh);
	/// @brief creates a temporary game object
	/// You should not directly delete objects created this way - use destroyGameObject() instead
	/// If you won't destroy an object created by this function, the destructor will do it for you
	TemporaryObject *createTempGameObject(Mesh3Dg *mesh, uint32_t time);
	TemporaryObject *createTempGameObject(Mesh3Dg *mesh, float range, Vector3Dg direction);
	/// @brief removes a given object from the objects' map and deletes it from the memory
	/// With this function you should only destroy objects created with either
	/// createGameObject() or createTempGameObject()
	bool destroyGameObject(GameObject *object);

	MapDataPtr allocMapData(uint32_t size);
	//get
	MapDataPtr getMapDataPtr() { return mMapData; }
	uint32_t getMapDataSize() { return mMapDataSize; }
	/**
	 * @brief Returns a pointer to a game object of a specified UID
	 * This function returns a pointer to a specified Game Object. This is a "fast version"
	 * of findGameObject() function - it does not check wether the UID is valid, so you must
	 * always past an existing UID as a parameter. If you want a function that checks the UID,
	 * use the findGameObject() instead.
	 * @return A pointer to the specified Game Object.
	 * @note You must give an UID that belongs to an existing object!
	 */
	GameObject *getGameObject(Mesh3DgUID muid);
	GameObjectType getObjectType(Mesh3Dg *mesh);

	GameObjectsMap::iterator begin() { return mGameObjects.begin(); }
	GameObjectsMap::iterator end() { return mGameObjects.end(); }
	/**
	 * @brief Finds an object of the specified UID and returns a pointer to it
	 * @return A pointer to an object, if found, and 0 otherwise.
	 */
	GameObject *findGameObject(Mesh3DgUID muid);
};

#endif //_GAME_OBJECTS_MANAGER_H_
