/*****************************************************************************

File: NaturePatch.cpp
Desc: This class provides basic functionality of a terrain patch
Date: 2003/02/27

Author: Martin Persson

*****************************************************************************/

#include "ClipmapPatch.h"

#include <cmath>

// We define it here. If there are changes we don't have to change it in every step
// (Or use parameters in a function call)
#define TOOBIG ((double)getLevelQuadSize(reallevel)*(double)REGIONSIZE / sqdist > clipRegion)

namespace Ogre
{

	//----------------------------------------------------------------------------

	bool ClipmapPatch::initialise(SceneNode *sceneRoot, SeperateCubemappedPlanetReader* geometryReader, int map) {
		// Set a lot of attributes

		mGeometryReader = geometryReader;
		mScale = geometryReader->getScale();
		// Not yet used. But needed for when multiple planets will be implemented
		mWorld = geometryReader->getOffset();
		mMap = map;
		mScaleHeight = geometryReader->getHeightScale();
		mOffset = geometryReader->getHeightOffset();
		clipRegion = geometryReader->getClipRegion();

		// New patch, no refinement needed yet.
		needRefinement = false;

		// Put this value in a variable. It won't be changing, so there no need to
		// get it every time from the geometry itself
		nbOfLevels = getDepth();

		// Calculate where in the memory the patches have to be written
		mPatchOffset = map*REGIONSURF*nbOfLevels;


		heightMapWidth = geometryReader->getMapSize();
		heightMapHeight = geometryReader->getMapSize();
		Real maxradius = 256*mScaleHeight+mOffset;

		clipRegions.clear();
		mInit=false;

		// The initialisation was succesfull
		return true;
	}


	void ClipmapPatch::updateVertexBuffers(HardwareVertexBufferSharedPtr posVertexBuffer, int patchOffset){
		mPatchOffset = patchOffset;
		size_t i;
		for (i=0;i<clipRegions.size();i++){
			clipRegions[i]->setVertexBuffer(posVertexBuffer,patchOffset);
		}
	}

	void ClipmapPatch::refinePatch(HardwareVertexBufferSharedPtr posVertexBuffer, int patchOffset){
		// Create the new region
		createRegion(clipRegions.size(), posVertexBuffer);
		int i;
		// Let the new clipRegion know the other regions
		for (i=CUBEA;i<CUBEB;i++){
			clipRegions[clipRegions.size()-1]->setOtherRegions(i,&(otherPatches[i]->clipRegions));
		}
		// It is refined, no need to refine it again
		needRefinement = false;
		// The buffers have changed, update the geometry to the new buffers
		updateVertexBuffers(posVertexBuffer,mPatchOffset);
	}

	void ClipmapPatch::refreshAll(){
		size_t i;
		for (i=0;i<clipRegions.size();i++){
			clipRegions[i]->refreshAll();
		}
	}

	bool ClipmapPatch::updatePositions(Vector3 &campos){	
		// update the camera-position
		this->campos = campos;

		//	if (!mInit){
		//		mInit= true;
		//	}

		int i;
		// See where the camera is
		Vector3 rpiq = getRelativePointInSphere(&campos);
		// Calculate the center on the patch
		Real t = rpiq.x;
		Real s = rpiq.z;
		int tnbX = getLevelSize(max(maxlevel,2));
		int tnbZ = getLevelSize(max(maxlevel,2));
		// Calculate the center
		int tcenterx = (((int)(tnbX * t+1))>>1)<<1;		
		int tcenterz = (((int)(tnbZ * s+1))>>1)<<1;
		// Keep it in the borders
		if (tcenterx<0) tcenterx = 0;
		if (tcenterx>=tnbX) tcenterx = tnbX-1;
		if (tcenterz<0) tcenterz = 0;
		if (tcenterz>=tnbX) tcenterz = tnbZ-1;
		//TODO find something to get this right. (If the worldscale changes this isn't correct anymore)
		Real elev = getElevation(tcenterx, tcenterz, max(maxlevel,2))*mScaleHeight;
		Real sqdist = rpiq.y - elev;
		if ((sqdist < 0)&&(sqdist > -mOffset/(Real)10)){
			sqdist = (double)getLevelQuadSize(nbOfLevels-1)*(double)REGIONSIZE / clipRegion / 2.0;
		}

		i=minlevel-1;
		if (i<0) i=0;
		int start = i;
		bool foundlowest = false;
		int nbOfUpdates = 0;
		bool foundMax = false;

		// If there is an updatebudget, stay under it
		while((!foundMax)
#ifdef MAXUPDATES
			&&(nbOfUpdates<MAXUPDATES)
#endif
			)
		{
			// We update next time, because we already have done all the work
			// till here, and it would be a waste to just trow it away.
			// (The complete vertexbuffer has to be rewritten)
			if (i>= (int)clipRegions.size()){
				needRefinement=true;
				foundMax = true;
				maxlevel = i-1;
			} else {
				int reallevel = i;
				int nbX = getLevelSize(reallevel);
				int nbZ = getLevelSize(reallevel);
				Real sizeX = getLevelQuadSize(reallevel);
				Real sizeZ = getLevelQuadSize(reallevel);
				int centerx = (((int)(nbX * t+1))>>1)<<1;
				int centerz = (((int)(nbZ * s+1))>>1)<<1;

				// Is it too big, so we will have to use a higher detail region

				//Calculate the translation of the camera in number of squares
				int movex = (int)(centerx-clipRegions[i]->centerx);
				int movez = (int)(centerz-clipRegions[i]->centerz);

				//Translate the region
				int movedx = clipRegions[i]->move(movex,E);
				int movedz = clipRegions[i]->move(movez,S);

				if ((movedx < REGIONSIZE+1)&&(movedz < REGIONSIZE+1)){
					nbOfUpdates+=movedx*(REGIONSIZE+1)+movedz*(REGIONSIZE+1-movedx);
				} else {
					nbOfUpdates+=REGIONSURF;
				}

				if (i>0){
					clipRegions[i-1]->setInnerUpperLeftQuad(Vector3ix(clipRegions[i]->getUpperLeftQuad().x>>1,clipRegions[i]->getUpperLeftQuad().y>>1,i-1));
					clipRegions[i-1]->setInnerLowerRightQuad(Vector3ix(clipRegions[i]->getLowerRightQuad().x>>1,clipRegions[i]->getLowerRightQuad().y>>1,i-1));
				}
#ifdef MAXUPDATES
				if (nbOfUpdates>MAXUPDATES){
					// We reached the updatebudget. Close the patch we found so it
					// won't be rendered as a hole. (Because the more detailed
					// patches won't be rendered
					clipRegions[i]->closeHole();
				}
#endif

				if (TOOBIG)
				{
					//Yes, we will have to leave a hole for the higher detail region

					if ((i > 0)&&(!clipRegions[i]->isOnPatch())) {
						maxlevel = i-1;
						if (minlevel > maxlevel) minlevel = i-1;
						clipRegions[i-1]->closeHole();
						i=nbOfLevels;
						foundMax = true;
					} else {
						if (i>=minlevel) {
							if ((i==0)||(clipRegions[i-1]->isTooBig())){
								minlevel = i;
								foundlowest = true;
							} else {
								if (i>0){
									if (i-1==start){
										minlevel = i-1;
										i-=3;
										if (i<-1) i=-1;
										start--;
									}
								}
							}
						}
					}
				} else {
					foundMax = true;
					//No, this is the highest needed lod.
					if ((i > 0)&&(!clipRegions[i]->isOnPatch())) {
						maxlevel = i-1;
						if (minlevel > maxlevel) minlevel = i-1;
						clipRegions[i-1]->closeHole();
						i=nbOfLevels;
					} else {

						clipRegions[i]->closeHole();
						maxlevel = i;
						//Is this the same maximum level region as previous frame?
						if ((i!=0)&&(i==start)){
							minlevel = i;
							i-=2;
							if (i<-1) i = -1;
							start--;
							foundMax = false;
						} else if ((i==0)||(clipRegions[i-1]->isTooBig())){
							minlevel=i;
						} else {
							if (minlevel==i){
								minlevel = i-1;
							}
						}
					}
				}


				if ((!TOOBIG)&&(i >= minlevel))
				{
					i=nbOfLevels;
				}

				i++;
			}
		}
		return needRefinement;
	}

	void ClipmapPatch::createRegions(HardwareVertexBufferSharedPtr posVertexBuffer){
		int i;

		Vector3 rpiq = getRelativePointInSphere(&campos);
		Real t = rpiq.x;
		Real sqdist = sqrt(rpiq.y);
		Real s = rpiq.z;

		i=0;
		// check for every level
		minlevel=i;
		maxlevel=-1;

		while((i<nbOfLevels)&&(maxlevel==-1)){
			int reallevel = i;
			// Get the number of quads in this level
			int nbX = getLevelSize(reallevel);
			int nbZ = getLevelSize(reallevel);

			int centerx = (((int)(nbX * t+1))>>1)<<1;
			int centerz = (((int)(nbZ * s+1))>>1)<<1;

			// The corners of the patch
			int leftx = centerx-(REGIONSIZE/2);
			int topz = centerz-(REGIONSIZE/2);
			int rightx = centerx+(REGIONSIZE/2)-1;
			int bottomz = centerz+(REGIONSIZE/2)-1;

			if (leftx<0) leftx=0;
			if (leftx>=getLevelSize(reallevel)) leftx=getLevelSize(reallevel)-1;
			if (rightx<0) rightx=-1;
			if (rightx>=getLevelSize(reallevel)) rightx=getLevelSize(reallevel)-1;
			if (topz<0) topz=0;
			if (topz>=getLevelSize(reallevel)) topz=getLevelSize(reallevel)-1;
			if (bottomz<0) bottomz=-1;
			if (bottomz>=getLevelSize(reallevel)) bottomz=getLevelSize(reallevel)-1;

			int pleftx = leftx>>1;
			int ptopz = topz>>1;
			int prightx = rightx>>1;
			int pbottomz = bottomz>>1;

			if ((i>0)){
				clipRegions[i-1]->setInnerUpperLeftQuad(Vector3ix(pleftx,ptopz,reallevel-1));
				clipRegions[i-1]->setInnerLowerRightQuad(Vector3ix(prightx,pbottomz,reallevel-1));
			}


			// Is it too big, so we will have to use a higher detail region and
			// is this not the maximum detail we have available?
			if (TOOBIG)
			{

				if ((i>0)&&(clipRegions[i-1]->isTooBig())){
					minlevel=i;
				}

			} else {
				//No, this is as fine as we need

				maxlevel = i;
			}

			ClipmapRegion* region;
			if (i>0){
				region = clipRegions[i-1];
			} else {
				region = 0;
			}

			ClipmapRegion* const newRegion = new ClipmapRegion(Vector3ix(leftx,topz,reallevel),
				Vector3ix(rightx,bottomz,reallevel),
				Vector3ix(centerx,centerz,reallevel),
				Vector3ix(centerx-1,centerz-1,reallevel),
				mGeometryReader,
				posVertexBuffer,
				region,
				mMap,
				reallevel*REGIONSURF);
			clipRegions.push_back(newRegion);
			clipRegions[i]->centerx = centerx;
			clipRegions[i]->centerz = centerz;

			i++;			
		}
		if ((i==nbOfLevels)&&(maxlevel==-1)){
			maxlevel=nbOfLevels-1;
			if ((nbOfLevels>1)&&(clipRegions[nbOfLevels-2]->isTooBig())){
				minlevel=nbOfLevels-1;
			}
		}
		//Initialize the other levels to some default value
		while (i<nbOfLevels){

			int reallevel = i;
			// Get the number of quads in this level
			int nbX = getLevelSize(reallevel);
			int nbZ = getLevelSize(reallevel);

			// Clean up the region if necessary

			int centerx = (((int)(nbX * t+1))>>1)<<1;
			int centerz = (((int)(nbZ * s+1))>>1)<<1;

			int ncenterx = ((((int)(2*nbX * t+1))>>1));
			int ncenterz = ((((int)(2*nbZ * s+1))>>1));

			// The corners of the patch
			int leftx = centerx-(REGIONSIZE/2);
			int topz = centerz-(REGIONSIZE/2);
			int rightx = centerx+(REGIONSIZE/2)-1;
			int bottomz = centerz+(REGIONSIZE/2)-1;

			if (leftx<0) leftx=0;
			if (leftx>=getLevelSize(reallevel)) leftx=getLevelSize(reallevel)-1;
			if (rightx<0) rightx=-1;
			if (rightx>=getLevelSize(reallevel)) rightx=getLevelSize(reallevel)-1;
			if (topz<0) topz=0;
			if (topz>=getLevelSize(reallevel)) topz=getLevelSize(reallevel)-1;
			if (bottomz<0) bottomz=-1;
			if (bottomz>=getLevelSize(reallevel)) bottomz=getLevelSize(reallevel)-1;

			int pleftx = leftx>>1;
			int ptopz = topz>>1;
			int prightx = rightx>>1;
			int pbottomz = bottomz>>1;

			ClipmapRegion* region;
			if (i>0){
				region = clipRegions[i-1];
			} else {
				region = 0;
			}
			clipRegions.push_back(new ClipmapRegion(Vector3ix(leftx,topz,reallevel),
				Vector3ix(rightx,bottomz,reallevel),
				Vector3ix(centerx,centerz,reallevel),
				Vector3ix(centerx-1,centerz-1,reallevel),
				mGeometryReader,
				posVertexBuffer,
				region,
				mMap,
				reallevel*REGIONSURF));
			clipRegions[i]->centerx = centerx;
			clipRegions[i]->centerz = centerz;

			i++;			
		}
		// If we want to reserve some extra memory so we don't have to re-initialize
		// the vertexbuffers very soon, we reserve it here.
		while (i<nbOfLevels+NBEXTRALVLS){
			createRegion(i,posVertexBuffer);
			i++;
		}
	}
	void ClipmapPatch::updateVertices(){
		int i;
		// Update the vertices from the found range of levels of detail to display
		for (i=minlevel;i<=maxlevel;i++){
			//		std::cout << "Updating vertices of level " << i << std::endl;
			clipRegions[i]->updateVertices();
			//		std::cout << "done" << std::endl;
		}
	}

	void ClipmapPatch::createRegion(int level, HardwareVertexBufferSharedPtr posVertexBuffer){
		int nbX = getLevelSize(level);
		int nbZ = getLevelSize(level);

		Real t=0.5;
		Real s=0.5;

		int centerx = (((int)(nbX * t+1))>>1)<<1;
		int centerz = (((int)(nbZ * s+1))>>1)<<1;

		// The corners of the patch
		int leftx = centerx-(REGIONSIZE/2);
		int topz = centerz-(REGIONSIZE/2);
		int rightx = centerx+(REGIONSIZE/2)-1;
		int bottomz = centerz+(REGIONSIZE/2)-1;

		if (leftx<0) leftx=0;
		if (leftx>=getLevelSize(level)) leftx=getLevelSize(level);
		if (rightx<0) rightx=-1;
		if (rightx>=getLevelSize(level)) rightx=getLevelSize(level)-1;
		if (topz<0) topz=0;
		if (topz>=getLevelSize(level)) topz=getLevelSize(level);
		if (bottomz<0) bottomz=-1;
		if (bottomz>=getLevelSize(level)) bottomz=getLevelSize(level)-1;

		int pleftx = leftx>>1;
		int ptopz = topz>>1;
		int prightx = rightx>>1;
		int pbottomz = bottomz>>1;

		if ((level>0)){
			clipRegions[level-1]->setInnerUpperLeftQuad(Vector3ix(pleftx,ptopz,level-1));
			clipRegions[level-1]->setInnerLowerRightQuad(Vector3ix(prightx,pbottomz,level-1));
		}

		ClipmapRegion* region;
		if (level>0){
			region = clipRegions[level-1];
		} else {
			region = 0;
		}

		clipRegions.push_back(new ClipmapRegion(Vector3ix(leftx,topz,level),
			Vector3ix(rightx,bottomz,level),
			Vector3ix(centerx,centerz,level),
			Vector3ix(centerx-1,centerz-1,level),
			mGeometryReader,
			posVertexBuffer,
			clipRegions[level-1],
			mMap,
			level*REGIONSURF));
		clipRegions[level]->centerx = centerx;
		clipRegions[level]->centerz = centerz;

	}

	//----------------------------------------------------------------------------

	Vector3 ClipmapPatch::getVector(Real x, Real y, Real z){
		return getVector(Vector3(x,y,z));
	}

	Vector3 ClipmapPatch::getVector(Vector3 point){
		Real x,y,z;
		Real minradius = mScaleHeight+mOffset;

		// Find the biggest axis-alignd vector. This will decide above which side 
		// of the cube the point will be
		if ((abs(point.x)<=abs(point.y))&&(abs(point.z)<=abs(point.y))){
			//x z y
			//z x y
			if (point.y>0){
				x = (point.x/point.y+1)/2;
				z = (point.z/point.y+1)/2;
			} else {
				if (point.y!=0) {
					if (point.x<0){
						x = -1-(point.x/(-point.y)+1)/2;
					} else {
						x = 2+((-point.x)/(-point.y)+1)/2;
					}
					if (point.z<0){
						z = -1-(point.z/(-point.y)+1)/2;
					} else {
						z = 2+((-point.z)/(-point.y)+1)/2;
					}
				} else {
					x=0.5;
					z=0.5;
				}
			}
		} else if ((abs(point.x)<=abs(point.z))&&(abs(point.y)<=abs(point.z))){
			//x y z
			//y x z
			if (point.z<0){
				z = ((point.y)/(-point.z)+1)/2-1;
			} else {
				if (point.z!=0) {
					z = 1+((-point.y)/(point.z)+1)/2;
				} else {
					z = 0.5;
				}
			}
			if (point.z>0){
				x = (point.x/point.z+1)/2;
			} else {
				if (point.z!=0) {
					x = (point.x/(-point.z)+1)/2;
				} else {
					x=0.5;
				}
			}
		} else {
			//y z x
			//z y x
			if (point.x<0){
				x = -((-point.y)/(-point.x)+1)/2;
			} else {
				x = 1+((-point.y)/(point.x)+1)/2;
			}
			if (point.x>0){
				z = (point.z/point.x+1)/2;
			} else {
				if (point.x!=0) {
					z = (point.z/(-point.x)+1)/2;
				} else {
					z=0.5;
				}
			}
		}

		y = point.length()-minradius;

		return Vector3(x,y,z);
	}

	Vector3 ClipmapPatch::getRelativePointInSphere(Vector3* point){
		Vector3 nul = Vector3(0,0,0);
		Vector3 diff = *(point)-nul;
		Vector3 res = *(point)-nul;

		// Rotate the axis that the calculations can be done for 1 basepatch
		switch (mMap) {
			case 0 :
				//x -> z
				//y -> -x
				//z -> -y
				return getVector(diff.z,-diff.x,-diff.y);
				break;
			case 1 :
				//x -> x
				//y -> z
				//z -> -y
				return getVector(diff.x,diff.z,-diff.y);
				break;
			case 2 :
				//x -> -z
				//y -> x
				//z -> -y
				return getVector(-diff.z,diff.x,-diff.y);
				break;
			case 3 :
				//x -> -x
				//y -> -z
				//z -> -y
				return getVector(-diff.x,-diff.z,-diff.y);
				break;
			case 4 :
				return getVector(diff.x,diff.y,diff.z);
				break;
			case 5 :
				return getVector(-diff.x,-diff.y,diff.z);
				break;
		}
		diff.y = abs(diff.y);

		return diff;
	}

	int ClipmapPatch::getLevelSize(int level){
		return mGeometryReader->getLevelSize(level)-1;
	}

	int ClipmapPatch::getDepth(){
		return mGeometryReader->getGeometryDepth();
	}

	Real ClipmapPatch::getLevelQuadSize(int level){
		return mGeometryReader->getLevelQuadSize(level);
	}

	Real ClipmapPatch::getElevation(int x, int y, int level){
		return mGeometryReader->getHeight(x, y, level, mMap);
	}

	void ClipmapPatch::setOtherPatches(ClipmapPatch** neighbour){
		otherPatches=neighbour;
	}

	void ClipmapPatch::setOtherRegions(int index, std::vector<ClipmapRegion*> *regions){
		for (size_t i=0;i<clipRegions.size();i++){
			clipRegions[i]->setOtherRegions(index,regions);
		}
	}

}
