#include "CubemappedPlanetReader.h"

namespace Ogre {

	CubemappedPlanetReader::CubemappedPlanetReader()
	{}
	CubemappedPlanetReader::~CubemappedPlanetReader()
	{}

	Vertex* CubemappedPlanetReader::getVertex(int x, int y, int level, int map){
		if (!isInLevelBorders(x,y,level)) {
			return getExternalVertex(x,y,level,map);
		}
		if (isInBuffer(x,y,level,map)){
			return ((vertexBuffers[map])->getVertex(x,y,level));
		} else {
			return calculateVertex(x,y,level,map);
		}
	}

	Vertex* CubemappedPlanetReader::getExternalVertex(int x, int y, int level, int map){
		if (x<0){
			if (map<4){
				return getVertex(x+getLevelSize(level),y,level,(map+3)%4);		
			} else if (map==4) {
				return getVertex(y,x+getLevelSize(level),level,0);
			} else if (map==5) {
				return getVertex(getLevelSize(level)-y,getLevelSize(level)-x,level,0);
			}
		} else if (x>=getLevelSize(level)){
			if (map<4){
				return getVertex(x-getLevelSize(level),y,level,(map+1)%4);		
			} else if (map==4) {
				return getVertex(getLevelSize(level)-y,x-getLevelSize(level),level,2);
			} else if (map==5) {
				return getVertex(y,2*getLevelSize(level)-x,level,2);
			}
		} else if (y<0){
			switch (map) {
			case 0 :
				return getVertex(-y,x,level,4);		
				break;
			case 1 :
				return getVertex(x,getLevelSize(level)+y,level,4);		
				break;
			case 2 :
				return getVertex(getLevelSize(level)+y,getLevelSize(level)-x,level,4);		
				break;
			case 3 :
				return getVertex(getLevelSize(level)-x,-y,level,4);		
				break;
			case 4 :
				return getVertex(getLevelSize(level)-x,-y,level,3);
				break;
			case 5 :
				return getVertex(x,getLevelSize(level)+y,level,1);
				break;
			}
		} else if (y>=getLevelSize(level)){
			switch (map) {
			case 0 :
				return getVertex(y-getLevelSize(level),getLevelSize(level)-x,level,5);
				break;
			case 1 :
				return getVertex(x,2*getLevelSize(level)-y,level,5);
				break;
			case 2 :
				return getVertex(2*getLevelSize(level)-y,x,level,5);
				break;
			case 3 :
				return getVertex(getLevelSize(level)-x,y-getLevelSize(level),level,5);
				break;
			case 4 :
				return getVertex(x,y-getLevelSize(level),level,1);
				break;
			case 5 :
				return getVertex(getLevelSize(level)-x,2*getLevelSize(level)-y,level,3);
				break;
			}
		}	
		std::cout << "ERROR : vertex on edge does not exist on edge " << x << "," << y << " level " << level << " map " << map << std::endl;
		return 0;
	}

	bool CubemappedPlanetReader::isOnLevelBorders(int x, int y, int level) const{
		return (level>=0)&&((x == getLevelSize(level)-1)||(y == getLevelSize(level)-1)||(x == 0)||(y == 0));
	}

	Real CubemappedPlanetReader::getEdgeHeight(int x, int y, int level, int map) const {
		if (map<4){
			if (x == getLevelSize(level)-1){
				return getRealHeight(0,y,level,(map+1)%4);		
			} else {
				return getRealHeight(x,y,level,map);
			}
		} else if (map==4) {
			if (x == getLevelSize(level)-1){
				return getRealHeight(getLevelSize(level)-1-y,0,level,2);
			} else if (x == 0) {
				return getRealHeight(y,0,level,0);
			} else if (y == getLevelSize(level)-1){
				return getRealHeight(x,0,level,1);
			} else if (y == 0) {
				return getRealHeight(getLevelSize(level)-1-x,0,level,3);
			}
		} else if (map==5) {
			if (x == getLevelSize(level)-1){
				return getRealHeight(y,getLevelSize(level)-1,level,0);
			} else if (x == 0) {
				return getRealHeight(getLevelSize(level)-1-y,getLevelSize(level)-1,level,2);
			} else if (y == getLevelSize(level)-1){
				return getRealHeight(getLevelSize(level)-1-x,getLevelSize(level)-1,level,1);
			} else if (y == 0) {
				return getRealHeight(x,getLevelSize(level)-1,level,3);
			}
		}
		std::cout << "ERROR : vertex on edge does not exist on edge " << x << "," << y << " level " << level << " map " << map << std::endl;
		return 0;
	}

	Vector3 CubemappedPlanetReader::getEdgePosition(int x, int y, int level, int map, Real height){
		if (map<4){
			if (x == getLevelSize(level)-1){
				return getPosition(0,y,level,(map+1)%4, height);
			} else {
				return getPosition(x,y,level,map, height);
			}
		} else if (map==4) {
			if (x == getLevelSize(level)-1){
				return getPosition(getLevelSize(level)-1-y,getLevelSize(level)-1,level,2, height);
			} else if (x == 0) {
				return getPosition(y,getLevelSize(level)-1,level,0, height);
			} else if (y == getLevelSize(level)-1){
				return getPosition(x,getLevelSize(level)-1,level,1, height);
			} else if (y == 0) {
				return getPosition(getLevelSize(level)-1-x,getLevelSize(level)-1,level,3, height);
			}
		} else if (map==5) {
			if (x == getLevelSize(level)-1){
				return getPosition(y,0,level,0, height);
			} else if (x == 0) {
				return getPosition(getLevelSize(level)-y-1,0,level,2, height);
			} else if (y == getLevelSize(level)-1){
				return getPosition(getLevelSize(level)-1-x,0,level,1, height);
			} else if (y == 0) {
				return getPosition(x,0,level,3, height);
			}
		}
		std::cout << "ERROR : vertex on edge does not exist on edge " << x << "," << y << " level " << level << " map " << map << std::endl;
		return Vector3();
	}

	Vertex* CubemappedPlanetReader::calculateVertex(int x, int y, int level, int map){
		if (!isInLevelBorders(x,y,level)) return NULL;
		Real height;
		height = getHeight(x,y,level,map);
		Vector3 position = getPosition(x,y,level,map, height);

#if USE_NORMALS
		Vector3 normal = getNormal(x, y, level, map, position);
		Vertex* vertex = new Vertex(position,normal,height);
#else
		Vertex* vertex = new Vertex(position,height);
#endif
#if USE_TEXTURES	
		//TODO We need a Vector2 here
		Vector2 texCoords = getTextureCoordinates(x, y, level, map);
		vertex->setTextureCoordinates(texCoords.x,texCoords.y);
#endif

		if(!isInBuffer(x,y,level,map)){
			vertexBuffers[map]->put(vertex,x,y,level);
		}
		return vertex;
	}

	Real CubemappedPlanetReader::getHeight(int x, int y, int level, int map) const {
		if (isOnLevelBorders(x,y,level)) {
			return getEdgeHeight(x,y,level,map);
		} else {
			return getRealHeight(x,y,level,map);
		}
	}

	Real CubemappedPlanetReader::getRealHeight(int x, int y, int level, int map) const {
		if (isInBuffer(x,y,level,map)){
			return ((vertexBuffers[map])->getVertex(x,y,level))->getHeight();
		} else {
			if (level < getGeometryDepth()){
				return readHeight(x,y,level,map);
			} else {
				return calculateHeight(x,y,level,map);
			}
		}
	}

	Real CubemappedPlanetReader::readHeight(int x, int y, int level, int map) const {
		Real height;
		fseek(mapfile, getVertexPosition(x,y,level,map), SEEK_SET);
		fread(&height, sizeof(Real), 1, mapfile);
		return height;
	}

	int CubemappedPlanetReader::getVertexPosition(int x,int y,int level,int map) const{
		return PlanetReader::getVertexPosition(x,y,level)*getMapSize();
	}

	int CubemappedPlanetReader::getMapSaveSize() const{
		return mMapSize;
	}

	void CubemappedPlanetReader::putOnCorrectMap(int &x, int &y, int &level, int &map) const {
		int t;
		if ((x<0)||(x>=getLevelSize(level))||(y<0)||(y>=getLevelSize(level))){
			if (x<0){
				switch (map) {
				case 0 :
				case 1 :
				case 2 :
				case 3 :
					map = (map+3)%4;
					x=getLevelSize(level)+x-1;
					break;
				case 4 :
					map = 0;
					t=-x;
					x=y;
					y=t;
					break;
				case 5 :
					map = 2;
					t=getLevelSize(level)+x-1;
					x=getLevelSize(level)-y-1;
					y=t;
					break;
				}
			} else if (x>=getLevelSize(level)){
				switch (map) {
				case 0 :
				case 1 :
				case 2 :
				case 3 :
					map = (map+1)%4;
					x=x-getLevelSize(level)+1;
					break;
				case 4 :
					map = 2;
					t=x-getLevelSize(level)+1;
					x=getLevelSize(level)-y-1;
					y=t;
					break;
				case 5 :
					map = 0;
					t=2*getLevelSize(level)-x-1;
					x=y;
					y=t;
					break;
				}
			} else if (y<0){
				switch (map) {
				case 0 :
					map = 4;
					t=-y;
					y=x;
					x=t;
					break;
				case 1 :
					map = 4;
					y=getLevelSize(level)+y-1;
					break;
				case 2 :
					map = 4;
					t=getLevelSize(level)+y-1;
					y=getLevelSize(level)-x-1;
					x=t;
					break;
				case 3 :
					map = 4;
					y=-y;
					x=getLevelSize(level)-x-1;
					break;
				case 4 :
					map = 3;
					y=-y;
					x=getLevelSize(level)-x-1;
					break;
				case 5 :
					map = 3;
					y=getLevelSize(level)+y-1;
					break;
				}
			} else if (y>=getLevelSize(level)){
				switch (map) {
				case 0 :
					map = 5;
					t=y-getLevelSize(level)+1;
					y=getLevelSize(level)-1-x;
					x=t;
					break;
				case 1 :
					map = 5;
					y=y-getLevelSize(level)+1;
					break;
				case 2 :
					map = 5;
					t=2*getLevelSize(level)-y-1;
					y=x;
					x=t;
					break;
				case 3 :
					map = 5;
					y=2*getLevelSize(level)-y-1;
					x=getLevelSize(level)-x-1;
					break;
				case 4 :
					map = 1;
					y=y-getLevelSize(level)+1;
					break;
				case 5 :
					map = 1;
					y=2*getLevelSize(level)-y-1;
					x=getLevelSize(level)-x-1;
					break;
				}
			}
		}
		if (!isInLevelBorders(x,y,level)){
			putOnCorrectMap(x,y,level,map);
		}
	}

	Vector3 CubemappedPlanetReader::getPosition(int x, int y, int level, int map) const {
		if (!isInLevelBorders(x,y,level)){
			putOnCorrectMap(x,y,level,map);
		}
		Vector3 point = getPointOnSphere(x, y, level, map);
		Vector3 normal = point;
		normal.normalise();
		Real height = getHeight(x,y,level,map);
		normal *= height;
		normal *=mHeightScale;
		return point+normal;
	}

	Vector3 CubemappedPlanetReader::getPosition(int x, int y, int level, int map, Real height) const {
		Vector3 point = getPointOnSphere(x, y, level, map);
		Vector3 normal = point;
		normal.normalise();
		normal *= height*mHeightScale;
		point+=normal;
		return point;
	}

	Vector3 CubemappedPlanetReader::getPointOnSphere(int x, int y, int level, int map) const {
		Vector3 point;
		int levelSize = getLevelSize(level)-1;
		switch (map) {
			// y is normal up
		case 0 :
			//x -> z
			//y -> -x
			//z -> -y
			point = Vector3(-1,-(2*y/(Real)levelSize-1),(2*x/(Real)levelSize-1));
			break;
		case 1 :
			//x -> x
			//y -> z
			//z -> -y
			point = Vector3(2*x/(Real)levelSize-1,-(2*y/(Real)levelSize-1),1);
			break;
		case 2 :
			//x -> -z
			//y -> x
			//z -> -y
			point = Vector3(1,-(2*y/(Real)levelSize-1),-(2*x/(Real)levelSize-1));
			break;
		case 3 :
			//x -> -x
			//y -> -z
			//z -> -y
			point = Vector3(-(2*x/(Real)levelSize-1),-(2*y/(Real)levelSize-1),-1);
			break;
		case 4 :
			point = Vector3(2*x/(Real)levelSize-1,1,2*y/(Real)levelSize-1);
			break;
		case 5 :
			point = Vector3(-(2*x/(Real)levelSize-1),-1,(2*y/(Real)levelSize-1));
			break;
		}
		Vector3 normal = point;
		normal.normalise();
		normal *= mHeightOffset;
		point *= mScale;
		point+=mOffset;
		point+=normal;
		return point;
	}

	Vector3 CubemappedPlanetReader::getNormalOnSphere(int x, int y, int level, int map) const {
		Vector3 point;
		int levelSize = getLevelSize(level)-1;
		switch (map) {
			// y is normal up
		case 0 :
			//x -> z
			//y -> -x
			//z -> -y
			point = Vector3(-1,-(2*y/(Real)levelSize-1),(2*x/(Real)levelSize-1));
			break;
		case 1 :
			//x -> x
			//y -> z
			//z -> -y
			point = Vector3(2*x/(Real)levelSize-1,-(2*y/(Real)levelSize-1),1);
			break;
		case 2 :
			//x -> -z
			//y -> x
			//z -> -y
			point = Vector3(1,-(2*y/(Real)levelSize-1),-(2*x/(Real)levelSize-1));
			break;
		case 3 :
			//x -> -x
			//y -> -z
			//z -> -y
			point = Vector3(-(2*x/(Real)levelSize-1),-(2*y/(Real)levelSize-1),-1);
			break;
		case 4 :
			point = Vector3(2*x/(Real)levelSize-1,1,2*y/(Real)levelSize-1);
			break;
		case 5 :
			point = Vector3(-(2*x/(Real)levelSize-1),-1,(2*y/(Real)levelSize-1));
			break;
		}
		point.normalise();
		return point;
	}

	Vector3 CubemappedPlanetReader::getNormal(int x, int y, int level, int map, Vector3 &point) const {
		// Correct normals, not optimal
		Vector3 horizontal, vertical, point1, point2;
		int levelsize = getLevelSize(level);
		point1 = getPosition(x+1,y,level,map);
		point2 = getPosition(x,y+1,level,map);
		horizontal = getPosition(x-1,y,level,map);
		horizontal = point1 - horizontal;
		vertical = getPosition(x,y-1,level,map);
		vertical = point2 - vertical;
		horizontal.normalise();
		vertical.normalise();

		return -horizontal.crossProduct(vertical);

	}

	Real CubemappedPlanetReader::calculateHeight(int x, int y, int level, int map) const {
		Real val = interpolateHeight(x,y,level,map);
		return val;
	}

	Real CubemappedPlanetReader::interpolateHeight(int x, int y, int level, int map) const {
		if (x%2==0){
			if (y%2==0){
				return getHeight(x>>1,y>>1,level-1,map);
			} else {
				Real height1 = getHeight(x>>1,y>>1,level-1,map);
				Real height2 = getHeight(x>>1,(y>>1)+1,level-1,map);
				Real displace = abs(height1-height2)*(rnd()-0.5);
				return (height1+height2)/2.0+displace;
			}
		} else {
			if (y%2==0){
				Real height1 = getHeight(x>>1,y>>1,level-1,map);
				Real height2 = getHeight((x>>1)+1,y>>1,level-1,map);
				Real displace = abs(height1-height2);
				Real rndo = rnd();
				displace *= (rndo-0.5);
				return (height1+height2)/2.0+displace;
			} else {
				Real height1 = getHeight(x>>1,y>>1,level-1,map);
				Real height2 = getHeight(x>>1,(y>>1)+1,level-1,map);
				Real height3 = getHeight((x>>1)+1,y>>1,level-1,map);
				Real height4 = getHeight((x>>1)+1,(y>>1)+1,level-1,map);
				Real maxh = std::max(std::max(height1,height2),std::max(height3,height4));
				Real minh = std::min(std::min(height1,height2),std::min(height3,height4));
				Real displace = (maxh-minh)*(rnd()-0.5);
				return (height1+height2+height3+height4)/4.0+displace;
			}
		}
	}

	Real CubemappedPlanetReader::rnd() const {
		Real rnd = rand();
		Real rndmax = RAND_MAX;
		return rnd/rndmax;
	}

	bool CubemappedPlanetReader::isInBuffer(int x, int y,int level,int map) const {
		return vertexBuffers[map]->isInBuffer(x,y,level);
	}

	int CubemappedPlanetReader::getMapSize() const{
		return vertexBuffers[0]->getLevelSize(getGeometryDepth()-1);
	}

	Vertex* CubemappedPlanetReader::getQuad(Vector3ix pos, int map){
		if ((getVertex(pos.x,pos.y,pos.z,map)==0)||(getVertex(pos.x+1,pos.y+1,pos.z,map)==0)) return 0;
		return getVertex(pos.x,pos.y,pos.z,map);
	}

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

} // namespace Ogre
