#ifndef SHAPE_H
#define SHAPE_H

#include "object.h"
#include "material.h"
#include <kLibrary\factory\factory.h>
#include "texture.h"
#include "light.h"

#include "mesh.h"

#include "xLoader.h"

#include <list>
#include <hash_map>

#include <boundingbox.h>
#include <octree.h>

namespace CBK
{
	namespace Shapes
	{
		class Sprite;
	}

	class Shape : public Object
	{
	public:
		//Create a new shape
		Shape(Mesh* mesh, IDirect3DDevice9* device = 0, cbk::Octree<Shape*>* octree = 0);
		Shape(IDirect3DDevice9* device = 0, cbk::Octree<Shape*>* octree = 0);

		//Clean up
		virtual ~Shape(void);

		//Draws the object.
		virtual Shape& update(const float& dt);
		virtual void apply(void);
		virtual void draw(const DWORD& subset = 0);
		Shape& drawBoundingBox(void);
		virtual float isCollide(const Shape& other, const float& dt) const;
		virtual float isCollideXZ(const Shape& other, const float& dt) const;
		const size_t getSubsetCount(void) const;

		//Allow the object to be scaled, transformed, and/or rotated
		void updateTransform(void) const;

		Shape& setPosition(const FLOAT& x, const FLOAT& y, const FLOAT& z);
		Shape& setPosition(const D3DXVECTOR3& position);
		Shape& move(const FLOAT& x, const FLOAT& y, const FLOAT& z);
		Shape& move(const D3DXVECTOR3& offset);

		Shape& setRotation(const D3DXMATRIX& rotation);
		Shape& setRotation(const D3DXVECTOR3& rotation);
		Shape& setRotation(const FLOAT& Yaw, const FLOAT& Pitch, const FLOAT& Roll);
		Shape& rotate(const D3DXMATRIX& rotation);
		Shape& rotate(const D3DXVECTOR3& rotation);
		Shape& rotate(const FLOAT& Yaw, const FLOAT& Pitch, const FLOAT& Roll);

		Shape& scale(const FLOAT& x, const FLOAT& y, const FLOAT& z);
		Shape& scale(const D3DXVECTOR3& scale);
		Shape& adjustScale(const FLOAT& x, const FLOAT& y, const FLOAT& z);
		Shape& adjustScale(const D3DXVECTOR3& difference);

		//Get the transforms
		const D3DXMATRIX& getTransform(void) const;
		const D3DXMATRIX& getRotation(void) const;
		const D3DXVECTOR3& getScale(void) const;
		const D3DXVECTOR3& getTranslation(void) const;

		const D3DXVECTOR3& getRealPosition(void) const;
		const D3DXVECTOR3& getVelocity(void) const;
		const D3DXVECTOR3& getAcceleration(void) const;

		Shape& moveTo(const FLOAT& x, const FLOAT& y, const FLOAT& z);
		Shape& moveTo(const D3DXVECTOR3& position);
		Shape& stopMoveTo(void);
		const D3DXVECTOR3* const getMovingTo(void) const;

		Shape& setVelocity(const FLOAT& x, const FLOAT& y, const FLOAT& z);
		Shape& setVelocity(const D3DXVECTOR3& velocity);
		Shape& accelerate(const FLOAT& x, const FLOAT& y, const FLOAT& z);
		Shape& accelerate(const D3DXVECTOR3& acceleration);
		Shape& setMaxVelocity(const float& maxVelocity);
		const float& getMaxVelocity(void) const;

		Shape& setAcceleration(const FLOAT& x, const FLOAT& y, const FLOAT& z);
		Shape& setAcceleration(const D3DXVECTOR3& acceleration);
		Shape& modifyAcceleration(const FLOAT& x, const FLOAT& y, const FLOAT& z);
		Shape& modifyAcceleration(const D3DXVECTOR3& deltaAcceleration);
		Shape& setMaxAcceleration(const float& maxAcceleration);
		const float& getMaxAcceleration(void) const;

		Shape& calcBoundaries(void);
		const float getCollideSphereRadius(void) const;
		const float& getMaxScale(void) const;
		const cbk::BoundingBox* const getBoundingBox(void) const;

		const FLOAT getSpeed(void) const;
		const D3DXVECTOR3 getDirection(void) const;
		const FLOAT getSpeedXZ(void) const;
		const D3DXVECTOR2 getDirectionXZ(void) const;

		const FLOAT getAccelSpeed(void) const;
		const D3DXVECTOR3 getAccelDirection(void) const;
		const FLOAT getAccelSpeedXZ(void) const;
		const D3DXVECTOR2 getAccelDirectionXZ(void) const;

		Mesh* getMesh(void) const;

		//Apply the transforms to the device
		void applyTransform(void) const;

		//Set the parent
		Shape& setParent(Shape* parent);

		//Get the parent
		Shape* getParent(void) const;

		//Gets the distance between the specified object and this object
		const float getDistance(const Shape* other) const;
		const float getDistanceXZ(const Shape* other) const;

		//Store child information
		Shape& addChild(Shape* child);
		Shape& removeChild(Shape* child);
		std::list<Shape*>::iterator getFirstChild(void);
		std::list<Shape*>::iterator getChildEnd(void);

		//Allow material setting
		virtual Shape& setMaterialLoader(kLibrary::Factory<Material, IDirect3DDevice9*>* materialLoader);
		virtual kLibrary::Factory<Material, IDirect3DDevice9*>* getMaterialLoader(void) const;
		virtual Shape& setMaterial(const std::string& name, const size_t& subset = 0);
		virtual const Material* getMaterial(const size_t& subset = 0) const;

		//Allow texture setting
		virtual Shape& setTextureLoader(kLibrary::Factory<Texture, IDirect3DDevice9*>* textureLoader);
		virtual kLibrary::Factory<Texture, IDirect3DDevice9*>* getTextureLoader(void) const;
		virtual Shape& setTexture(const std::string& name, const size_t& subset = 0);
		virtual const Texture* getTexture(const size_t& subset = 0) const;

		//Allow Mesh loading
		virtual Object& load(const std::string& name);
		Shape& setMesh(Mesh* mesh);
		Shape& setMeshLoader(kLibrary::Factory<Shapes::xLoader, const MeshParam&>* meshLoader);

		//Light Linking
		Shape& addLight(Light* light);
		Shape& removeLight(Light* light);
		bool isLightLinked(Light* light);
		const Shape& applyLighting(void) const;

		//Terrain Information
		Shape& setCurrentTerrain(Shape* terrain);
		Shape& setTerrainYOffset(const float& yOffset);
		const float getTerrainYOffset(void) const;

		Shape& removeObject(Shape* object);

		virtual Object& reset(void);

		static const DWORD& getMaxNumLights(void);

		cbk::Octree<Shape*>::oNode* getOctreeNode(void) const;
		Shape& setOctreeNode(cbk::Octree<Shape*>::oNode* octreenode);

		cbk::Octree<Shape*>* getOctree(void) const;
		Shape& setOctree(cbk::Octree<Shape*>* octree);
		
	protected:

	private:
		//The mesh data
		Mesh* _mesh;

		//The current terrain object
		Shape* _terrain;

		//The selection circle
		Shapes::Sprite* _selCircle;

		//The lights to apply to the object
		std::list<Light*> _lightsLinked;
		static DWORD _maxNumLights;

		//Factories
		kLibrary::Factory<Material, IDirect3DDevice9*>* _materialLoader;
		kLibrary::Factory<Texture, IDirect3DDevice9*>* _textureLoader;
		kLibrary::Factory<Shapes::xLoader, const MeshParam&>* _meshLoader;

		//The complete transform matrix
		mutable D3DXVECTOR3 _realPosition;
		mutable D3DXMATRIX _completeTransform;	//Our complete transform
		mutable D3DXMATRIX _parentTransform;		//Our parent's transform
		mutable D3DXMATRIX _transform;			//Our local transform

		//Does the transform matrix need to be updated?
		mutable bool _needTransformUpdate;

		cbk::Octree<Shape*>::oNode *_octreeNode;
		cbk::Octree<Shape*> *_octree;

		//The parts of the transform matrix
		D3DXMATRIX _matrixRotation;
		D3DXVECTOR3 _vectScale;
		D3DXVECTOR3 _vectTranslation;

		D3DXVECTOR3 _vectVelocity;
		D3DXVECTOR3 _vectAcceleration;
		float _maxVelocity, _maxAcceleration;

		mutable D3DXVECTOR3 _tAxisXWorld, _tAxisYWorld, _tAxisZWorld;
		mutable D3DXVECTOR3 _tAxisXLocal, _tAxisYLocal, _tAxisZLocal;

		D3DXVECTOR3 _vectMoveTo;
		bool _bMovingTo;
		
		//A list of objects sorted by distance
		struct _ShapeDistance
		{
			float distance;
			Shape* shape;
			_ShapeDistance(float distance = 0.0f, Shape* shape = 0)
			{
				this->distance = distance;
				this->shape = shape;
			}
			bool operator<(const _ShapeDistance& other)
			{
				return (distance < other.distance);
			}
		};

		//The parent of the object (for transforms)
		Shape* _parent;
		std::list<Shape*> _children;

		//No copy
		Shape(const Shape&);
		Shape& operator=(const Shape&);
	};
}

#endif //SHAPE_H