#include "QuadIndex.h"

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];
//	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)];		
		for (int j=0;j<getLevelSize(i)*getLevelSize(i);j++){		
			quadLoaded[i][j] = false;
		}
	}
	bufferQueue.clear();
//	std::cout << "Depth = " << depth << std::endl;	
}
QuadIndex::~QuadIndex()
{}

Quad* QuadIndex::getQuad(int x, int y, int level){
	bool print = false;
	if (print) {
		std::cout << "Getting quad " << x << "," << y << " from level " << level << std::endl;	
	}
	// Geef null terug indien het level te hoog is of kleiner als 0
//	std::cout << "Checking level for max depth " << depth << std::endl;	
	if ((level >= depth)||(level<0)) {
		std::cout << "level " << level << " not valid, depth " << depth << std::endl;
		return NULL;	
	}
	// Geef null terug indien de gevraagde patch 
	// buiten het bestaande gebied ligt
//	std::cout << "Checking x and y" << std::endl;	
	if ((x >= getLevelSize(level))||(y >= getLevelSize(level))||(x < 0)||(y < 0)) {
//		std::cout << "x or y not valid " << x << "," << y << " for level " << level << std::endl;
		return NULL;	
	}
//	std::cout << "Seems to be in bounds, checking for memory" << std::endl;	
	
	if (!isQuadInMemory(x,y,level)){
		if (print) {
			std::cout << "Not in memory. Loading..." << std::endl;	
		}
		loadQuadFromDisk(x,y,level);
	} else {
		if (print) {
			std::cout << "Seems to be in memory" << std::endl;	
		}
	}
	// Give the pointer back, not the patch itself
	Quad** quadLevel = quadTree[level];
	Quad* quad = quadLevel[y*getLevelSize(level)+x];
	if (print) {
		std::cout << "Got quad, now returning " << quad << std::endl;	
	}
	return quad;
//	std::cout << "got quad " << quadje << std::endl;	
//	quadje = quad;
//	return *(quadTree[level]+y*size+x);
}

Quad* QuadIndex::getQuad(Vector3i* quadPos){
	return getQuad(quadPos->x,quadPos->y,quadPos->z);
}
Quad* QuadIndex::getQuad(Vector3i quadPos){
	return getQuad(quadPos.x,quadPos.y,quadPos.z);
}

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

Quad** QuadIndex::getLevelQuad(int level){
	if ((level >= depth)||(level<0)) return NULL;	
	return quadTree[level];
}

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::setQuad(int x, int y, int level, Quad* quad){
//	std::cout << "ERROR (DEPRECATED) : setting quad " << quad << " " << x << "," << y << " level " << level << std::endl;
	Quad** quadLevel = quadTree[level];
//	std::cout << "quadlevel loaded " << quadLevel << " size should be " << getLevelSize(level) << std::endl;
	quadLevel[y*getLevelSize(level)+x] = quad;
//	std::cout << "got quad " << quadje << std::endl;	
//	quadje = quad;
//	std::cout << "setting done" << std::endl;
}

bool QuadIndex::isQuadInMemory(int x, int y, int level){
	bool print=false;
	if (print){
		std::cout << "Checking " << x << "," << y << " level " << level << " for being in memory" << std::endl;
	}
	if ((level >= depth)||(level<0)||(x >= getLevelSize(level))||(y >= getLevelSize(level))||(x < 0)||(y < 0)) {
		return false;
	}
	return quadLoaded[level][y*getLevelSize(level)+x];
}

void QuadIndex::setQuadInMemory(int x, int y, int level){
	if ((level < depth)&&(level>=0)&&(x < getLevelWidth(level))&&(y < getLevelHeight(level))&&(x >= 0)&&(y >= 0)) {
		quadLoaded[level][y*getLevelSize(level)+x] = true;
	}
}
void QuadIndex::setQuadOutMemory(int x, int y, int level){
	if ((level < depth)&&(level>=0)&&(x < getLevelWidth(level))&&(y < getLevelHeight(level))&&(x >= 0)&&(y >= 0)) {
		quadLoaded[level][y*getLevelSize(level)+x] = false;
	}
}
void QuadIndex::setClipmapFile(FILE* clipmapFile){
	this->file = clipmapFile;
}
void QuadIndex::loadQuadFromDisk(int x, int y, int level){
	bool print = false;
	if (print){			
		std::cout << "Loading quad from disk " << x << "," << y << " level " << level << std::endl;	
	}
	Vector3i* poss;
	if (print){			
		if (!bufferQueue.empty()){
			poss = bufferQueue.back();
			std::cout << "Backquad " << poss << " at " << &poss << std::endl;	
		}
	}
	if (print){			
		std::cout << "Checking memory" << std::endl;	
	}
	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 (print){			
		std::cout << "Memory check complete." << std::endl;	
	}
	if (print){			
		if (!bufferQueue.empty()){
			poss = bufferQueue.back();
			std::cout << "Backquad " << poss << " at " << &poss << std::endl;	
		}
	}
	if (!isQuadInMemory(x,y,level)){
		if (print){			
			std::cout << "Not yet in memory. (OK)" << std::endl;	
		}
		if ((level < depth)&&(level>=0)&&(x < getLevelWidth(level))&&(y < getLevelHeight(level))&&(x >= 0)&&(y >= 0)) {
			if (print){			
				std::cout << "In boundaries (OK)" << std::endl;	
			}
			
			Vertex vertices1[(NBOFVERTICESTOREAD+1)];
			Vertex vertices2[(NBOFVERTICESTOREAD+1)];
			// Read from the file at the right position
			int qpos1 = getVertexPosition(x,y,level);
			int qpos2 = getVertexPosition(x,y+1,level);
			fseek(file, qpos1 , SEEK_SET);
			int i,j;
//			std::cout << "level " << level << " getLevelWidth(level) " << getLevelWidth(level) << " getLevelHeight(level) " << getLevelHeight(level) << std::endl;
			for (i=0;i<=NBOFQUADSTOREAD;i++){
				if ((x+i<=getLevelWidth(level))&&(y<=getLevelHeight(level))){
					vertices1[i] = readVertex();
				} else {
					break;
				}
			}
			fseek(file, qpos2 , SEEK_SET);
			for (i=0;i<=NBOFQUADSTOREAD;i++){
				if ((x+i<=getLevelWidth(level))&&(y+1<=getLevelHeight(level))){
					vertices2[i] = readVertex();
				} else {
					break;
				}
			}
			int max = i-1;
			for(i=0;i<max;i++){
				Quad* quad = new Quad();
				Vector3 vertex = vertices1[i].getVertex();
				Vector3 normal = vertices1[i].getNormal();
//				std::cout << "Vertex " << x+i << "," << y << " " << vertex.x << "," << vertex.y << "," << vertex.z << std::endl;
//				std::cout << "Normal " << normal.x << "," << normal.y << "," << normal.z << std::endl;
				quad->vertices[0] = vertex.x;
				quad->vertices[1] = vertex.y;
				quad->vertices[2] = vertex.z;
				quad->normals[0] = normal.x;
				quad->normals[1] = normal.y;
				quad->normals[2] = normal.z;
				vertex = vertices1[i+1].getVertex();
				normal = vertices1[i+1].getNormal();
//				std::cout << "Vertex " << x+i+1 << "," << y << " " << vertex.x << "," << vertex.y << "," << vertex.z << std::endl;
//				std::cout << "Normal " << normal.x << "," << normal.y << "," << normal.z << std::endl;
				quad->vertices[3] = vertex.x;
				quad->vertices[4] = vertex.y;
				quad->vertices[5] = vertex.z;
				quad->normals[3] = normal.x;
				quad->normals[4] = normal.y;
				quad->normals[5] = normal.z;
				vertex = vertices2[i].getVertex();
				normal = vertices2[i].getNormal();
//				std::cout << "Vertex " << x+i << "," << y+1 << " " << vertex.x << "," << vertex.y << "," << vertex.z << std::endl;
//				std::cout << "Normal " << normal.x << "," << normal.y << "," << normal.z << std::endl;
				quad->vertices[6] = vertex.x;
				quad->vertices[7] = vertex.y;
				quad->vertices[8] = vertex.z;
				quad->normals[6] = normal.x;
				quad->normals[7] = normal.y;
				quad->normals[8] = normal.z;
				vertex = vertices2[i+1].getVertex();
				normal = vertices2[i+1].getNormal();
//				std::cout << "Vertex " << x+i+1 << "," << y+1 << " " << vertex.x << "," << vertex.y << "," << vertex.z << std::endl;
//				std::cout << "Normal " << normal.x << "," << normal.y << "," << normal.z << std::endl;
				quad->vertices[9] = vertex.x;
				quad->vertices[10] = vertex.y;
				quad->vertices[11] = vertex.z;
				quad->normals[9] = normal.x;
				quad->normals[10] = normal.y;
				quad->normals[11] = normal.z;
				setQuad(x+i,y,level,quad);
				setQuadInMemory(x+i,y,level);
			}
			
			bufferQueue.push_front(new Vector3i(x,y,level));
	poss = bufferQueue.back();
	// Try to free memory. If it fails, try next quad
	if (print){			
		std::cout << "Backquad " << poss << " at " << &poss << std::endl;	
	}
		} else {
			std::cout << "WARNING : Not in boundaries!" << std::endl;	
		}
	} else {
		std::cout << "WARNING : Already in memory, but still atempting to load!" << std::endl;	
	}
	if (print){			
		std::cout << "Done : loading from disk" << std::endl;	
	}
}

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));
//		std::cout << "Going to read from " << pos << std::endl;
		return pos;
	} else {
		std::cout << "WARNING : Not in boundaries!" << std::endl;	
		return -1;
	}
}

void QuadIndex::freemem(){
	bool print = true;
	if (print){			
		std::cout << "Freeing memory..." << std::endl;	
	}
	if (print){			
		std::cout << "Getting quad for removal" << std::endl;	
	}
	Vector3i* pos;
	pos = bufferQueue.back();
	// Try to free memory. If it fails, try next quad
	if (print){			
		std::cout << "Got quad " << pos << " at " << &pos << " " << pos->x << "," << pos->y << " level " << pos->z << std::endl;	
	}
	while ((!unloadQuad(pos->x,pos->y,pos->z))&&(bufferQueue.size()>0)){
		bufferQueue.pop_back();
		pos = bufferQueue.back();
	}
	if (print){			
		std::cout << "Memory check complete." << std::endl;	
	}
	bufferQueue.pop_back();
}
bool QuadIndex::unloadQuad(int x, int y, int level){
	bool print = false;
	if (print){			
		std::cout << "Removing " << x << "," << y << " " << level << " from memory" << std::endl;
	}
	if (isQuadInMemory(x,y,level)){
		if ((level < depth)&&(level>=0)&&(x < getLevelWidth(level))&&(y < getLevelHeight(level))&&(x >= 0)&&(y >= 0)) {
			Quad* quad = getQuad(x,y,level);
			
			if ((y<getLevelHeight(level))&&(isQuadInMemory(x,y+1,level))){
				getQuad(x,y+1,level)->setBottom(NOTINMEMORY);
			}
			if ((y>0)&&(isQuadInMemory(x,y-1,level))){
				getQuad(x,y-1,level)->setTop(NOTINMEMORY);
			}
			if ((x<getLevelWidth(level))&&(isQuadInMemory(x+1,y,level))){
				getQuad(x+1,y,level)->setRight(NOTINMEMORY);
			}
			if ((x>0)&&(isQuadInMemory(x-1,y,level))){
				getQuad(x-1,y,level)->setLeft(NOTINMEMORY);
			}
		
			setQuad(x,y,level,NULL);
			setQuadOutMemory(x,y,level);

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


void QuadIndex::handleNeighbours(Quad* quad, int x, int y, int level){	
	// Set the neighbours
	if ((x>0)&&(x<getLevelWidth(level))&&(y>0)&&(y<getLevelHeight(level))){
		if (isQuadInMemory(x,y-1,level)){
			quad->setTop(getQuad(x,y-1,level));
			getQuad(x,y-1,level)->setBottom(quad);
		} else {
			quad->setTop(NOTINMEMORY);
		}
	} else {
		quad->setTop(NULL);
	}
	if ((x>0)&&(x<getLevelWidth(level))&&(y>0)&&(y<getLevelHeight(level))){
		if (isQuadInMemory(x,y+1,level)){
			quad->setBottom(getQuad(x,y+1,level));
			getQuad(x,y+1,level)->setTop(quad);
		} else {
			quad->setBottom(NOTINMEMORY);
		}
	} else {
		quad->setBottom(NULL);
	}
	if ((x>0)&&(x<getLevelWidth(level))&&(y>0)&&(y<getLevelHeight(level))){
		if (isQuadInMemory(x-1,y,level)){
			quad->setLeft(getQuad(x-1,y,level));
			getQuad(x-1,y,level)->setRight(quad);
		} else {
			quad->setLeft(NOTINMEMORY);
		}
	} else {
		quad->setLeft(NULL);
	}
	if ((x>0)&&(x<getLevelWidth(level))&&(y>0)&&(y<getLevelHeight(level))){
		if (isQuadInMemory(x+1,y,level)){
			quad->setRight(getQuad(x+1,y,level));
			getQuad(x+1,y,level)->setLeft(quad);
		} else {
			quad->setRight(NOTINMEMORY);
		}
	} else {
		quad->setRight(NULL);
	}
}

Vector3 QuadIndex::getVertex(int x, int y, int level){
	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 ((x<getLevelWidth(level))&&(y<getLevelHeight(level))){
		Quad* quad = getQuad(x,y,level);
		return Vector3(quad->vertices[0],quad->vertices[1],quad->vertices[2]);
	} else if(x>=getLevelWidth(level)) {
			if (y<getLevelHeight(level)) {
				Quad* quad = getQuad(x-1,y,level);
				return Vector3(quad->vertices[3],quad->vertices[4],quad->vertices[5]);
			} else {
				Quad* quad = getQuad(x-1,y-1,level);
				return Vector3(quad->vertices[9],quad->vertices[10],quad->vertices[11]);
			}
	} else {
		Quad* quad = getQuad(x,y-1,level);
		return Vector3(quad->vertices[6],quad->vertices[7],quad->vertices[8]);
	}
}

Vector3 QuadIndex::getNormal(int x, int y, int level){
	if ((x>getLevelWidth(level))||(y>getLevelHeight(level))||(x<0)||(y<0)){
		std::cout << "ERROR : getting normal " << x << "," << y << "," << level << " is out of bounds!" << std::endl;
		return Vector3(0,0,0);
	}
	if ((x<getLevelWidth(level))&&(y<getLevelHeight(level))){
		Quad* quad = getQuad(x,y,level);
		return Vector3(quad->normals[0],quad->normals[1],quad->normals[2]);
	} else if(x>=getLevelWidth(level)) {
			if (y<getLevelHeight(level)) {
				Quad* quad = getQuad(x-1,y,level);
				return Vector3(quad->normals[3],quad->normals[4],quad->normals[5]);
			} else {
				Quad* quad = getQuad(x-1,y-1,level);
				return Vector3(quad->normals[9],quad->normals[10],quad->normals[11]);
			}
	} else {
		Quad* quad = getQuad(x,y-1,level);
		return Vector3(quad->normals[6],quad->normals[7],quad->normals[8]);
	}
}


Vertex QuadIndex::readVertex(){
	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 = Vertex(coords,normal,height);
#else
	Vertex vertex = Vertex(coords,height);
#endif


	return vertex;
}

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

}
