#ifndef LANDSCAPECREATOR_HXX
#define LANDSCAPECREATOR_HXX

#include <fstream>

#include "AbstractLandscape.hxx"
#include "../Shared/utils.h"


/**
   Creates a landscape according to a height field, which is computed with the
   help of a fractal. All parameters are encapsulated in a given subclass of
   AbstractLandscape.
*/
class LandscapeCreator
{
 public:
    LandscapeCreator(AbstractLandscape *lsc, std::string outFileName)
	: _landscape( lsc ), _outFileName( outFileName ), _noOfTriangles(0),
	  _maxX( _landscape->getMaxX() ), _maxY( _landscape->getMaxY() )	  
    {
	// create the height field
	_heights = new float[index(_maxX-1,_maxY-1)+1];
    }

    ~LandscapeCreator() {
	delete[] _heights;
    }

    /** main method: creates the landscape */
    void createLandscape() {
	_outFile.open(_outFileName.c_str());
	
	writeHeader();
	computeHeightField();
	writeBody();
	
	_outFile.close();
    }

 private:
    /** pointer to the subclass of AbstractLandscape that describes the desired landscape */
    AbstractLandscape * _landscape;

    /** the name of the nff file */
    std::string _outFileName;

    /** the output file stream of the nff file */
    std::ofstream _outFile;

    /** the number of written triangles (just for statistical purposes) */
    int _noOfTriangles;

    /** size of the height field in x-direction */
    int _maxX;
    
    /** size of the height field in y-direction */
    int _maxY;

    /** the height field */
    float * _heights;

    /** combines two indices to obtain one index of the height field */
    int index(int x, int y) const {return x * _maxY + y;}

    /** writes the header of the nff file (camera, light, background) */
    void writeHeader() {
	std::cout << "writing header" << std::endl;
	    
	_outFile << "b " << _landscape->getBgColor() << std::endl;
    
	_outFile << "v" << std::endl << "from " << _landscape->getCameraFrom() << std::endl
		 << "at " << _landscape->getCameraAt() << std::endl
		 << "up " << _landscape->getCameraUp() << std::endl
		 << "angle 60" << std::endl << "hither 0" << std::endl
		 << "resolution "
		 << (int)_landscape->getResolution().x << " "
		 << (int)_landscape->getResolution().y << std::endl << std::endl;

	_outFile << "dl " << _landscape->getSunDirection() << std::endl;
    }

    /** computes the height field */
    void computeHeightField() {
	std::cout << "computing height field..." << std::endl;
	
	Vec3f domain = _landscape->getDomain();   // the domain of the fractal function
	Vec3f stretch = _landscape->getStretch(); // stretch factors
	int sealevel = _landscape->getSealevel(); // the sea level (everything below is water)
	
	Percent progress(_maxX);
	for (int x=0; x<_maxX; x++) {
	    progress.printStatus(x);
	    for (int y=0; y<_maxY; y++) {
		// call of the fractal function
		_heights[index(x,y)] = _landscape->getHeight(
		    Vec3f(x/(float)_maxX*domain.x,
			  y/(float)_maxY*domain.y,
			  domain.z));
		    
		if (_heights[index(x,y)] < sealevel) {
		    // update the extension of the water
		    _landscape->getBumpMap()->extend( Vec3f(x*stretch.x, y*stretch.y, sealevel) );
		}
	    }
	}
	std::cout << std::endl;    
    }

    void writeBody() {
	std::cout << "writing triangles..." << std::endl;

	Vec3f stretch = _landscape->getStretch();
	int skipFactor = _landscape->getSkipFactor();
	
	Percent progress(_maxY);
	int oldRatio = 0;
	int currentRatio;
	int currentSkip = 1;
	bool skipChanged = false;
	for (int y=0; y<_maxY-currentSkip; y+=currentSkip) {
	    // check if the already processed ratio is big enough to skip
	    // triangles for the future because they are far away	    
	    skipChanged = false;
	    currentRatio = (int)((y/(float)_maxY)/_landscape->getSkipRatio());
	    if (currentRatio != oldRatio) {
		// update oldRatio and currentSkip
		oldRatio = currentRatio;
		currentSkip *= skipFactor;
		skipChanged = true;
	    }
	    progress.printStatus(y);
	    for (int x=0; x<_maxX-currentSkip; x+=currentSkip) {
		// four corners of a square
		Vec3f a(x,y,_heights[index(x,y)]);
		Vec3f b(x,y+currentSkip,_heights[index(x,y+currentSkip)]);
		Vec3f c(x+currentSkip,y,_heights[index(x+currentSkip,y)]);
		Vec3f d(x+currentSkip,y+currentSkip,_heights[index(x+currentSkip,y+currentSkip)]);

		// apply stretch factors
		a = product(a,stretch);
		b = product(b,stretch);
		c = product(c,stretch);
		d = product(d,stretch);

		if ( ! skipChanged ) {
		    // represent square by two triangles
		    writeTriangle(a,b,c);
		    writeTriangle(b,c,d);
		}
		else {
		    // interface between sparse and dense areas
		    // use additional point to avoid gaps
		    Vec3f e(x+currentSkip/skipFactor,y,_heights[index(x+currentSkip/skipFactor,y)]);
		    e = product(e,stretch);
		    writeTriangle(a,b,e);
		    writeTriangle(e,b,c);
		    writeTriangle(b,c,d);
		}
	    }	
	}
	std::cout << std::endl;

	std::cout << _noOfTriangles << " triangles written to " << _outFileName << std::endl;
    }

    
    void writeTriangle(Vec3f a, Vec3f b, Vec3f c) {
	_noOfTriangles++;
	int sealevel = _landscape->getSealevel();
    
	if ((a.z < sealevel) || (b.z < sealevel) || (c.z < sealevel)) {
	    // W A T E R
	    // nothing can be below the sealevel
	    a.z = std::max(a.z, (float)sealevel);
	    b.z = std::max(b.z, (float)sealevel);
	    c.z = std::max(c.z, (float)sealevel);

	    // write water shader
	    _outFile << "f " << _landscape->getColorMap()->getWaterColor() << " "
		     << _landscape->getKdWater() << " " << _landscape->getKsWater()
		     << " 0 0 0" << std::endl;

	    // write water triangle
	    _outFile << "pn 3" << std::endl
		     << _landscape->getBumpMap()->getNewNormal(a,b,c) << std::endl
		     << a << std::endl << b << std::endl << c << std::endl;
	}
	else {
	    // E A R T H
	    // write earth shader
	    _outFile << "fph " << _landscape->getColorMap()->getColor(a,b,c) << " "
		     << _landscape->getKdEarth() << " " << _landscape->getKsEarth()
		     << " 0 0 0" << std::endl;

	    // write earth triangle
	    _outFile << "p 3" << std::endl
		     << a << std::endl << b << std::endl << c << std::endl;
	}
    }
    
};

#endif // LANDSCAPECREATOR_HXX
