#include "ClipmapPatchManager.h"

namespace Ogre
{
	unsigned int ClipmapPatchManager::mGenNameCount = 0;

	ClipmapPatchManager::ClipmapPatchManager()
	{
		// generate name for this instance
		mName = "ClipmapPatchManager" + toString(mGenNameCount++);
		mInit = false;
		needRefinement = false;
		clipmapPatch = new ClipmapPatch*[CUBEB-CUBEA];
	}
	ClipmapPatchManager::~ClipmapPatchManager()
	{
		posVertexBuffer.setNull(); 
		indexBuffer.setNull();
		int i;
		for (i=CUBEA;i<CUBEB;i++){
			delete clipmapPatch[i];
		}
	}
	bool ClipmapPatchManager::initialise(SceneNode *sceneRoot, SeperateCubemappedPlanetReader* geometryReader){
		int i;
		for (i=CUBEA;i<CUBEB;i++){
			clipmapPatch[i] = new ClipmapPatch();
			clipmapPatch[i]->initialise(sceneRoot, geometryReader, i);
		}
		mIndexData = 0;
		mVertexData = 0;
		mGeometryReader = geometryReader;
		mSceneRoot  = sceneRoot;
		mScale = geometryReader->getScale();
		mWorld = geometryReader->getOffset();
		Real mScaleHeight = geometryReader->getHeightScale();
		Real mOffset = geometryReader->getHeightOffset();
		clipRegion = geometryReader->getClipRegion();
		needRefinement = false;

		nbOfLevels = getDepth();

		Real maxradius = 256*mScaleHeight+mOffset;
		mBounds.setExtents(mWorld.x - maxradius * mScale.x,
			mWorld.y - maxradius * mScale.y,
			mWorld.z - maxradius * mScale.z,
			mWorld.x + maxradius * mScale.x,
			mWorld.y + maxradius * mScale.y,	    // wrong
			mWorld.z + maxradius * mScale.z);
		std::cout << mWorld.x - maxradius * mScale.x << "," <<
			mWorld.y - maxradius * mScale.y << "," <<
			mWorld.z - maxradius * mScale.z << "-" <<
			mWorld.x + maxradius * mScale.x << "," <<
			mWorld.y + maxradius * mScale.y <<  "," <<
			mWorld.z + maxradius * mScale.z << std::endl;

#if USE_TEXTURES
		setMaterial(geometryReader->getMaterial(),sceneRoot);
#else
		mMaterial = reinterpret_cast<Material*>
			(MaterialManager::getSingleton().getByName("BaseWhite"));
#endif
		createLandscape();
		generateRenderQueue();
		return true;
	}


	void ClipmapPatchManager::createLandscape(){
		int numVertices;
		int i;
		int count=0;
		for (i=CUBEA;i<CUBEB;i++){
			count+=clipmapPatch[i]->clipRegions.size();
			if (clipmapPatch[i]->needRefinement){
				count++;
			}
		}
		numVertices=max((getDepth()+NBEXTRALVLS)*REGIONSURF*(CUBEB-CUBEA),count*REGIONSURF);

		if (mVertexData!=NULL){
			delete mVertexData;
		}
		mVertexData = new VertexData();

		mVertexData->vertexStart = 0;
		mVertexData->vertexCount = numVertices;

		VertexDeclaration* vdecl = mVertexData->vertexDeclaration; 
		VertexBufferBinding* vbind = mVertexData->vertexBufferBinding;

		size_t offset = 0;
		vdecl->addElement(0, offset, VET_FLOAT3, VES_POSITION);
		offset += VertexElement::getTypeSize(VET_FLOAT3);

#if USE_NORMALS
		// normals
		vdecl->addElement(0, offset, VET_FLOAT3, VES_NORMAL);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
#endif

#if USE_COLOURS
		// update colours
		vdecl->addElement(0, offset, VET_COLOUR, VES_DIFFUSE);
		offset += VertexElement::getTypeSize(VET_COLOUR);
#endif
#if USE_TEXTURES
		// texture coord sets
		vdecl->addElement(0, offset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 0);
		offset += VertexElement::getTypeSize(VET_FLOAT2);
#endif


		// Prepare buffer for positions
		posVertexBuffer = 
			HardwareBufferManager::getSingleton().createVertexBuffer( 
			offset, 
			numVertices, 
			HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
		vbind->setBinding(0, posVertexBuffer);

	}

	void ClipmapPatchManager::updateIndices(bool force){
		int mIndexCount=0, tot=0;
		int maxlevel, minlevel;
		int i,j;
#define DRWLVL 4
		for (j=CUBEA;j<CUBEB;j++){
			minlevel=clipmapPatch[j]->getMinLevel();
			maxlevel=clipmapPatch[j]->getMaxLevel();
			if (maxlevel < minlevel) minlevel = maxlevel;
			for (i=maxlevel;i>=minlevel;i--){
				mIndexCount+=clipmapPatch[j]->clipRegions[i]->getIndexCount();
			}
		}

		if (mIndexData!=0){
			delete mIndexData;
		}
		mIndexData = new IndexData();
		mIndexData->indexStart = 0;
		mIndexData->indexCount = mIndexCount;    

		indexBuffer = 
			HardwareBufferManager::getSingleton().createIndexBuffer(
#if USE_16BIT_INDICES
			HardwareIndexBuffer::IT_16BIT, 
#else
			HardwareIndexBuffer::IT_32BIT, 
#endif
			mIndexCount, 
			HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);			
		mIndexData->indexBuffer = indexBuffer;


		for (j=CUBEA;j<CUBEB;j++){
			minlevel=clipmapPatch[j]->getMinLevel();
			maxlevel=clipmapPatch[j]->getMaxLevel();
			for (i=maxlevel;i>=minlevel;i--){
				clipmapPatch[j]->clipRegions[i]->setIndexBuffer(indexBuffer);
			}

			if (force){
				for (size_t k=0;k < clipmapPatch[j]->clipRegions.size();k++){
					clipmapPatch[j]->clipRegions[i]->updateVertexBuffer();
				}
			}
			for (i=maxlevel;i>=minlevel;i--){
				tot+=clipmapPatch[j]->clipRegions[i]->updateIndexBuffer(tot);
			}
		}
	}

	void ClipmapPatchManager::generateRenderQueue(){
		int i,j;
		bool refined=false;
		if (needRefinement){
			createLandscape();
			int pos=0;
			for (i=CUBEA;i<CUBEB;i++){
				if (clipmapPatch[i]->needRefinement){
					clipmapPatch[i]->refinePatch(posVertexBuffer,pos);
				} else {
					clipmapPatch[i]->updateVertexBuffers(posVertexBuffer,pos);
				}
				clipmapPatch[i]->refreshAll();
				pos+=clipmapPatch[i]->clipRegions.size()*REGIONSURF;
			}
			refined = true;
			needRefinement = false;
		}
		for (i=CUBEA;i<CUBEB;i++){
			if (mInit){
				clipmapPatch[i]->updatePositions(campos);
				if (clipmapPatch[i]->needRefinement) needRefinement = true;
			} else {
				clipmapPatch[i]->createRegions(posVertexBuffer);
				clipmapPatch[i]->setOtherPatches(clipmapPatch);
				clipmapPatch[i]->updateVertexBuffers(posVertexBuffer, (i-CUBEA)*REGIONSURF*nbOfLevels);
			}
		}
		if (!mInit){
			for (i=CUBEA;i<CUBEB;i++){
				for (j=CUBEA;j<CUBEB;j++){
					clipmapPatch[i]->setOtherRegions(j,&clipmapPatch[j]->clipRegions);
				}
			}
		}
		for (i=CUBEA;i<CUBEB;i++){
			clipmapPatch[i]->updateVertices();
		}
		updateIndices(refined||!mInit);
		mInit =true;
	}

	bool ClipmapPatchManager::setMaterial(String materialName, SceneNode* mSceneRoot) {
#if USE_TEXTURES
		// get material
		mMaterial = MaterialManager::getSingleton().getByName("NaturePatchMaterial");
		if (!mMaterial.isNull()) 
			MaterialManager::getSingleton().unload("NaturePatchMaterial");
		mMaterial = MaterialManager::getSingleton().create("NaturePatchMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

		TextureUnitState *layer;
		layer = mMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(materialName, 0);

#endif

#if USE_NORMALS
		mMaterial->setLightingEnabled(true);
#endif

		mMaterial->load();

		return true;
	}

	void ClipmapPatchManager::_notifyCurrentCamera(Camera *cam) {
		pcampos = campos;
		campos = cam->getWorldPosition()+cam->getPosition();
	}


	void ClipmapPatchManager::getRenderOperation(RenderOperation& op)
	{

		assert( mInit && "Uninitialized" );

		op.useIndexes = true;
		op.operationType = RenderOperation::OT_TRIANGLE_LIST;

		op.vertexData = mVertexData;
		op.indexData = mIndexData;


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

	int ClipmapPatchManager::getLevelSize(short level){
		return mGeometryReader->getLevelSize(level);
	}

} // namespace Ogre
