
#ifdef ASURA_USE_PRAGMA_ONCE
    #pragma once
#endif

#ifndef __TOOLS_TEXTURETILER_ALGORITHM_H_
#define __TOOLS_TEXTURETILER_ALGORITHM_H_

#include "Core/Asura.h"

#include "Core/Asura_Maths.h"

#include "TextureTiler/Tools_TextureTiler_Export.h"
// #include "TextureTiler/Tools_TextureTiler_Render.h"
	
class Asura_TiledTextureInfo;

enum TOOLS_TT_ALGORITHM_TYPE
{
	TOOLS_TT_ALGORITHM_CLASSIC,
	TOOLS_TT_ALGORITHM_FIRSTAVAILABLESPACE,
	TOOLS_TT_ALGORITHM_RECTTREE,
	TOOLS_TT_NUM_ALGORITHM,
};

template< typename T > class Tools_TT_Vector_2
{
public:
	Tools_TT_Vector_2()
	:m_X(0)
	,m_Y(0)
	{}

	Tools_TT_Vector_2( const T& X,const T& Y )
	:m_X(X)
	,m_Y(Y)
	{}

	virtual ~Tools_TT_Vector_2()
	{}

	void Set( const T X,const T Y );

	const T& x() const { return m_X; }
	const T& y() const { return m_Y; }

	T& x() { return m_X; }
	T& y() { return m_Y; }

private:
	T m_X;
	T m_Y;
};

template< typename T > void Tools_TT_Vector_2<T>::Set( const T X,const T Y )
{
	x() = X;
	y() = Y;
}

typedef Tools_TT_Vector_2<int>   Tools_TT_Vector_2i;
typedef Tools_TT_Vector_2<u_int> Tools_TT_Vector_2u;
typedef Tools_TT_Vector_2<float> Tools_TT_Vector_2f;

template< typename T > class Tools_TT_Rectangle_2 : public Tools_TT_Vector_2< Tools_TT_Vector_2<T> >
{
	typedef Tools_TT_Vector_2< Tools_TT_Vector_2<T> > PARENT;
	typedef Tools_TT_Vector_2<T>					  VECTOR;

public:
	Tools_TT_Rectangle_2()
	:PARENT( VECTOR(0,0),VECTOR(0,0) )
	{}

	Tools_TT_Rectangle_2( const VECTOR& min, const VECTOR& max )
	:PARENT(min,max)
	{}
	
	Tools_TT_Rectangle_2( const T& minX,const T& minY, const T& maxX,const T& maxY )
	:PARENT( VECTOR(minX,minY),VECTOR(maxX,maxY) )
	{}

	const VECTOR& Min() const { return x(); }
	const VECTOR& Max() const { return y(); }

	VECTOR& Min() { return x(); }
	VECTOR& Max() { return y(); }

	const T GetWidth() const  { return Max().x() - Min().x(); }
	const T GetHeight() const { return Max().y() - Min().y(); }

	const bool Contains( const VECTOR& point ) const;
};

template< typename T > const bool Tools_TT_Rectangle_2<T>::Contains( const VECTOR& point ) const
{
	return ( point.x() >= Min().x() ) && ( point.y() >= Min().y() ) && ( point.x() <= Max().x() ) && ( point.y() <= Max().y() );
}

typedef Tools_TT_Rectangle_2<int> Tools_TT_Rectangle_2i;
typedef Tools_TT_Rectangle_2<u_int> Tools_TT_Rectangle_2u;
typedef Tools_TT_Rectangle_2<float> Tools_TT_Rectangle_2f;

class Tools_TT_Tile
{
public:
	Tools_TT_Tile( const int iTextureID = ASURA_TEXTURE_NONE );
	Tools_TT_Tile( const Tools_TT_Tile& xTile );
	Tools_TT_Tile( const Asura_TiledTextureInfo& xTileInfo, const int iTextureID );
	
	const bool SetTexture( const int iTextureID );

	// render this tile at its UV coordinates within the screen coordinate rectangle provided
	void Render( const Asura_Bounding_Rectangle& xRectangle,const int iBackgroundColour,const ASURA_TRANSLUCENCY_MODE eTranslucency ) const;

	// recalculate the uv coordinates of this tile within the parent tiled texture
	void UpdateUV( const int iTileToWidth, const int iTileToHeight );

	const Tools_TT_Vector_2i& GetPosition() const { return m_xPosition; } // Get location of top left corner
	void SetPosition( const Tools_TT_Vector_2i& xPosition ) { m_xPosition = xPosition; } // Set location of top left corner

	void SetWidth( const int iWidth ) { m_xImageDimensions.x() = iWidth; }
	void SetHeight( const int iHeight ) { m_xImageDimensions.y() = iHeight; }

	const int GetWidth() const{ return m_xImageDimensions.x(); }
	const int GetHeight() const{ return m_xImageDimensions.y(); }

	const Asura_Hash_ID GetHash() const { return GetName().GetHash(); }

	// returns the uv rectangle occupied by this tile in the parent TTL
	const Asura_Bounding_Rectangle& GetUVRectangle() const {return m_xUVRectangle;}
	
	const Asura_String& GetName() const { return m_xTextureName; }
	const int GetTextureID() const{return m_iTextureID;}	
	
	void SetTiled( const bool bTiled ){m_bTiled = bTiled;}
	const bool IsTiled() const{return m_bTiled;}

	// return the uv bounds of the image within its texture 
	// (useful when image dimensions are not a power of 2)
	const Asura_Bounding_Rectangle& GetImageUV() const {return m_xImageUV;}

	bool IsValid() const { return m_bValid; }
private:
	Asura_String m_xTextureName;
	
	Asura_Bounding_Rectangle m_xUVRectangle;

	// the fraction of the texture that should be included in the tile
	// some textures are bigger than the image file that they are loaded 
	// from because Asura prefers texture dimensions that are n^2
	Asura_Bounding_Rectangle m_xImageUV; 

	Tools_TT_Vector_2i m_xPosition;
	Tools_TT_Vector_2i m_xImageDimensions;
	Tools_TT_Vector_2i m_xTextureDimensions;

	int m_iTextureID;

	bool m_bTiled;
	bool m_bValid;
};

//Node for RectTree tiling
class Tools_TT_RectTreeNode
{
public :
	Tools_TT_RectTreeNode(const Tools_TT_Rectangle_2i& xRect);
	~Tools_TT_RectTreeNode();

	const Tools_TT_RectTreeNode* Insert(Tools_TT_Rectangle_2i& xRect);

	const Tools_TT_Rectangle_2i& GetRect() const {return m_xRect;}

private :
	Tools_TT_RectTreeNode* m_apxChild[2];
	Tools_TT_Rectangle_2i  m_xRect;
	bool m_bFilled;

};

class IsTiled
{
public:
	template< typename T > const bool operator()( const T& xArgument ) const
	{
		return xArgument.IsTiled();
	}
};

// Functor describing sort operation on Tools_TT_Tile class above
class OrderForTiling
{
	public:
		const bool operator()(const Tools_TT_Tile &lhs, const Tools_TT_Tile &rhs) const
		{

			const bool bWidthsEquate( lhs.GetWidth() == rhs.GetWidth() );

			return (lhs.GetWidth() > rhs.GetWidth()) || 
				   (bWidthsEquate && (lhs.GetHeight() > rhs.GetHeight()) ||
				   (bWidthsEquate && (lhs.GetHeight() == rhs.GetHeight()) && (lhs.GetName() > rhs.GetName())) ); // ensure consistent tiling during the same session 
		}
};

template<typename T, typename Functor> void Tools_TT_Sort( T* const pxBegin,T* const pxEnd, const Functor& xOrder )
{
	T xTmp;
	
	T* pxSortedItr(pxBegin);
	T* pxMoveItr(pxBegin);

	for( T* pxItr(pxBegin);pxItr!=pxEnd;++pxItr )
	{
		xTmp = *pxItr;
		
		// find first sorted item that is not less than pxItr
		for( pxSortedItr = pxBegin;(pxSortedItr!=pxItr) && xOrder(*pxSortedItr,*pxItr);++pxSortedItr );
		
		// move all items between pxSortedItr and pxItr
		for( pxMoveItr = pxItr; pxMoveItr!=pxSortedItr;--pxMoveItr )
		{
			*pxMoveItr = *(pxMoveItr - 1);
		}

		// insert pxItr in the loctaion occupied by pxSortedItr
		*pxSortedItr = xTmp;
	}
}

inline const bool Tools_TT_PointInRectangle( const Tools_TT_Vector_2i& xPoint, const Tools_TT_Rectangle_2i& xRect)
{
	return xRect.Contains( xPoint );
}


template< typename T > inline const bool Tools_TT_PointInRectangle( const T& X, const T& Y, const Asura_Bounding_Rectangle& xRect)
{
	return xRect.Contains( Asura_Vector_2( static_cast<float>(X),static_cast<float>(Y) ) );
}

template< typename Ta,typename Tb > 
inline const bool Tools_TT_PointInRectangle( const Ta& X, const Ta& Y, 
											 const Tb& rectMinX, const Tb& rectMinY,
											 const Tb& rectMaxX, const Tb& rectMaxY )
{
	return ( (X>=rectMinX) && (X<=rectMaxX) &&
		     (Y>=rectMinY) && (Y<=rectMaxY) );
}

// calls requested tiling algorithm (final arg) and returns the number of bitmaps 
// which were NOT tiled (usually beacause they would not fit within the specified 
// space). The first arg is an array of TextureInfo's to be tiled, the second the 
// size of that array whilst the third arg gives the dimensions of the required 
// tiled texture list.
const u_int Tools_TT_DoTiling( Tools_TT_Tile* axTools_TT_Tile, const u_int uTools_TT_TileSize, const Tools_TT_Vector_2i& xTextureDimensions, const TOOLS_TT_ALGORITHM_TYPE eAlgorithmType );
const u_int Tools_TT_DoTiling_Classic( Tools_TT_Tile* axTools_TT_Tile, const u_int uTools_TT_TileSize, const Tools_TT_Vector_2i& xTextureDimensions );
const u_int Tools_TT_DoTiling_FirstAvailableSpace( Tools_TT_Tile* axTools_TT_Tile, const u_int uTools_TT_TileSize, const Tools_TT_Vector_2i& xTextureDimensions );
const u_int Tools_TT_DoTiling_RectTree(Tools_TT_Tile* axTools_TT_Tile, const u_int uTools_TT_TileSize, const Tools_TT_Vector_2i& xTextureDimensions );

const bool Tools_TT_TextureFits( const Tools_TT_Rectangle_2i& xAreaFitInto, const Tools_TT_Tile& xTools_TT_Tile);
const float Tools_TT_AssessTextureFitness( const Tools_TT_Rectangle_2i& xAreaFitInto, const Tools_TT_Tile& xTools_TT_Tile);
const bool Tools_TT_TextureInRegion( const Tools_TT_Tile* const axTools_TT_Tile, const u_int uTools_TT_TileSize, const Tools_TT_Rectangle_2i& xRegion);

const bool Tools_TT_FindLargestFreeRectangle( const Tools_TT_Tile* const axTools_TT_Tile, const u_int uTools_TT_TileSize, Tools_TT_Rectangle_2i& xRegion, const Tools_TT_Vector_2i& xTextureDimensions );

#endif // __TOOLS_TEXTURETILER_ALGORITHM_H_