#include "VertexBuffer.h"

namespace Ogre {

	VertexBuffer::VertexBuffer(int depth)
	{
		this->depth=depth;
		for (int i=0;i<depth;i++){	
			vertexTree.push_back(new Vertex*[BUFFERVOLUME]);
			vertexLoaded.push_back(new bool[BUFFERVOLUME]);
			bufferPosition.push_back(Vector2i(0,0));
			bufferStart.push_back(0);
			for (int j=0;j<BUFFERVOLUME;j++){
				vertexLoaded[i][j] = false;
				vertexTree[i][j] = 0;
			}
		}
	}
	VertexBuffer::~VertexBuffer()
	{}

	void VertexBuffer::createLevel(int level, int x, int y){
		const size_t lvl = level;
		while(lvl >= vertexTree.size()){
			vertexTree.push_back(new Vertex*[BUFFERVOLUME]);
			vertexLoaded.push_back(new bool[BUFFERVOLUME]);
			bufferPosition.push_back(Vector2i(x,y));
			bufferStart.push_back(0);
			for (int j=0;j<BUFFERVOLUME;j++){		
				vertexLoaded[vertexTree.size()-1][j] = false;
				vertexTree[vertexTree.size()-1][j] = 0;
			}
		}
	}

	void VertexBuffer::put(Vertex* vertex, int x, int y, int level){
		const size_t lvl = level;
		if (lvl>=vertexTree.size()){
			createLevel(level, x ,y);
		}
		if (!isInBufferBorders(x,y,level)){
			moveBufferFor(x,y,level);
		}

		setVertexInMemory(x,y,level);
		//TODO Only one conversion per call
		convertToRelative(x,y,level);

		vertexTree[level][(x*BUFFERSIZE+y+bufferStart[level])%BUFFERVOLUME] = vertex;
	}

	bool VertexBuffer::isInBufferBorders(int x, int y, int level) const{
		return (bufferPosition[level].x<=x)&&(bufferPosition[level].y<=y)&&(bufferPosition[level].x+BUFFERSIZE>x)&&(bufferPosition[level].y+BUFFERSIZE>y);
	}

	void VertexBuffer::convertToRelative(int &x, int &y, int &level) const {
		x-=bufferPosition[level].x;
		y-=bufferPosition[level].y;
	}

	void VertexBuffer::moveBufferFor(int x, int y, int level){
		int i,j;
		if (bufferPosition[level].x>x){
			int amount = bufferPosition[level].x-x;

			if (amount < BUFFERSIZE){
				for (j=0;j<amount;j++){
					for (i=0;i<BUFFERSIZE;i++){
						unloadVertex(bufferPosition[level].x+BUFFERSIZE-j-1,bufferPosition[level].y+i,level);
					}
				}
				// adding BUFFERVOLUME would be insufficient
				bufferStart[level]=((bufferStart[level]-BUFFERSIZE*amount)%BUFFERVOLUME+BUFFERVOLUME)%BUFFERVOLUME;
			} else {
				for (j=0;j<BUFFERSIZE;j++){
					for (i=0;i<BUFFERSIZE;i++){
						unloadVertex(bufferPosition[level].x+BUFFERSIZE-j-1,bufferPosition[level].y+i,level);
					}
				}
				bufferStart[level]=0;
			}
			bufferPosition[level] = Vector2i(x,bufferPosition[level].y);
			// If amount > BUFFERSIZE just adding the volume could still give negative results
		} else if (bufferPosition[level].x+BUFFERSIZE<=x) {
			int amount = x-bufferPosition[level].x-BUFFERSIZE+1;
			if (amount < BUFFERSIZE){
				for (j=0;j<amount;j++){
					for (i=0;i<BUFFERSIZE;i++){
						unloadVertex(bufferPosition[level].x+j,bufferPosition[level].y+i,level);
					}
				}
				bufferStart[level]=(bufferStart[level]+BUFFERSIZE*amount)%BUFFERVOLUME;
			} else {
				for (j=0;j<BUFFERSIZE;j++){
					for (i=0;i<BUFFERSIZE;i++){
						unloadVertex(bufferPosition[level].x+j,bufferPosition[level].y+i,level);
					}
				}
				bufferStart[level]=0;
			}
			bufferPosition[level] = Vector2i(x-BUFFERSIZE+1,bufferPosition[level].y);
		}
		if (bufferPosition[level].y>y){
			int amount = bufferPosition[level].y-y;

			if (amount < BUFFERSIZE){
				for (j=0;j<amount;j++){
					for (i=0;i<BUFFERSIZE;i++){
						unloadVertex(bufferPosition[level].x+i,bufferPosition[level].y+BUFFERSIZE-j-1,level);
					}
				}
				// If amount > BUFFERSIZE just adding the volume could still give negative results
				bufferStart[level]=((bufferStart[level]-amount)%BUFFERVOLUME+BUFFERVOLUME)%BUFFERVOLUME;
			} else {
				for (j=0;j<BUFFERSIZE;j++){
					for (i=0;i<BUFFERSIZE;i++){
						unloadVertex(bufferPosition[level].x+i,bufferPosition[level].y+BUFFERSIZE-j-1,level);
					}
				}
				bufferStart[level]=0;
			}
			bufferPosition[level] = Vector2i(bufferPosition[level].x,y);
		} else if (bufferPosition[level].y+BUFFERSIZE<=y) {
			int amount = y-bufferPosition[level].y-BUFFERSIZE+1;
			if (amount < BUFFERSIZE){
				for (j=0;j<amount;j++){
					for (i=0;i<BUFFERSIZE;i++){
						unloadVertex(bufferPosition[level].x+i,bufferPosition[level].y+j,level);
					}
				}
				bufferStart[level]=(bufferStart[level]+amount)%BUFFERVOLUME;
			} else {
				for (j=0;j<BUFFERSIZE;j++){
					for (i=0;i<BUFFERSIZE;i++){
						unloadVertex(bufferPosition[level].x+i,bufferPosition[level].y+j,level);
					}
				}
				bufferStart[level]=0;
			}
			bufferPosition[level] = Vector2i(bufferPosition[level].x,y-BUFFERSIZE+1);
		}	
	}

	void VertexBuffer::unloadVertex(int x, int y, int level){
		const size_t lvl = level;
		if ((lvl < vertexTree.size())&&(level>=0)&&(x < getLevelSize(level))&&(y < getLevelSize(level))&&(x >= 0)&&(y >= 0)) {
			if (isInBuffer(x,y,level)){
				Vertex* vertex = getVertex(x,y,level);

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

				if (vertex!=0){	
					delete vertex;
				}
			}
		} else {
			std::cout << "ERROR : Failed unloading vertex " << x << "," << y << " : not in bounds" << std::endl;
		}
	}

	Vertex* VertexBuffer::getVertex(int x, int y, int level) const {
		return vertexTree[level][getPositionInBuffer(x,y,level)];
	}

	int VertexBuffer::getPositionInBuffer(int x, int y, int level) const {
		convertToRelative(x,y,level);
		return getRelativePositionInBuffer(x, y, level);
	}

	int VertexBuffer::getRelativePositionInBuffer(int x, int y, int level) const {
		return (x*BUFFERSIZE+y+bufferStart[level])%BUFFERVOLUME;
	}

	bool VertexBuffer::isInBuffer(int x, int y,int level) const {
		const size_t lvl = level;
		if ((lvl>=vertexTree.size())||(level<0)){
			return false;
		}
		if (!isInBufferBorders(x,y,level)){
			return false;
		}
		if (!isInLevelBorders(x,y,level)){
			return false;
		}
		return vertexLoaded[level][getPositionInBuffer(x,y,level)];
	}

	long VertexBuffer::getLevelSize(int level) const {
		if (level<0) return 0;	
		return (1<<level)+1;
	}

	void VertexBuffer::setVertexInMemory(int x, int y, int level){
		if (isInLevelBorders(x,y,level)) {
			if (isInBufferBorders(x,y,level)){
				vertexLoaded[level][getPositionInBuffer(x,y,level)] = true;
				convertToRelative(x,y,level);
			}
		}
	}
	void VertexBuffer::setVertexOutMemory(int x, int y, int level){
		if (isInLevelBorders(x,y,level)) {
			if (isInBufferBorders(x,y,level)){
				vertexLoaded[level][getPositionInBuffer(x,y,level)] = false;
				convertToRelative(x,y,level);
			}
		}
	}

	bool VertexBuffer::isInLevelBorders(int x, int y, int level) const{
		return (level>=0)&&(x < getLevelSize(level))&&(y < getLevelSize(level))&&(x >= 0)&&(y >= 0);
	}


} // namespace Ogre
