#include <fstream>
#include <sstream>

#include "../molecule.h"
#include "importexport.h"

//----------------------------------------------------------------------
template <class T>
void ExportAllMaskVoxelCoordinates(const i3d::Image3d<T> &mask,
											  const T value,
											  const char* fileName)
{
	std::ofstream pointFile(fileName);
	if (!pointFile.is_open()) {
		std::ostringstream err;
		err << "Can't open the output file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}

	//time savers...
	const float xRes=mask.GetResolution().GetX();
	const float yRes=mask.GetResolution().GetY();
	const float zRes=mask.GetResolution().GetZ();
	const float xOff=mask.GetOffset().x;
	const float yOff=mask.GetOffset().y;
	const float zOff=mask.GetOffset().z;

	//working pointers
	const T* m=mask.GetFirstVoxelAddr();
	const T* mL=m+ mask.GetImageSize();

	size_t cnt=0;

	for (size_t i=0; (m != mL) && (pointFile.good()); ++m, ++i)
		if ( (*m > 0) && ((value == 0) || (value == *m)) )
		{
			//turn the current position-index into a position-xyzVector
			i3d::Vector3d<size_t> v=mask.GetPos(i);

			//turn v into an absolute micron coordinate
			const float X=((float)v.x+0.5f)/xRes + xOff;
			const float Y=((float)v.y+0.5f)/yRes + yOff;
			const float Z=((float)v.z+0.5f)/zRes + zOff;

			pointFile << X << " " << Y << " " << Z << "\n";
			++cnt;
		}
	if (!pointFile.good())
	{
		pointFile.close();
		std::ostringstream err;
		err << "Error while writing file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}
	pointFile.close();

	DEBUG_REPORT("Successfully exported " << cnt
					<< " voxels into " << fileName); 
}


template <class T>
void ExportGridMaskVoxelCoordinates(const i3d::Image3d<T> &mask,
											const T value,
											i3d::Vector3d<size_t> const &step,
											const char* fileName)
{
	std::ofstream pointFile(fileName);
	if (!pointFile.is_open()) {
		std::ostringstream err;
		err << "Can't open the output file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}

	//time savers...
	const float xRes=mask.GetResolution().GetX();
	const float yRes=mask.GetResolution().GetY();
	const float zRes=mask.GetResolution().GetZ();
	const float xOff=mask.GetOffset().x;
	const float yOff=mask.GetOffset().y;
	const float zOff=mask.GetOffset().z;

	size_t cnt=0;

	for (size_t z=0; (z < mask.GetSizeZ()) && (pointFile.good()); ++z)
	for (size_t y=0; (y < mask.GetSizeY()) && (pointFile.good()); ++y)
	for (size_t x=0; (x < mask.GetSizeX()) && (pointFile.good()); ++x)
	if ((x % step.x == 0) || (y % step.y == 0))
	{
		const T m=mask.GetVoxel(x,y,z);

		if ( (m > 0) && ((value == 0) || (value == m)) )
		{
			//turn current pixel position into an absolute micron coordinate
			//of the voxel centre
			const float X=((float)x+0.5f)/xRes + xOff;
			const float Y=((float)y+0.5f)/yRes + yOff;
			const float Z=((float)z+0.5f)/zRes + zOff;

			pointFile << X << " " << Y << " " << Z << "\n";
			++cnt;
		}
	}
	if (!pointFile.good())
	{
		pointFile.close();
		std::ostringstream err;
		err << "Error while writing file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}
	pointFile.close();

	DEBUG_REPORT("Successfully exported " << cnt
					<< " voxels into " << fileName); 
}


//----------------------------------------------------------------------
void inline ReadPointTypeCoordinate(i3d::Vector3d<float> const &data,
									  float &x, float &y, float &z)
{
	x=data.x;
	y=data.y;
	z=data.z;
}

void inline ReadPointTypeCoordinate(Molecule const &data,
									  float &x, float &y, float &z)
{
	x=data.pos.x;
	y=data.pos.y;
	z=data.pos.z;
}


void inline WritePointTypeCoordinate(float const &x, float const &y, float const &z,
										i3d::Vector3d<float> &data)
{
	data.x=x;
	data.y=y;
	data.z=z;
}

void inline WritePointTypeCoordinate(float const &x, float const &y, float const &z,
										Molecule &data)
{
	data.pos.x=x;
	data.pos.y=y;
	data.pos.z=z;
}


//----------------------------------------------------------------------
template <class T>
size_t ExportPointList(std::vector< T /**/> const &PList,
							  const size_t maxCnt,
							  const char* fileName)
{
	std::ofstream pointFile(fileName);
	if (!pointFile.is_open()) {
		std::ostringstream err;
		err << "Can't open the output file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}

	typename std::vector< T /**/ >::const_iterator cIter=PList.begin();
	size_t counter=0;

	while ( (pointFile.good()) && (counter < maxCnt)
			&& (cIter != PList.end()) )
	{
		float x,y,z;
		ReadPointTypeCoordinate(*cIter, x,y,z);

		pointFile << x << " " << y << " " << z << "\n";
		++cIter;
		++counter;
	}
	if (!pointFile.good())
	{
		pointFile.close();
		std::ostringstream err;
		err << "Error while writing file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}
	pointFile.close();

	DEBUG_REPORT("Successfully exported " << counter
					<< " points into " << fileName); 

	return (counter);
}

//----------------------------------------------------------------------
template <class T>
size_t ImportAndResetPointList(std::vector< T /**/> &PList,
										 i3d::Vector3d<float>* Pcentre,
										 const char* fileName,
										 const size_t estCount)
{
	std::ifstream pointFile(fileName);
	if (!pointFile.is_open()) {
		std::ostringstream err;
		err << "Can't open the input file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}

	PList.clear();
	//if a reservation hint was supplied, use it...
	if (estCount > 0) PList.reserve(estCount);

	//centre point
	double cx=0., cy=0., cz=0.;

	while (pointFile.good())
	{
		float x,y,z;
		pointFile >> x >> y >> z;

		cx+=(double)x;
		cy+=(double)y;
		cz+=(double)z;

		//auxiliary destination to iteratively read from file,
		//should be local to (hopefully) be constructed again
		//and again with every new iteration, which should create
		//'dots' with unique IDs (in contrast to creating a 'dot'
		//just once before this while cycle and keeping _coping_ it
		//during the push_back() operation)
		T point;
		WritePointTypeCoordinate(x,y,z, point);

		PList.push_back(point);
	}
	//can't check result of reading as the cycle ends iff !good()
	pointFile.close();

	//if the file format is OKay, the last reading of the tripple x,y,z
	//had to fail to break the while cycle, hence:
	PList.pop_back();

	if (Pcentre)
	{
		Pcentre->x=cx/(double)PList.size();
		Pcentre->y=cy/(double)PList.size();
		Pcentre->z=cz/(double)PList.size();
	}

	DEBUG_REPORT("Successfully imported " << PList.size()
					<< " points from " << fileName); 

	if (PList.size() > estCount)
		REPORT("Warning: reserved only for " << estCount
					<< " points but imported more ("
					<< PList.size() << ")!");

	return (PList.size());
}

//----------------------------------------------------------------------
template <class T>
size_t ImportPointList(std::vector< T /**/> &PList,
							  i3d::Vector3d<float>* Pcentre,
							  const size_t maxCnt,
							  const char* fileName)
{
	std::ifstream pointFile(fileName);
	if (!pointFile.is_open()) {
		std::ostringstream err;
		err << "Can't open the input file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}

	typename std::vector< T /**/ >::iterator iter=PList.begin();
	size_t counter=0;

	//centre point
	double cx=0., cy=0., cz=0.;

	while ( (pointFile.good()) && (iter != PList.end())
			&& (counter < maxCnt) )
	{
		float x,y,z;
		pointFile >> x >> y >> z;

		cx+=(double)x;
		cy+=(double)y;
		cz+=(double)z;

		WritePointTypeCoordinate(x,y,z, *iter);
		++iter;
		++counter;
	}
	if (!pointFile.good())
	{
		pointFile.close();
		std::ostringstream err;
		err << "Error while reading file " << fileName << ".";
		throw ERROR_REPORT(err.str());
	}
	pointFile.close();

	//if we got here, the reading was OKay all the time

	if (Pcentre)
	{
		Pcentre->x=cx/(double)counter;
		Pcentre->y=cy/(double)counter;
		Pcentre->z=cz/(double)counter;
	}

	DEBUG_REPORT("Successfully imported " << counter
					<< " points from " << fileName); 

	if (counter != maxCnt)
		REPORT("Warning: expected " << maxCnt
					<< " points but imported less ("
					<< counter << ")!");

	return (counter);
}



//------------------------------------------------------------------
// explicit instantiations
//------------------------------------------------------------------
template
void ExportAllMaskVoxelCoordinates(const i3d::Image3d<i3d::GRAY8> &mask,
											  const i3d::GRAY8 value,
											  const char* fileName);
template
void ExportAllMaskVoxelCoordinates(const i3d::Image3d<i3d::GRAY16> &mask,
											  const i3d::GRAY16 value,
											  const char* fileName);

template
void ExportGridMaskVoxelCoordinates(const i3d::Image3d<i3d::GRAY8> &mask,
											const i3d::GRAY8 value,
											i3d::Vector3d<size_t> const &step,
											const char* fileName);
template
void ExportGridMaskVoxelCoordinates(const i3d::Image3d<i3d::GRAY16> &mask,
											const i3d::GRAY16 value,
											i3d::Vector3d<size_t> const &step,
											const char* fileName);

template
size_t ExportPointList(std::vector< i3d::Vector3d<float> > const &PList,
							  const size_t maxCnt,
							  const char* fileName);
template
size_t ExportPointList(std::vector< Molecule > const &PList,
							  const size_t maxCnt,
							  const char* fileName);

template
size_t ImportAndResetPointList(std::vector< i3d::Vector3d<float> > &PList,
										 i3d::Vector3d<float>* Pcentre,
										 const char* fileName,
										 const size_t estCount);
template
size_t ImportAndResetPointList(std::vector< Molecule > &PList,
										 i3d::Vector3d<float>* Pcentre,
										 const char* fileName,
										 const size_t estCount);

template
size_t ImportPointList(std::vector< i3d::Vector3d<float> > &PList,
							  i3d::Vector3d<float>* Pcentre,
							  const size_t maxCnt,
							  const char* fileName);
template
size_t ImportPointList(std::vector< Molecule > &PList,
							  i3d::Vector3d<float>* Pcentre,
							  const size_t maxCnt,
							  const char* fileName);
