/*
 * Tools related to the MitoPacq, the CBIA time-lapse cell observation simulator.
 *
 * Copyright (C) 2005-2013   Centre for Biomedical Image Analysis (CBIA)
 *
 * This library-related tools is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <iostream>
#include <sstream>
#include <i3d/image3d.h>

using namespace std;
using namespace i3d;

#define TimeLapseInit		1
#define TimeLapseInsert		2
#define TimeLapseReplace	3
#define TimeLapseAppend		4

template <class VOXEL>
int Move2DFrames(Image3d<VOXEL> &seq,			//INPUT/OUTPUT: the sequence image to be modified
	       const int from,				//INPUT: the splitting point
	       const int offset)			//INPUT: the number of frames to add or remove
{
	//integrity check
	if ((from-offset) > (int)seq.GetSizeZ()) {
		std::cerr << "Move2DFrames(): too many frames to delete (from=" << from \
			  << ", offset=" << offset << ", GetSizeZ()=" << seq.GetSizeZ() << ")\n";
		return(1);
	}

	if ((from < 0) || (from >= (int)seq.GetSizeZ())) {
		std::cerr << "Move2DFrames(): the splitting point is outside range (from=" \
			  << from << ", GetSizeZ()=" << seq.GetSizeZ() << ")\n";
		return(1);
	}

	//an "easy job", zero offset indicates no movement
	if (offset == 0) return(0);

	//the size of exactly one frame
	long size=seq.GetSizeX() * seq.GetSizeY();

	//the backup via copy constructor
	const std::valarray<VOXEL> backup(seq.GetVoxelData());
	const int SizeZ=(int)seq.GetSizeZ();

	//the new image is resized (initialization will be conducted later)
	seq.MakeRoom(seq.GetSizeX(),seq.GetSizeY(),(size_t)(seq.GetSizeZ()+offset));

	VOXEL *d=seq.GetFirstVoxelAddr();
	//const VOXEL *b=&backup[0];
	const VOXEL &backup_zero=backup[0];
	const VOXEL *b=&backup_zero;

	//first, copying the non-moving part
	int i=0;
	for (; i < size*from; ++i, ++d, ++b) *d=*b;

	//initializing new frames
	if (offset > 0) for (i=0; i < size*offset; ++i, ++d) *d=static_cast<VOXEL>(0);

	//last, copying the moving part (or what has remained from it)
	int j=SizeZ-from; //will always be: j >= 1
	if (offset < 0) {
		j+=offset; //will actually subtract |offset| value
		b+=size*(-offset); //will move the pointer forward (skip removed lines)
	}
	for (i=0; i < size*j; ++i, ++d, ++b) *d=*b;

	return(0);
}

template <class VOXEL>
int Insert2DFrame(Image3d<VOXEL> const &frame,		//INPUT: frame to be included
		Image3d<VOXEL> &seq,			//INPUT/OUTPUT: the sequence image to be modified
		const int time,				//INPUT: position where to include the new frame
		const char action,			//INPUT: meaning of the time parameter
		const int count=1)			//INPUT: number of action repetitions
{
	if (action == TimeLapseInit) {
		if (time <= 0) {
			std::cerr << "Insert2DFrame(): The number of frames in the new sequence is negative or zero! Can't be.\n";
			return(-1);
		}
		//initiation... just working with memory
		seq.MakeRoom(frame.GetSizeX(),frame.GetSizeY(),(size_t)time);

		seq.SetOffset(frame.GetOffset());
		seq.SetResolution(frame.GetResolution());
		seq.GetVoxelData()=static_cast<VOXEL>(0);
		return(0);
	}
	if ((action != TimeLapseReplace) && (action != TimeLapseInsert) && (action != TimeLapseAppend)) {
		std::cerr << "Insert2DFrame(): Invalid action.\n";
		return(-1);
	}

	//compatibility tests...
	if ((frame.GetSizeX() != seq.GetSizeX()) || (frame.GetSizeY() != seq.GetSizeY())) {
		std::cerr << "Insert2DFrame(): The inserted frame's xy sizes differ from sequence image sizes!\n";
		return(-1);
	}

	if ((frame.GetOffset() != seq.GetOffset()) || (frame.GetResolution().GetRes() != seq.GetResolution().GetRes())) {
		std::cerr << "Insert2DFrame(): The inserted frame's metadata differs from the sequence's metadata!\n";
		return(-1);
	}

	if ( ((time < 0) || (time >= (int)seq.GetSizeZ())) && (action != TimeLapseAppend) ) {
		std::cerr << "Insert2DFrame(): The frame number to insert new frame at is invalid (time=" \
			  << time << ", GetSizeZ()=" << seq.GetSizeZ() << ")\n";
		return(-1);
	}

	if ((count <= 0) && (action != TimeLapseReplace)) {
		std::cerr << "Insert2DFrame(): The count is negative or zero! Can't be.\n";
		return(-1);
	}

	//the size of exactly one frame
	const long size=seq.GetSizeX() * seq.GetSizeY();
	VOXEL *s=NULL;

	int ret_val=-1;

	if (action == TimeLapseAppend) {
		//the backup via copy constructor
		std::valarray<VOXEL> backup(seq.GetVoxelData()); //can't be const because of freeing later on
		const long SizeZ = (long)seq.GetSizeZ();

		//the new image is resized 
		seq.MakeRoom(seq.GetSizeX(),seq.GetSizeY(),(size_t)(SizeZ+count));

		//restore the backup
		const VOXEL *b=&backup[0];
		s=seq.GetFirstVoxelAddr();

		for (long i=0; i < (SizeZ*size); ++i, ++b, ++s) *s=*b;
		backup.resize((size_t)0); //free the memory

		ret_val=(int)SizeZ; //the first frame modified
	} else {
		if (action == TimeLapseInsert) {
			//make room if necessary
			ret_val=Move2DFrames<VOXEL>(seq,time,count);
			if (ret_val) return(-1); //if error then quit
		}

		//now we can safely replace the given frame
		s=seq.GetVoxelAddr(0,0,time);

		ret_val=time;
	}

	//just to make sure the parameter count is valid when its validity is not expected
	int Count=(action == TimeLapseReplace)? 1 : count;

	//insert or append the new frame count-many times
	for (int j=0; j < Count; ++j) {
		const VOXEL *f=frame.GetFirstVoxelAddr();
		for (long i=0; i < size; ++i, ++f, ++s) *s=*f;
	}

	return(ret_val);
}


template <class VOXEL>
int ReadSequence(i3d::Image3d<VOXEL> &seq,
		 const char *NamePrefix,const int Width,const char *NameSuffix,
		 const int StartIndex,const int StopIndex)
{
	if (StopIndex < StartIndex) {
		std::cerr << "ReadSequence(): StopIndex cannot be smaller than StartIndex!\n";
		return(1);
	}

	//read the first image, will be used as a sample image
	std::ostringstream fn;
	fn << NamePrefix;
	fn.fill('0'); fn.width(Width); fn << StartIndex;
	fn << NameSuffix;

	std::cout << "Reading initial frame from file " << fn.str() << std::endl;

	Image3d<VOXEL> im(fn.str().c_str());			//a sample image

	//prepare the sequence image
	seq.MakeRoom(im.GetSizeX(),im.GetSizeY(),StopIndex-StartIndex+1); //allocate memory
	seq.SetOffset(im.GetOffset());				//and set metadata
	seq.SetResolution(im.GetResolution());

	//this call is prefered because it conducts necessary compatibility tests
	//(or simplier, we could have copied it ourselves...)
	if (Insert2DFrame(im,seq,0,TimeLapseReplace) != 0) {	//copy image data
		std::cerr << "ReadSequence(): StopIndex Cannot insert the first frame!\n";
		return(1);
	}

	for (int i=StartIndex+1; i <= StopIndex; ++i) {
		fn.seekp(std::ios_base::beg);			//"clears" the string/filename

		fn << NamePrefix;
		fn.width(Width); fn << i;
		fn << NameSuffix;

		std::cout << "Reading frame no. " << i-StartIndex << " from file " << fn.str() << std::endl;

		im.ReadImage(fn.str().c_str());			//next image in the sequence
		if (Insert2DFrame(im,seq,i-StartIndex,TimeLapseReplace) != (i-StartIndex)) {
			std::cerr << "ReadSequence(): StopIndex Cannot insert the frame no. " << i-StartIndex << "!\n";
			return(1);
		}
	}

	return(0);
}



int main(int argc, char *argv[])
{
    cout << argv[0] << " - part of the tools set of the OpticalFlow library \n"
         "Copyright (C) 2005-2013 Centre for Biomedical Image Analysis (CBIA)\n"
         "This is free software; see the source for copying conditions. "
         "There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR "
         "A PARTICULAR PURPOSE.\n\n";

    if (argc < 7)
    {
	cout << "The program composes several 2D images into a single 3D one, all images should be of the same size.\n";
	cout << "To read files green_0001.tif till green_0012.tif, supply parameters \"green_\" 4 \".tif\" 1 12\n\n";

        cout << "Bad number of parameters\nUsage: " << argv[0] << " output3D prefix width suffix range_from range_to z_res\n" \
	     << "example: " << argv[0] << " cell3D.ics cell2D 2 .tif 1 16 80\n" \
	     << "which will create cell3D.ics out from cell2D_01.tif to cell2D_16.tif.\n";
        cout << "The resolution z_res is given in pixel size in microns.\n";
        return -2;
    }

    const int width=atoi(argv[3]);
    const int rFrom=atoi(argv[5]);
    const int rTo  =atoi(argv[6]);
    const float Zres=atof(argv[7]);

    try
    {
    	//prepare name for the first filename so that Voxel type can be guessed
	std::stringstream fn;
	fn << argv[2];
	fn.fill('0'); fn.width(width); fn << rFrom;
	fn << argv[4];
	cout << "Trying to guess voxel type from the first filename in the series: " << fn.str() << endl;

        ImgVoxelType reference_vt = ReadImageType(fn.str().c_str());
        cout << "reference_vt=" << VoxelTypeToString(reference_vt) << endl;

        switch (reference_vt)
        {
        case Gray8Voxel:
       	{
		//output image
		Image3d<GRAY8> out;

		//read sequence
		if (!ReadSequence<GRAY8>(out,argv[2],width,argv[4],rFrom,rTo)) {
			//adjust resolution, the z-axis
			Resolution res(out.GetResolution());
			res.SetZ(1/Zres);
			out.SetResolution(res);

			//save it
			out.SaveImage(argv[1]);
		} else cerr << "Error processing sequence!\n";
		break;
       	}

        case Gray16Voxel:
       	{
		//output image
		Image3d<GRAY16> out;

		//read sequence
		if (!ReadSequence<GRAY16>(out,argv[2],width,argv[4],rFrom,rTo)) {
			//adjust resolution, the z-axis
			Resolution res(out.GetResolution());
			res.SetZ(1/Zres);
			out.SetResolution(res);

			//save it
			out.SaveImage(argv[1]);
		} else cerr << "Error processing sequence!\n";
		break;
       	}

        case RGBVoxel:
       	{
		//output image
		Image3d<RGB> out;

		//read sequence
		if (!ReadSequence<RGB>(out,argv[2],width,argv[4],rFrom,rTo)) {
			//adjust resolution, the z-axis
			Resolution res(out.GetResolution());
			res.SetZ(1/Zres);
			out.SetResolution(res);

			//save it
			out.SaveImage(argv[1]);
		} else cerr << "Error processing sequence!\n";
		break;
       	}

      case FloatVoxel:
       	{
		//output image
		Image3d<float> out;

		//read sequence
		if (!ReadSequence<float>(out,argv[2],width,argv[4],rFrom,rTo)) {
			//adjust resolution, the z-axis
			Resolution res(out.GetResolution());
			res.SetZ(1/Zres);
			out.SetResolution(res);

			//save it
			out.SaveImage(argv[1]);
		} else cerr << "Error processing sequence!\n";
		break;
       	}

       case DoubleVoxel:
	{
		//output image
		Image3d<double> out;

		//read sequence
		if (!ReadSequence<double>(out,argv[2],width,argv[4],rFrom,rTo)) {
			//adjust resolution, the z-axis
			Resolution res(out.GetResolution());
			res.SetZ(1/Zres);
			out.SetResolution(res);

			//save it
			out.SaveImage(argv[1]);
		} else cerr << "Error processing sequence!\n";
		break;
	}

       default:
            cerr << "Other voxel types not supported" << endl;
        }
    }
    catch(IOException & e)
    {
        cerr << e << endl;
        exit(1);
    }
    catch(InternalException & e)
    {
        cerr << e << endl;
        exit(1);
    }
    catch(bad_alloc &)
    {
        cerr << "Not enough memory." << endl;
        exit(1);
    }
    catch(...)
    {
        cerr << argv[0] << "Unknown exception." << endl;
        exit(1);
    }

    return 0;
}
