/*
 * 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 <vector>
#include <i3d/image3d.h>
#include <ctype.h>
using namespace std;
using namespace i3d;

/*
 * This macro controls which variant of the Merge() for RGB should be used.
 * There are two versions of this function. Both consider pixel intensity
 * in the process of merging current image into the "history" image. However,
 * one function consideres combination of the three R,G,B values based on which
 * all three colour channels are either used or ignored. THIS IS what will be
 * DONE IF the following MACRO will NOT BE SET. Otherwise, each channel is
 * processed individually and independently of the others, that is, R may be used
 * while G may be ignored. THIS IS what will be DONE IF the following MACRO will
 * BE SET.
 */
#define RGB_MERGE_INDIVIDUALLY	1

template <class V>
void Normalize(Image3d <V> &i) {
	V max = std::numeric_limits<V>::min(),
	  min = std::numeric_limits<V>::max();

	float stretch=min-max; //switched because of the above!

	V *pointer = i.GetFirstVoxelAddr();
	V *last = i.GetVoxelAddr(i.GetImageSize());

	//determine intensity range
	while (pointer != last) {
		max=(max < *pointer)? *pointer : max;
		min=(min > *pointer)? *pointer : min;

		++pointer;
	}

	float val=max-min;
	stretch/=val;

	pointer = i.GetFirstVoxelAddr();

	//normalize... i.e. stretch to the whole interval
	while (pointer != last) {
		val=*pointer-min;
		*pointer=(V)(val * stretch);

		++pointer;
	}
}


template <class V>
void SetSize(Image3d<V> const &i, Image3d<float> &res) {
	//cout << "Setting size...\n";

	res.MakeRoom(i.GetSize());
	res.SetResolution(i.GetResolution());
	res.GetVoxelData()=0.0f;
}

void SetSize(Image3d<RGB> const &i, Image3d<RGB> &res) {
	//cout << "Setting size...\n";

	res.MakeRoom(i.GetSize());
	res.SetResolution(i.GetResolution());
	res.GetVoxelData()=0.0f;
}


template <class V>
void Merge(Image3d<V> const &i, const float coef, Image3d<float> &res) {
	//cout << "Merging... with coef=" << coef << endl;

	float *f=res.GetFirstVoxelAddr();
	const V *p=i.GetFirstVoxelAddr();

	for (size_t i=0; i < res.GetImageSize(); ++i, ++f, ++p) {
		float val=coef * (float)*p;
		if (*f < val) *f=val;
	}
}


#ifdef RGB_MERGE_INDIVIDUALLY

//merge each channel independently
void Merge(Image3d<RGB> const &i, const float coef, Image3d<RGB> &res) {
	//cout << "Merging... with coef=" << coef << endl;

	RGB *f=res.GetFirstVoxelAddr();
	const RGB *p=i.GetFirstVoxelAddr();

	for (size_t i=0; i < res.GetImageSize(); ++i, ++f, ++p) {
		float val=coef*(float)p->red;
		if (f->red < val) f->red=(GRAY8)val;

		val=coef*(float)p->green;
		if (f->green < val) f->green=(GRAY8)val;

		val=coef*(float)p->blue;
		if (f->blue < val) f->blue=(GRAY8)val;
	}
}

#else

//merge according to intensity
void Merge(Image3d<RGB> const &i, const float coef, Image3d<RGB> &res) {
	//cout << "Merging... with coef=" << coef << endl;

	RGB *f=res.GetFirstVoxelAddr();
	const RGB *p=i.GetFirstVoxelAddr();

	for (size_t i=0; i < res.GetImageSize(); ++i, ++f, ++p) {
		float P=(p->red)*(p->red) + (p->green)*(p->green) + (p->blue)*(p->blue);
		float F=(f->red)*(f->red) + (f->green)*(f->green) + (f->blue)*(f->blue);
		if (F < (coef*coef*P)) {
			f->red=(GRAY8)(coef*(float)p->red);
			f->green=(GRAY8)(coef*(float)p->green);
			f->blue=(GRAY8)(coef*(float)p->blue);
		}
	}
}

#endif


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 < 3)
    {
	cout << "The program composes several images into a single one, all images should be of the same size.\n";
	cout << "It reads a sequence of filenames A B C... from STDIN with file A as the oldest (will be the darkest)\n" \
	     << "frame, the parameter fade_coef (float) defines the fading effect, i.e., i-th oldest image is pixelwise\n" \
	     << "multiplied by 1-i*fade_coef and merged into the output image; in fact, only darker pixels in the output\n" \
	     << "image are replaced with those (and brighter) from the multiplied current input image.\n";
#ifdef RGB_MERGE_INDIVIDUALLY
	cout << "In case of RGB images, the colour channels are processed individually and independtly of each other.\n";
#else
	cout << "In case of RGB images, the colour channels are processed together.\n";
#endif
	cout << "This behaviour can be changed with the RGB_MERGE_INDIVIDUALLY macro in the source code.\n";
	cout << "Also note that normalization is not implemented for RGB images!\n\n";

        cout << "Bad number of parameters\nUsage: " << argv[0] << " fade_coef [-n] output\n" \
	     << "example: cat frame_list | " << argv[0] << " 0.9 output.ics\n" ;
        return -2;
    }


    //determine the arguments
    float fade_coef=atof(argv[1]);
    char should_normalize=0;
    string output;

    if ((argv[2][0] == '-') && (argv[2][1] == 'n')) {
	//the second parameter is '-n'
	if (argc == 3) {
		//there is the third parameter missing
		cerr << "You seems to forget to supply the last parameter, i.e. the output filename.\n";
		return -2;
	} else {
		//ok, parameter full house ;-)
		should_normalize=1;
		output = argv[3];
	}
    } else output = argv[2]; //no '-n'

    cout << "parsed: fade_coef=" << fade_coef << ", should_normalize=" << (int)should_normalize << ", output=" << output.c_str() << endl;

    try
    {
	//determine the voxel type within the sequence, determine it from the first filename on the list
	string filename;
	if (!(cin >> filename)) {
		cerr << "The STDIN seems to be empty... Bailing out.\n";
		return -1;
	}

        ImgVoxelType reference_vt = ReadImageType(filename.c_str());
        cout << "reference_vt=" << VoxelTypeToString(reference_vt) << endl;
	
	//filenames list
	vector<string> filenames;
	filenames.push_back(filename);

	//read the rest of the filename sequence
	char ok=1;
	while ((ok) && (cin >> filename)) {
        	ImgVoxelType vt = ReadImageType(filename.c_str());
		if (vt != reference_vt) ok=0; else filenames.push_back(filename);
	}

	if (ok == 0) {
		//voxel type is not constant
		cerr << "Voxel type differs within the sequence, due to " << filename.c_str() << "!\n";
		return -1;
	}


	//
	//OK, let's start merging....
	//

	Image3d<float> res; //the output image, voxel type will be converted later
	char size_not_set=1; //res image hasn't been initialized yet

	float act_coef=1.0f;

	//scanning through the list from the end, starting with the "youngest"/latest image
	vector<string>::const_iterator iter=filenames.end(); --iter;

	//the list is stored
        switch (reference_vt)
        {
        case Gray8Voxel:
            {
		while (iter != filenames.begin()) {
			cout << "processing filename=" << iter->c_str() << " with act_coef=" << act_coef << endl;

			Image3d<GRAY8> i(iter->c_str());
			if (should_normalize) Normalize<GRAY8>(i);

			if (size_not_set) {
				SetSize<GRAY8>(i,res);
				size_not_set=0;
			} 

			if (res.GetSize() != i.GetSize()) {
				cerr << "Image size differs within the sequence, due to " << iter->c_str() << "!\n";
				return -2;
			} else Merge<GRAY8>(i,act_coef,res);

			act_coef -= fade_coef;
			--iter;
		}

		//iter == filenames.begin(), we need one more iteration
		cout << "processing filename=" << iter->c_str() << " with act_coef=" << act_coef << endl;
		Image3d<GRAY8> i(iter->c_str());
		if (should_normalize) Normalize<GRAY8>(i);

		if (size_not_set) {
			SetSize<GRAY8>(i,res);
			size_not_set=0;
		} 
		
		if (res.GetSize() != i.GetSize()) {
			cerr << "Image size differs within the sequence, due to " << iter->c_str() << "!\n";
			return -2;
		} else Merge<GRAY8>(i,act_coef,res);

		//save the result
		FloatToGray(res,i);
                i.SaveImage(output.c_str());
                break;
            }

        case Gray16Voxel:
            {
		while (iter != filenames.begin()) {
			cout << "processing filename=" << iter->c_str() << " with act_coef=" << act_coef << endl;

			Image3d<GRAY16> i(iter->c_str());
			if (should_normalize) Normalize<GRAY16>(i);

			if (size_not_set) {
				SetSize<GRAY16>(i,res);
				size_not_set=0;
			} 
			
			if (res.GetSize() != i.GetSize()) {
				cerr << "Image size differs within the sequence, due to " << iter->c_str() << "!\n";
				return -2;
			} else Merge<GRAY16>(i,act_coef,res);

			act_coef -= fade_coef;
			--iter;
		}

		//iter == filenames.begin(), we need one more iteration
		cout << "processing filename=" << iter->c_str() << " with act_coef=" << act_coef << endl;
		Image3d<GRAY16> i(iter->c_str());
		if (should_normalize) Normalize<GRAY16>(i);

		if (size_not_set) {
			SetSize<GRAY16>(i,res);
			size_not_set=0;
		} 
		
		if (res.GetSize() != i.GetSize()) {
			cerr << "Image size differs within the sequence, due to " << iter->c_str() << "!\n";
			return -2;
		} else Merge<GRAY16>(i,act_coef,res);

		//save the result
		FloatToGray(res,i);
                i.SaveImage(output.c_str());
                break;
            }
 
        case FloatVoxel:
            {
		while (iter != filenames.begin()) {
			cout << "processing filename=" << iter->c_str() << " with act_coef=" << act_coef << endl;

			Image3d<float> i(iter->c_str());
			//if (should_normalize) Normalize<float>(i);

			if (size_not_set) {
				SetSize<float>(i,res);
				size_not_set=0;
			} 
			
			if (res.GetSize() != i.GetSize()) {
				cerr << "Image size differs within the sequence, due to " << iter->c_str() << "!\n";
				return -2;
			} else Merge<float>(i,act_coef,res);

			act_coef -= fade_coef;
			--iter;
		}

		//iter == filenames.begin(), we need one more iteration
		cout << "processing filename=" << iter->c_str() << " with act_coef=" << act_coef << endl;
		Image3d<float> i(iter->c_str());
		//if (should_normalize) Normalize<float>(i);

		if (size_not_set) {
			SetSize<float>(i,res);
			size_not_set=0;
		} 
		
		if (res.GetSize() != i.GetSize()) {
			cerr << "Image size differs within the sequence, due to " << iter->c_str() << "!\n";
			return -2;
		} else Merge<float>(i,act_coef,res);

		//save the result
                res.SaveImage(output.c_str());
                break;
            }

        case RGBVoxel:
	    {
	    	Image3d<RGB> Res;
		while (iter != filenames.begin()) {
			cout << "processing filename=" << iter->c_str() << " with act_coef=" << act_coef << endl;

			Image3d<RGB> i(iter->c_str());
			if (should_normalize) cerr << "Warning: Normalization not implemented for RGB images!\n";

			if (size_not_set) {
				SetSize(i,Res);
				size_not_set=0;
			} 
			
			if (Res.GetSize() != i.GetSize()) {
				cerr << "Image size differs within the sequence, due to " << iter->c_str() << "!\n";
				return -2;
			} else Merge(i,act_coef,Res);

			act_coef -= fade_coef;
			--iter;
		}

		//iter == filenames.begin(), we need one more iteration
		cout << "processing filename=" << iter->c_str() << " with act_coef=" << act_coef << endl;
		Image3d<RGB> i(iter->c_str());
		if (should_normalize) cerr << "Warning: Normalization not implemented for RGB images!\n";

		if (size_not_set) {
			SetSize(i,Res);
			size_not_set=0;
		} 
		
		if (Res.GetSize() != i.GetSize()) {
			cerr << "Image size differs within the sequence, due to " << iter->c_str() << "!\n";
			return -2;
		} else Merge(i,act_coef,Res);

		//save the result
                Res.SaveImage(output.c_str());
                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;
}
