#include "QuadIndex.h"

#define MAXBUFFERSIZE 65535

namespace Ogre {

	QuadIndex::QuadIndex(int size)
	{
		this->size = size;
		this->depth=(int)((double)(log((double)size))/(double)(log((double)2))+1);
		//	quadTree = new Quad**[depth];
		//	quadLoaded = new bool*[depth];
		vertexTree = new Vertex**[depth];
		vertexLoaded = new bool*[depth];
		//	std::cout << "Depth = " << depth << std::endl;	
		for (int i=0;i<depth;i++){		
			//		quadTree[i] = new Quad*[getLevelSize(i)*getLevelSize(i)];
			//		quadLoaded[i] = new bool[getLevelSize(i)*getLevelSize(i)];		
			vertexTree[i] = new Vertex*[getLevelSize(i)*getLevelSize(i)];		
			vertexLoaded[i] = new bool[getLevelSize(i)*getLevelSize(i)];		
			for (int j=0;j<getLevelSize(i)*getLevelSize(i);j++){		
				vertexLoaded[i][j] = false;
			}
		}
		bufferQueue.clear();
		//	std::cout << "Depth = " << depth << std::endl;	
	}
	QuadIndex::~QuadIndex()
	{}


	int QuadIndex::getDepth(){
		return depth;
	}

	int QuadIndex::getLevelSize(int level){
		if ((level >= depth)||(level<0)) return 0;	
		return (1<<level);
	}
	int QuadIndex::getLevelWidth(int level){
		if ((level >= depth)||(level<0)) return 0;	
		return getLevelSize(level);
	}
	int QuadIndex::getLevelHeight(int level){
		if ((level >= depth)||(level<0)) return 0;	
		return getLevelSize(level);
	}

	/**
	*  Returns the unscaled size of a Quad on the given level
	*/
	int QuadIndex::getLevelQuadSize(int level){
		if ((level >= depth)||(level<0)) return 0;	
		return (1<<(depth-level-1));
	}

	void QuadIndex::setClipmapFile(FILE* clipmapFile){
		this->file = clipmapFile;
	}

	void QuadIndex::setLevelHeaderSize(int levelHeaderSize){
		std::cout << "Setting level header size to " << levelHeaderSize << std::endl;	
		this->levelHeaderSize = levelHeaderSize;
	}

	void QuadIndex::setVertexSize(int vertexSize){
		std::cout << "Setting quad size to " << vertexSize << std::endl;	
		this->vertexSize = vertexSize;
	}

	Vertex* QuadIndex::getVertexObject(int x, int y, int level){
		if ((level>depth)||(level<0)){
			std::cout << "ERROR : getting vertex " << x << "," << y << "," << level << " is out of bounds!" << std::endl;
			return 0;
		}

		if ((x>getLevelWidth(level))||(y>getLevelHeight(level))||(x<0)||(y<0)){
			std::cout << "ERROR : getting vertex " << x << "," << y << "," << level << " is out of bounds!" << std::endl;
			return 0;
		}
		if (!isVertexInMemory(x,y,level)){
			loadVertexFromDisk(x,y,level);
		}
		Vertex** vertexLevel = vertexTree[level];
		Vertex* vertex = vertexLevel[y*(getLevelSize(level)+1)+x];
		return vertex;
	}

	Vector3 QuadIndex::getVertex(int x, int y, int level){
		if ((level>depth)||(level<0)){
			std::cout << "ERROR : getting vertex " << x << "," << y << "," << level << " is out of bounds!" << std::endl;
			return Vector3(0,0,0);
		}

		if ((x>getLevelWidth(level))||(y>getLevelHeight(level))||(x<0)||(y<0)){
			std::cout << "ERROR : getting vertex " << x << "," << y << "," << level << " is out of bounds!" << std::endl;
			return Vector3(0,0,0);
		}
		if (!isVertexInMemory(x,y,level)){
			loadVertexFromDisk(x,y,level);
		}
		Vertex** vertexLevel = vertexTree[level];
		Vertex* vertex = vertexLevel[y*(getLevelSize(level)+1)+x];
		return vertex->getVertex();
	}

	Vector3 QuadIndex::getNormal(int x, int y, int level){
		if ((level>depth)||(level<0)){
			std::cout << "ERROR : getting vertex " << x << "," << y << "," << level << " is out of bounds!" << std::endl;
			return Vector3(0,0,0);
		}

		if ((x>getLevelWidth(level))||(y>getLevelHeight(level))||(x<0)||(y<0)){
			std::cout << "ERROR : getting vertex " << x << "," << y << "," << level << " is out of bounds!" << std::endl;
			return Vector3(0,0,0);
		}
		if (!isVertexInMemory(x,y,level)){
			loadVertexFromDisk(x,y,level);
		}
		Vertex** vertexLevel = vertexTree[level];
		Vertex* vertex = vertexLevel[y*(getLevelSize(level)+1)+x];
		return vertex->getNormal();
	}

	void QuadIndex::loadVertexFromDisk(int x, int y, int level){
		Vector3i* poss;
		if ((bufferQueue.size()>=MAXBUFFERSIZE)||(bufferQueue.size()>bufferQueue.max_size())){
			if (bufferQueue.size()>bufferQueue.max_size()) {
				std::cout << "WARNING maximum deque size of " << bufferQueue.max_size() << " reached" << std::endl;
			}
			freemem();
			std::cout << "Freeing memory, " << bufferQueue.size() << " reached" << std::endl;
		}
		if (!isVertexInMemory(x,y,level)){
			if ((level < depth)&&(level>=0)&&(x <= getLevelWidth(level))&&(y <= getLevelHeight(level))&&(x >= 0)&&(y >= 0)) {
				// Read from the file at the right position
				int qpos = getVertexPosition(x,y,level);
				fseek(file, qpos , SEEK_SET);
				int i;
				for (i=0;i<NBOFVERTICESTOREAD;i++){
					if ((x+i<=getLevelWidth(level))&&(y<=getLevelHeight(level))){
						Vertex* vertex = readVertex(x+i,y,level);
						if (!isVertexInMemory(x+i,y,level)){
							setVertex(x+i,y,level,vertex);
							setVertexInMemory(x+i,y,level);
						}
					} else {
						break;
					}
				}

				bufferQueue.push_front(new Vector3i(x,y,level));
				poss = bufferQueue.back();
			}
		}
	}

	Vertex* QuadIndex::readVertex(int x,int y,int level){
		//	std::cout << "Reading quad " << x << "," << y << " level " << level << " from disk" << std::endl;	

		Real* vertexarray = new Real[VERTEXSIZE];
		Real height;
		int rlength = fread(&height, sizeof(Real), 1, file);
		if (rlength!=1){
			std::cout << "ERROR Could not read height, read " << rlength << " bytes" << std::endl;
		}

		//read vertex
		fread(vertexarray, sizeof(Real), VERTEXSIZE, file);

		Vector3 coords = Vector3(vertexarray[0], vertexarray[1], vertexarray[2]);

#if USE_NORMALS
		Vector3 normal = Vector3(vertexarray[3], vertexarray[4], vertexarray[5]);
		Vertex* vertex = new Vertex(coords,normal,height);
#else
		Vertex* vertex = new Vertex(coords,height);
#endif


		return vertex;
	}

	bool QuadIndex::isVertexInMemory(int x, int y, int level){
		if ((level >= depth)||(level<0)||(x > getLevelSize(level))||(y > getLevelSize(level))||(x < 0)||(y < 0)) {
			return false;
		}
		return vertexLoaded[level][y*(getLevelSize(level)+1)+x];
	}

	void QuadIndex::setVertex(int x, int y, int level, Vertex* vertex){

		Vertex** vertexLevel = vertexTree[level];
		vertexLevel[y*getLevelSize(level)+x] = vertex;
	}

	void QuadIndex::setVertexInMemory(int x, int y, int level){
		if ((level < depth)&&(level>=0)&&(x <= getLevelWidth(level))&&(y <= getLevelHeight(level))&&(x >= 0)&&(y >= 0)) {
			vertexLoaded[level][y*(getLevelSize(level)+1)+x] = true;
		}
	}
	void QuadIndex::setVertexOutMemory(int x, int y, int level){
		if ((level < depth)&&(level>=0)&&(x <= getLevelWidth(level))&&(y <= getLevelHeight(level))&&(x >= 0)&&(y >= 0)) {
			vertexLoaded[level][y*(getLevelSize(level)+1)+x] = false;
		}
	}

	void QuadIndex::freemem(){
		Vector3i* pos;
		pos = bufferQueue.back();
		// Try to free memory. If it fails, try next quad
		while ((!unloadVertex(pos->x,pos->y,pos->z))&&(bufferQueue.size()>0)){
			bufferQueue.pop_back();
			pos = bufferQueue.back();
		}
		bufferQueue.pop_back();
	}

	bool QuadIndex::unloadVertex(int x, int y, int level){
		if (isVertexInMemory(x,y,level)){
			if ((level < depth)&&(level>=0)&&(x <= getLevelWidth(level))&&(y <= getLevelHeight(level))&&(x >= 0)&&(y >= 0)) {
				Vertex* vertex = getVertexObject(x,y,level);

				setVertex(x,y,level,NULL);
				setVertexOutMemory(x,y,level);

				delete vertex;	
				return true;
			}	
		}
		return false;	
	}

	int QuadIndex::getVertexPosition(int x, int y, int level){
		if ((level < depth)&&(level>=0)&&(x <= getLevelWidth(level))&&(y <= getLevelHeight(level))&&(x >= 0)&&(y >= 0)) {
			int pos = (depth-level)*levelHeaderSize;
			int i;
			for (i=depth-1;i>level;i--){
				pos+=(getLevelHeight(i)+1)*(getLevelWidth(i)+1)*vertexSize;
			}
			pos += vertexSize*(x+y*(getLevelWidth(level)+1));
			return pos;
		} else {
			std::cout << "WARNING : Not in boundaries!" << std::endl;	
			return -1;
		}
	}

	/**
	* Returns the quad on the given coordinates
	* WARNING : Should be used as few as possible, because it generates a new quad
	* 		every time
	*/
	// Quad QuadIndex::getQuad(int x, int y, int level){
	// 	Quad quad;
	// 	int i,j,k;
	// 	for (i=0;i<2;i++){
	// 		for (j=0;j<2;j++){
	// 			Vertex* vertex = getVertexObject(x+i,y+j,level);
	// 			quad.vertices[(i*2+j)*3] = vertex->getVertex()[(i*2+j)*3];
	// 			quad.vertices[(i*2+j)*3+1] = vertex->getVertex()[(i*2+j)*3+1];
	// 			quad.vertices[(i*2+j)*3+2] = vertex->getVertex()[(i*2+j)*3+2];
	// #if USE_NORMALS
	// 			quad.normals[(i*2+j)*3] = vertex->getNormal()[(i*2+j)*3];
	// 			quad.normals[(i*2+j)*3+1] = vertex->getNormal()[(i*2+j)*3+1];
	// 			quad.normals[(i*2+j)*3+2] = vertex->getNormal()[(i*2+j)*3+2];
	// #endif			
	// 		}
	// 	}
	// 	return quad;
	//}



}
