#include <i3d/image3d.h>
#include <i3d/transform.h>
#include <i3d/morphology.h>

#include <stdlib.h> //for rnd generator seeding
#include <time.h>
#include <unistd.h>
#include <string.h>

#include "params.h"
#include "graphics.h"
#include "TriangleMesh.h"

//create some shared objects...
ParamsClass params;
void ParamsSetup(void);
//
ActiveMesh mesh;

extern std::string path;
extern int ID;
bool LoadNewMesh(const char* path,const int ID,const int fileNo,const bool keepTrajectories=false);

int main(int argc,char **argv)
{
	if (argc == 1)
	{
		//I'm executed with no params, provide help
		std::cout << "ADD SOME HEADER INFORMATION HERE\n\n";
		std::cout << "params:  cell_surf_mesh  filo1_description [filoN_description]\n\n";
		std::cout << "The program will try to load object files (cell body surface mesh -- the\n"
		             "first parameter) and (filopodia skeleton definitions -- the second till\n"
		             "the last parameter(s)) and display them with OpenGL. Note, at least one\n"
		             "filopodium mesh has to be supplied.\n\n";
		std::cout << "or, params:  a  path_to_VTKs dataset_ID\n";
		std::cout << "The program will try to load VTK objects one by one at time point 0.\n"
		             "In the GUI window, one should use keys 'n' and 'p' to force the program\n"
		             "to reload the object at next and previous time point, respectively.\n"
		             "Note the first parameter 'a' to push the program to run in this mode.\n\n";
		std::cout << "or, params:  mesh.stl\n";
		std::cout << "The program will try to load and display the given STL mesh.\n";
		return(-1);
	}

	//init the situation...
	ParamsSetup();

	if (argc == 2)
	{
		int retval=mesh.ImportSTL(argv[1]);
		if (retval) 
		{
			std::cout << "some error reading STL: " << retval << "\n";
			return(5);
		}
		std::cout << "vertices   #: " << mesh.Pos.size() << "\n";
		std::cout << "triangles  #: " << mesh.ID.size()/3 << "\n";
		std::cout << "normals    #: " << mesh.norm.size() << "\n";
	}
	else
	{
		if (argv[1][0] == 'a' && argv[1][1] == (char)0)
		{
			//the first parameter is 'a', we will try to read all data available
			path=argv[2];

			//get dataset ID
			ID=atoi(argv[3]);

			//load the complete mesh data
			LoadNewMesh(path.c_str(),ID,0);
		}
		else
		{
			//the first parameter is NOT 'a', we read the remaining params and display them
			//read cell body
			std::cout << "reading cell body: " << argv[1] << "\n";
			int retval=mesh.ImportVTK(argv[1]);
			if (retval) 
			{
				std::cout << "some error reading cell body: " << retval << "\n";
				return(1);
			}

			//read filopodia files
			for (int i=0; i < argc-2; ++i)
			{
				std::cout << "reading filopodium: " << argv[2+i] << "\n";
				retval=mesh.ImportVTK_Ftree(argv[2+i],999999);
				if (retval) 
				{
					std::cout << "some error reading filopodium: " << retval << "\n";
					return(2);
				}
			}
		}

		std::cout << "vertices   #: " << mesh.Pos.size() << "\n";
		std::cout << "triangles  #: " << mesh.ID.size()/3 << "\n";
		std::cout << "normals    #: " << mesh.norm.size() << "\n";

		//std::cout << "filopodia #: " << mesh.Ftree.size() << "\n";
		for (size_t f=0; f < mesh.Ftree.size(); ++f)
		{
			if (mesh.Ftree[f].Pos.size() > 0)
			{
				std::cout << f << ": vertices  #: " << mesh.Ftree[f].Pos.size() << "\n";
				std::cout << f << ": triangles #: " << mesh.Ftree[f].ID.size()/3 << "\n";
				std::cout << f << ": normals   #: " << mesh.Ftree[f].norm.size() << "\n";
			}
		}
	}

	//position the mesh with its centre aligned with the scene centre
	mesh.CenterMesh();
	mesh.TranslateMesh(params.sceneCentre - (mesh.meshPlaygroundSize/2.0f - mesh.meshShift));

	//display all the meshes
	initializeGL();
	loopGL();
	closeGL();

	return(0);
}


void ParamsSetup(void)
{
	//set up the environment
   params.sceneOffset=Vector3d<float>(0.f);
   params.sceneSize=Vector3d<float>(27.5f,27.5f,22.0f); //for sample cell

	params.sceneCentre=params.sceneSize;
	params.sceneCentre/=2.0f;
	params.sceneCentre+=params.sceneOffset;

	params.sceneOuterBorder=Vector3d<float>(2.f);
	params.sceneBorderColour.r=0.7f;
	params.sceneBorderColour.g=0.7f;
	params.sceneBorderColour.b=0.0f;

	params.inputCellsFilename="cells/cell%d.txt";
	params.numberOfAgents=2;

	params.friendshipDuration=10.f;
	params.maxCellSpeed=0.20f;

	//adapted for cell cycle length of 24 hours
	params.cellCycleLength=14.5*60; //[min]

	params.currTime=0.0f; //all three are in units of minutes
	params.incrTime=0.0f;
	params.stopTime=50.0f;

	params.imgSizeX=500; //pixels
	params.imgSizeY=500;
	params.imgResX=1.0f; //pixels per micrometer
	params.imgResY=1.0f;

	params.imgOutlineFilename="Outline%05d.tif";
	params.imgPhantomFilename="Phantom%05d.tif";
	params.imgMaskFilename="Mask%05d.tif";
	params.imgFluoFilename="FluoImg%05d.tif";
	params.imgPhCFilename="PhCImg%05d.tif";
	params.tracksFilename="tracks.txt";


	//re-seed the standard rnd generator
	//(which is used for Perlin texture)
	unsigned int seed=(1 << 20) * (unsigned int)time(NULL) * (unsigned int)getpid();
	srand(seed);
}


bool LoadNewMesh(const char* path,const int ID,const int fileNo,const bool keepTrajectories)
{
	char fn[1024];

	//first, obtain a fresh volumetric mesh of the cell body
	sprintf(fn,"%s/ID%d/ID%d_t%03d_nucleusSurf.vtk",path,ID,ID,fileNo);
	std::cout << "reading cell body: " << fn << "\n";

	//keep the volumetric configuration of the first load
	int retval=mesh.ImportVTK(fn);
	if (retval) 
	{
		//report errors except for "cannot open file"
		if (retval > 1) 
			std::cout << "some error reading cell body: " << retval << "\n";
		return(false);
	}

	//start loading filopodia objects
	mesh.DiscardAllFilopodiaObjects();
	int filoNo=0;

	do
	{
		++filoNo;
		sprintf(fn,"%s/ID%d/ID%d_t%03d_fTree%02d.vtk",path,ID,ID,fileNo,filoNo);

		retval=mesh.ImportVTK_Ftree(fn,999999);
		if (retval != 1) std::cout << "reading filopodium: " << fn << "\n";
	} while (retval < 2 && filoNo < 100);
	//NB: retval == 0 when read successfully; == 1 when file open error

	//report errors except for "cannot open file"
	if (retval > 1) 
	{
		std::cout << "some error reading filopodium: " << retval << "\n";
		return(false);
	}

	//remove last filopodium mesh (which is usually empty...)
	mesh.DiscardLastEmptyFilopodiumObject();

	//to get rid of unused parameter warning...
	retval+=(keepTrajectories)? 1 : 0;

	std::cout << "loaded mesh #" << fileNo << "\n";

	return(true);
}
