#include <fstream>
#include <iostream>
#include <iomanip>
#include <i3d/image3d.h>

#include "../../scheduler.h"

using std::cout;
using std::endl;

template <class VT>
void Relabel(i3d::Image3d<VT> &img,const VT Lout,const VT Lin) {
	VT *p=img.GetFirstVoxelAddr();
	const VT *pL=img.GetFirstVoxelAddr() + img.GetImageSize();

	while (p != pL) {
		if (*p == Lout) *p=Lin;
		++p;
	}
}

int main(void) {
	//type of mask images
	typedef i3d::GRAY16 MV;

	//names of the metadata text file
	//when preparing tracks for the first image from gtgen outputs
	const char ORIGtracks[50]="tracks.txt";
	const char FINALtracks[50]="man_track.txt";

	//when cleaning tracks after the first processing of them
	//(the first processing is important as it distinguished between
	//disconnected tracks of the same objects, which is behaviour we want)
	//const char ORIGtracks[50]="man_trackBOTH.txt";
	//const char FINALtracks[50]="man_track.txt";

	//span of processed mask files
	const size_t countFrom=0;
	const size_t countTo=49;

	//add IDs of cells which are supposed to be removed from
	//the GT masks, also their daughters will be removed as well
	//
	//FinalPreview images must be regenerated with the new masks
	//using the 'gtgen-ng orig_phant new_mask new_finalPreview'
	std::map<MV,bool> droppers;

	/*
	//dataset Training: 05:
	droppers[60]=true;
	droppers[62]=true;
	droppers[68]=true;
	droppers[57]=true;
	droppers[56]=true;
	droppers[71]=true;
	droppers[67]=true;
	droppers[46]=true;
	droppers[69]=true;
	droppers[70]=true;
	droppers[63]=true;
	droppers[26]=true;
	droppers[21]=true;
	droppers[47]=true;
	droppers[65]=true;
	droppers[66]=true;
	droppers[59]=true;
	droppers[61]=true;
	droppers[44]=true;
	droppers[39]=true;
	*/

	/*
	//dataset Training: 06:
	droppers[53]=true;
	droppers[39]=true;
	droppers[42]=true;
	droppers[56]=true;
	droppers[70]=true;
	droppers[63]=true;
	droppers[64]=true;
	droppers[66]=true;
	droppers[67]=true;
	droppers[72]=true;
	droppers[57]=true;
	droppers[48]=true;
	droppers[51]=true;
	droppers[54]=true;
	droppers[61]=true;
	droppers[62]=true;
	droppers[60]=true;
	droppers[68]=true;
	droppers[71]=true;
	droppers[74]=true;

	droppers[58]=true;
	droppers[70]=true;
	droppers[65]=true;
	droppers[59]=true;
	*/


	//now retrieve orig tracking information
	//iterate:
	//	read orig mask image
	//	find out which IDs are present in it
	//	check every found ID with the orig file
	//		and with internal structures
	//	save the possibly modified mask image
	//save the new tracking information

	std::map<MV, TrackRecord<MV> /**/ > tracks;
	std::map<MV,MV> remap;

	std::ifstream OLDtracks(ORIGtracks);
	if (!OLDtracks.is_open()) {
		cout << "orig tracking information not present, can't read " << ORIGtracks << endl;
		return -1;
	}

	MV ID;
	MV maxID=0; //max being used ID

	while (OLDtracks >> ID) {
		//managed to start reading a new line, let's also read the rest of it;
		//
		//here is a reference to a track already existing in the tracks structure
		TrackRecord<MV> &myT=tracks[ID];
		if (ID > maxID) maxID=ID;

		OLDtracks >> myT.fromTimeStamp >> myT.toTimeStamp >> myT.parentID;
		//special marks:
		//will be changed just once when the cell is spotted for the first time
		myT.fromTimeStamp=99999;
		//will be changed every time the cell is spotted
		myT.toTimeStamp=0;

		//identity mapping at the begining
		remap[ID]=ID;
	}
	OLDtracks.close();

	cout << std::setfill('0');

	//MAIN CYCLE
	//note: indicator of employed default constructor is TrackRecord::toTimeStamp == 0
	for (size_t T=countFrom; T <= countTo; ++T) {
		//open input mask
		char fn[50];
		//sprintf(fn,"%.2lu.ics",T);
		//sprintf(fn,"sceneMasks0%.2lu.ics",T);
		//sprintf(fn,"man_track%.2lu_atBOTH.ics",T);
		sprintf(fn,"man_track%.2lu_START.tif",T);
		cout << "reading: " << fn << endl;
		i3d::Image3d<MV> frame(fn);

		//learn what IDs are present in the current frame
		std::map<MV,int> presentIDs;
		for (size_t i=0; i < frame.GetImageSize(); ++i) {
			const MV seenID=frame.GetVoxel(i);
			if (seenID > 0) presentIDs[seenID]=1;
		}

		//process them
		std::map<MV,int>::iterator pIDs=presentIDs.begin();
		while (pIDs != presentIDs.end()) {
			//process discovered ID
			ID=pIDs->first;

			//does it have different name?
			MV nID=remap[ID];
			cout << "found ID=" << ID << ", which is remapped to ID=" << nID << endl;

			if (nID == 0) {
				//this ID is in image but not in the tracks structure...
				cout << "DETECTED PROBLEM WITH ID=" << ID
				     << " BEING IN THE IMAGE BUT NOT IN THE STRUCTURES, QUITING...\n";
				exit(1);
				/*
				//setup a new track
				tracks[ID].fromTimeStamp=T;
				tracks[ID].toTimeStamp=T;
				tracks[ID].parentID=0;
				*/
			}
			
			if (tracks[nID].toTimeStamp < T-1) {
				//this ID has not been seen in the previous frame,
				//so the ID is returning from "out of scene visit"
				//and we may need to assing it a new ID

				//if fromTimeStamp == 99999 then despite the cell is returning, it
				//hasn't left from the scene because we have never seen it here before;
				//hence, no need to assign a _new_ ID
				if (tracks[nID].fromTimeStamp < 99999) {
					//we should assign a new ID
					nID= ++maxID;
					cout << "tracking event: ID=" << remap[ID] << " was last seen in frame "
					     << tracks[remap[ID]].toTimeStamp
					     << ", now re-appeared in frame " << T
					     << " and got new ID=" << nID << endl;
					remap[ID]=nID;

					//and create it a new track
					tracks[nID].fromTimeStamp=T;
					tracks[nID].toTimeStamp=T;
					tracks[nID].parentID=0;
					//note that if daughter cell returns from trip,
					//we no longer remember who was her mother;
					//it is just some new cell to us now

					//no new initial identity mapping for relabeled tracks
					//so that if the same ID suddenly appears in the image,
					//we can detect and report it
				}
			}

			//confirm we have seen this cell in this frame
			tracks[nID].toTimeStamp=T;
			if (nID != ID) {
				cout << "relabeling image: from ID=" << ID
				     << " to ID=" << nID << " in frame " << T << endl;
				Relabel(frame,ID,nID);
			}

			if (droppers[nID]) Relabel(frame,nID,(MV)0);

			//first time we meet?
			if (tracks[nID].fromTimeStamp==99999) {
				tracks[nID].fromTimeStamp=T;

				//also update ID of my mother in my track, if we have some
				//(note that it holds remap[0]==0)
				MV mID=remap[tracks[nID].parentID];

				if (mID) {
					//okay, I do have mother and her current name is mID,
					//but hey, what if I was born when my mother was out of
					//the scene, then we must forget this tracking information
					//and pretend like I have just came here from a trip as well
					//
					//if my mother was in the last scene, the T-1 is included
					//in her life-span...
					if ((tracks[mID].fromTimeStamp <= T-1)
					  && (T-1 <= tracks[mID].toTimeStamp))
						tracks[nID].parentID=mID;
					else
						tracks[nID].parentID=0;
				}

				//remove parent if mother was discarded
				if (droppers[mID]) tracks[nID].parentID=0;

				cout << "tracking event: ID=" << nID
				     << " was spotted for the first time in frame "
				     << T << ", parent has ID=" << mID << endl;
			}

			cout << "confirming presence of ID=" << nID << " in frame "
			     << std::setw(3) << T << endl;

			//next ID, please...
			pIDs++;
		}

		//report who was not found from those who we have seen in the last frame
		std::map<MV, TrackRecord<MV> /**/ >::iterator itTr;
		for (itTr = tracks.begin(); itTr != tracks.end(); itTr++)
			if ((itTr->second.fromTimeStamp < 99999) //was seen
			  && (itTr->second.toTimeStamp == T-1)) //lastly in the last frame
				cout << "tracking event: ID=" << itTr->first
				     << " is now missing in frame " << T << endl;

		//write polished final mask
		//sprintf(fn,"man_track%.2lu_START.tif",T);
		sprintf(fn,"man_track%.2lu.tif",T);
		cout << "writing: " << fn << endl;
		frame.SaveImage(fn);
	}


	std::ofstream NEWtracks(FINALtracks);

	 std::map<MV, TrackRecord<MV> /**/ >::iterator itTr;
	 for (itTr = tracks.begin(); itTr != tracks.end(); itTr++)
	 {
	 	if (itTr->second.fromTimeStamp == 99999) {
			cout << "warning: ID=" << itTr->first << " was never spotted in the images\n";
		} else if (!droppers[itTr->first]) {
			NEWtracks << itTr->first << " " 
				 << itTr->second.fromTimeStamp << " "
				 << itTr->second.toTimeStamp << " "
				 << itTr->second.parentID << endl;
		}
	 }

	if (!NEWtracks.good()) cout << "Error writing final tracks " << FINALtracks << endl;
	NEWtracks.close();

	return(0);
}
