/*
 * 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 <i3d/image3d.h>
#include <i3d/vector3d.h>
#include <iostream>
#include <math.h>

#include "../toolbox/importexport.cpp"

using namespace i3d;

template <class VOXEL>
void UpdateMetaData(i3d::Image3d<float> &updateImg,
						  i3d::Image3d<VOXEL> const &templateImg)
{
	updateImg.CopyMetaData(templateImg);
	updateImg.MakeRoom(templateImg.GetSizeX(), templateImg.GetSizeY(), 1);
}


int main(int argc, char **argv)
{
    std::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 != 8)
    {
		  std::cout
		  	<< "Calculates 2D flow field from Mitopacq-exported chromatin molecules coordinates --\n"
			<< "basically, from the rearragement of the chromatin texture in the phantom.\n"
			<< "It considers only molecules from the given slice. It ignores the z-coordinate completely.\n\n"
			<< "Creates two images outputX and outputY of the resolution, offset, etc. and size of the templateImg,\n"
			<< "and fills it with a flow field calculated from chromatin txtFrom and txtTo coordinate files.\n"
			<< "The flow vectors will be either in units of micrometers or pixels.\n"
			<< "The coordinates in the chromatin txtFrom and txtTo are assumed in microns.\n\n"
			<< "Usage: " << argv[0] << " txtFrom txtTo outputX outputY templateImg slice_no px/um\n";
        return -2;
    }

	 //template image
    i3d::ImgVoxelType vt = i3d::ReadImageType(argv[5]);
    std::cerr << "Template Voxel=" << VoxelTypeToString(vt) << std::endl;

	 //output images
	 i3d::Image3d<float> fx,fy;

    switch (vt)
    {
    case i3d::Gray8Voxel:
        {
			i3d::Image3d<i3d::GRAY8> tmp(argv[5]);
			UpdateMetaData(fx,tmp);
			UpdateMetaData(fy,tmp);
			break;
        }
    case i3d::Gray16Voxel:
        {
			i3d::Image3d<i3d::GRAY16> tmp(argv[5]);
			UpdateMetaData(fx,tmp);
			UpdateMetaData(fy,tmp);
			break;
        }
    case i3d::FloatVoxel:
        {
			i3d::Image3d<float> tmp(argv[5]);
			UpdateMetaData(fx,tmp);
			UpdateMetaData(fy,tmp);
			break;
        }
    case i3d::DoubleVoxel:
        {
			i3d::Image3d<double> tmp(argv[5]);
			UpdateMetaData(fx,tmp);
			UpdateMetaData(fy,tmp);
			break;
        }
	 case i3d::RGBVoxel:
        {
			i3d::Image3d<i3d::RGB> tmp(argv[5]);
			UpdateMetaData(fx,tmp);
			UpdateMetaData(fy,tmp);
			break;
        }
	 case i3d::RGB16Voxel:
        {
			i3d::Image3d<i3d::RGB16> tmp(argv[5]);
			UpdateMetaData(fx,tmp);
			UpdateMetaData(fy,tmp);
			break;
        }

    default:
        std::cerr << "Other voxel types not supported" << std::endl;
    }


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

	 //what volume thickness to consider...
	 const size_t slicePX=atol(argv[6]);
	 const float sliceUM_from=zOff +     (float)slicePX/zRes;
	 const float sliceUM_to  =zOff + (float)(slicePX+1)/zRes;
	 std::cout << "processing at slice " << slicePX << " px, which is z-range "
	 		<< sliceUM_from << "--" << sliceUM_to << " um\n";

	 //export unit?
	 const bool inPixels=((argv[7][0] == 'u') && (argv[7][1] == 'm'))? false : true;
	 std::cout << "created flow fields will assume units of "
	 		<< ((inPixels)? "pixels\n" : "microns\n");

	//now read the chromatin points
	std::vector< i3d::Vector3d<float> /**/> chrFrom, chrTo;
	ImportAndResetPointList(chrFrom,NULL,argv[1],1400000);
	ImportAndResetPointList(chrTo,NULL,argv[2],1400000);

	//prepare the output flow fields
	i3d::Image3d<float> counts,lengths;
	UpdateMetaData(counts,fx);
	UpdateMetaData(lengths,fx);

	lengths.GetVoxelData()=0.f;
	counts.GetVoxelData()=0.f;
	fx.GetVoxelData()=0.f;
	fy.GetVoxelData()=0.f;

	//time-savers...
	float* const pfx=fx.GetFirstVoxelAddr();
	float* const pfy=fy.GetFirstVoxelAddr();
	float* const pcounts=counts.GetFirstVoxelAddr();
	float* const plengths=lengths.GetFirstVoxelAddr();

	size_t dotCounter=0;

	//now scan through the points, calculate displacements
	//and add to the flow fields (if z-coordinate is OK)
	for (size_t i=0; i < chrFrom.size(); ++i)
	if ((sliceUM_from <= chrFrom[i].z) && (chrFrom[i].z < sliceUM_to))
	{
		//the chromatin molecule is within the desired slice
		//calc. its pixel position
		i3d::Vector3d<int> Pos(0);
		Pos.x=roundf((chrFrom[i].x-xOff)*xRes);
		Pos.y=roundf((chrFrom[i].y-yOff)*yRes);

		if (fx.Include(Pos))
		{
			//the molecule could be even seen in the template image:
			//offset within the image
			const size_t offset=fx.GetIndex(Pos);

			//update the flow field images
			*(pfx+offset) += chrTo[i].x - chrFrom[i].x;
			*(pfy+offset) += chrTo[i].y - chrFrom[i].y;
			*(pcounts+offset) += 1.f;

			if ((Pos.x == 57) && (Pos.y == 56))
			{
				float dx=chrTo[i].x - chrFrom[i].x;
				float dy=chrTo[i].y - chrFrom[i].y;


				std::cout << Pos << ": (" << dx << "," << dy << ")um, length="
						<< sqrtf(dx*dx + dy*dy) << "um \n";
			}

			++dotCounter;
		}
	}

	//now finalize the flow field
	if (inPixels)
	{
		for (size_t i=0; i < fx.GetImageSize(); ++i)
		if (*(pcounts+i) > 0)
		{
			*(pfx+i) /= *(pcounts+i); *(pfx+i) *= xRes;
			*(pfy+i) /= *(pcounts+i); *(pfy+i) *= yRes;

			//calculate length
			*(plengths+i)=sqrtf(*(pfy+i) * *(pfy+i)  +  *(pfx+i) * *(pfx+i));
		}
	}
	else
	{
		for (size_t i=0; i < fx.GetImageSize(); ++i)
		if (*(pcounts+i) > 0)
		{
			*(pfx+i) /= *(pcounts+i);
			*(pfy+i) /= *(pcounts+i);

			//calculate length
			*(plengths+i)=sqrtf(*(pfy+i) * *(pfy+i)  +  *(pfx+i) * *(pfx+i));
		}
	}

	//save the output
	fx.SaveImage(argv[3]);
	fy.SaveImage(argv[4]);
	counts.SaveImage("density.tif"); //DEBUG
	lengths.SaveImage("lengths.tif"); //DEBUG

	std::cout << "included " << dotCounter << " molecules\n";
	std::cout << "images density.tif and lengths.tif were created as well...\n";

    return 0;
}
