#include "hpiontrackerhitf.h"
#include "hpiontrackerdef.h"
#include "hpiontrackercal.h"
#include "hpiontrackerhit.h"
#include "hpiontrackerdetector.h"
#include "hpiontrackerhitfpar.h"
#include "hpiontrackergeompar.h"
#include "../base/geometry/hgeomtransform.h"
#include "hades.h"
#include "hcategory.h"
#include "hdebug.h"
#include "hevent.h"
#include "hiterator.h"
#include "hruntimedb.h"
#include "hspectrometer.h"
#include <iomanip>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <stdlib.h>
#include <string.h>
using namespace std;
#define PR(x) std::cout << "++DEBUG: " << #x << " = |" << x << "| (" << __FILE__ << ", " << __LINE__ << ")\n";
#define PRh(x) std::cout << "++DEBUG: " << #x << " = hex |" << std::hex << x << std::dec << "| (" << __FILE__ << ", " << __LINE__ << ")\n";
typedef std::vector< StripData > strips_vec; 
typedef std::vector<HitCanData> hitcan; 
struct ClusterCand
{
    strips_vec hits; 
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
};
struct ClusterData
{
    Int_t strip;                  
    Float_t pos;                  
    Float_t time;                 
    Float_t charge;                       
    
    
    
    
    
};
struct hit 
{
    hitcan h[2];
};
struct HitsData
{
    Float_t posX;                 
    Float_t timeX;                        
    Float_t chargeX;                      
    Float_t posY;                 
    Float_t timeY;                        
    Float_t chargeY;                      
    Float_t time_avg;                     
    Float_t charge_avg;                   
    void calcAvgs()
    {
	Float_t qtot = chargeX + chargeY;
	time_avg = (timeX * chargeX + timeY*chargeY)/qtot;
	charge_avg = (chargeX + chargeY) / 2.0;
    }
    
    
    
    
    
    
};
Bool_t hit_sort_c(StripData a, StripData b)
{
    return a.second.second < b.second.second;
}
typedef std::vector< ClusterCand > clus_cand_vec;
typedef std::vector< ClusterData > clusters_vec;
typedef std::vector< HitsData > hits_vec;
ClassImp (HPionTrackerHitF)
    HPionTrackerHitF::HPionTrackerHitF (void)
{
    
    pCalCat      = NULL;
    pHitCat      = NULL;
    iter         = NULL;
    pHitfpar     = NULL;
    pGeompar     = NULL;
}
HPionTrackerHitF::HPionTrackerHitF (const Text_t * name, const Text_t * title, Bool_t skip)
: HReconstructor (name, title)
{
    
    
    pCalCat      = NULL;
    pHitCat      = NULL;
    iter         = NULL;
    pHitfpar     = NULL;
    pGeompar     = NULL;
}
HPionTrackerHitF::~HPionTrackerHitF (void)
{
    
    if (NULL != iter)
    {
	delete iter;
	iter = NULL;
    }
}
Bool_t HPionTrackerHitF::init (void)
{
    
    
    
    HPionTrackerDetector * det = (HPionTrackerDetector *) gHades->getSetup()->getDetector ("PionTracker");
    if (!det)
    {
	Error ("init", "No PionTracker found.");
	return kFALSE;
    }
    pHitfpar = (HPionTrackerHitFPar *) gHades->getRuntimeDb()->getContainer ("PionTrackerHitFPar");
    if (!pHitfpar) return kFALSE;
    pGeompar = (HPionTrackerGeomPar *) gHades->getRuntimeDb()->getContainer ("PionTrackerGeomPar");
    if (!pGeompar) return kFALSE;
    pCalCat = gHades->getCurrentEvent()->getCategory (catPionTrackerCal);
    if (!pCalCat)
    {
	Error ("init()", "HPionTrackerCal category not available!");
	return kFALSE;
    }
    iter = (HIterator *) pCalCat->MakeIterator();
    loccal.set (2, 0, 0);
    pHitCat = det->buildCategory (catPionTrackerHit);
    if (!pHitCat) return kFALSE;
    loc.set(2, 0, 0);
    fActive = kTRUE;
    return kTRUE;
}
Int_t HPionTrackerHitF::execute (void)
{
    
    HPionTrackerCal * pCal = NULL;
    HPionTrackerHit * pHit = NULL;
    const Int_t modules = pHitfpar->getNumModules();
    strips_vec fired_strips[modules];
    strips_vec can_strips[modules];
    
    
    for (Int_t entry = 0; entry < pCalCat->getEntries(); ++entry)
    {
	if (NULL == (pCal = static_cast<HPionTrackerCal *> (pCalCat->getObject (entry))))
	{
	    Error ("execute", "Pointer to HPionTrackerCal object == NULL, returning");
	    return -1;
	}
	Int_t module = pCal->getModule();
	Int_t strip = pCal->getStrip();
	size_t mult = pCal->getMultiplicity();
	Float_t win_l = pHitfpar->getTimeWindowOffset(module);
	Float_t win_u = pHitfpar->getTimeWindowWidth(module) + win_l;
	
	
	for (size_t i = 0; i < mult; ++i)
	{
	    Float_t hit_t;
	    Float_t hit_c;
	    
	    pCal->getTimeAndCharge(i, hit_t, hit_c);
	    
	    if ( (hit_t < win_l) or (hit_t > win_u))  
		continue;
	    
	    
	    std::pair<Int_t, Float_t> pinner(strip, hit_c);
	    StripData stripdat(hit_t,pinner);
	    fired_strips[module].push_back(stripdat);
	    
	    
	}
    }
    
    hit hits;
    hits.h[0].clear();
    hits.h[1].clear();
    
    for (Int_t m1 = 0; m1 < modules; ++m1)
    {
	for (UInt_t h1 = 0; h1 < fired_strips[m1].size(); ++h1)
	{
	    Float_t stripdiff1=fired_strips[m1][h1].second.first-fired_strips[m1][h1+1].second.first;
	    Float_t stripdiff2=fired_strips[m1][h1].second.first-fired_strips[m1][h1+2].second.first;
	    if(abs(stripdiff1)==1)
	    {
		if(abs(stripdiff2)==2)
		{
		    Int_t av_strip=fired_strips[m1][h1+1].second.first;
		    Float_t av_time=(fired_strips[m1][h1].first+fired_strips[m1][h1+1].first+fired_strips[m1][h1+2].first)/3.;
		    Float_t av_charge=(fired_strips[m1][h1].second.second+fired_strips[m1][h1+1].second.second+fired_strips[m1][h1+2].second.second)/3.;
		    std::pair<Int_t, Float_t> stripper(av_strip, av_charge);
		    StripData stripcandat(av_time,stripper);
		    can_strips[m1].push_back(stripcandat);
		    h1++;
		}
		else
		{
		    Int_t av_strip=fired_strips[m1][h1].second.first;
		    Float_t av_time=(fired_strips[m1][h1].first+fired_strips[m1][h1+1].first)/2.;
		    Float_t av_charge=(fired_strips[m1][h1].second.second+fired_strips[m1][h1+1].second.second)/2.;
		    std::pair<Int_t, Float_t> stripper(av_strip, av_charge);
		    StripData stripcandat(av_time,stripper);
		    can_strips[m1].push_back(stripcandat);
		}
		h1++;
	    }
	    else
	    {
		std::pair<Int_t, Float_t> stripper(fired_strips[m1][h1].second.first,fired_strips[m1][h1].second.second);
		StripData stripcandat(fired_strips[m1][h1].first,stripper);
		can_strips[m1].push_back(stripcandat);
	    }
	}
    }
    
    for (Int_t m1 = 0; m1 < modules; ++m1)
    {
	Float_t clus_time_diff = pHitfpar->getClusterDistT(m1);
	Float_t charge_diff = pHitfpar->getChargeThresh(m1);
	for (Int_t m2 = m1+1; m2< modules; ++m2)
	{
	    for (UInt_t h1 = 0; h1 < can_strips[m1].size(); ++h1)
	    {
		for (UInt_t h2 = 0; h2 < can_strips[m2].size(); ++h2)
		{
		    Float_t difft = can_strips[m1][h1].first-can_strips[m2][h2].first;
		    if (difft > (-1. * clus_time_diff) and difft < clus_time_diff) 
		    {
			Float_t diffc = can_strips[m1][h1].second.second-can_strips[m2][h2].second.second;
			Float_t radius = sqrt(diffc*diffc + difft * difft);
			if (m1 == 0 and m2 == 1) 
			{
			    Float_t chargethr_u =  charge_diff;
			    Float_t chargethr_l =  -1.0 * charge_diff;
			    if (can_strips[m1][h1].second.second != HPionTrackerCal::InvalidAdc() and can_strips[m2][h2].second.second != HPionTrackerCal::InvalidAdc() ) 
			    {
				if( diffc >= chargethr_l and diffc <= chargethr_u) 
				{
				    Float_t avg_t = (can_strips[m1][h1].first+can_strips[m2][h2].first)/2.;
				    Float_t avg_c = (can_strips[m1][h1].second.second+can_strips[m2][h2].second.second)/2.;
				    HitCanData can;
				    can.radtimecharge = radius;
				    can.posX = can_strips[m1][h1].second.first;
				    can.timeX = can_strips[m1][h1].first;
				    can.chargeX = can_strips[m1][h1].second.second;
				    can.eventnrX = h1;
				    can.posY = can_strips[m2][h2].second.first;
				    can.timeY = can_strips[m2][h2].first;
				    can.chargeY = can_strips[m2][h2].second.second;
				    can.eventnrY = h2;
				    can.timeavg = avg_t;
				    can.chargeavg = avg_c;
				    hits.h[0].push_back(can);
				}
			    }
			    else
			    {	Float_t avg_t = (can_strips[m1][h1].first+can_strips[m2][h2].first)/2.;
				Float_t avg_c = (can_strips[m1][h1].second.second+can_strips[m2][h2].second.second)/2.;
			    HitCanData can;
			    can.radtimecharge = radius;
			    can.posX = can_strips[m1][h1].second.first;
			    can.timeX = can_strips[m1][h1].first;
			    can.chargeX = can_strips[m1][h1].second.second;
			    can.eventnrX = h1;
			    can.posY = can_strips[m2][h2].second.first;
			    can.timeY = can_strips[m2][h2].first;
			    can.chargeY = can_strips[m2][h2].second.second;
			    can.eventnrY = h2;
			    can.timeavg = avg_t;
			    can.chargeavg = avg_c;
			    hits.h[0].push_back(can);
			    }
			}
			if (m1 == 2 and m2 == 3) 
			{
			    Float_t chargethr_u =  charge_diff;
			    Float_t chargethr_l =  -1.0 * charge_diff;
			    if (can_strips[m1][h1].second.second != HPionTrackerCal::InvalidAdc() and can_strips[m2][h2].second.second != HPionTrackerCal::InvalidAdc()) 
			    {
				if( diffc >= chargethr_l and diffc <= chargethr_u) 
				{
				    Float_t avg_t = (can_strips[m1][h1].first+can_strips[m2][h2].first)/2.;
				    Float_t avg_c = (can_strips[m1][h1].second.second+can_strips[m2][h2].second.second)/2.;
				    HitCanData can;
				    can.radtimecharge = radius;
				    can.posX = can_strips[m1][h1].second.first;
				    can.timeX = can_strips[m1][h1].first;
				    can.chargeX = can_strips[m1][h1].second.second;
				    can.eventnrX = h1;
				    can.posY = can_strips[m2][h2].second.first;
				    can.timeY = can_strips[m2][h2].first;
				    can.chargeY = can_strips[m2][h2].second.second;
				    can.eventnrY = h2;
				    can.timeavg = avg_t;
				    can.chargeavg = avg_c;
				    hits.h[1].push_back(can);
				}
			    }
			    else
			    {
				Float_t avg_t = (can_strips[m1][h1].first+can_strips[m2][h2].first)/2.;
				Float_t avg_c = (can_strips[m1][h1].second.second+can_strips[m2][h2].second.second)/2.;
				HitCanData can;
				can.radtimecharge = radius;
				can.posX = can_strips[m1][h1].second.first;
				can.timeX = can_strips[m1][h1].first;
				can.chargeX = can_strips[m1][h1].second.second;
				can.eventnrX = h1;
				can.posY = can_strips[m2][h2].second.first;
				can.timeY = can_strips[m2][h2].first;
				can.chargeY = can_strips[m2][h2].second.second;
				can.eventnrY = h2;
				can.timeavg = avg_t;
				can.chargeavg = avg_c;
				hits.h[1].push_back(can);
			    }
			}
		    }
		}
	    }
	}
    }
    
    std::sort(hits.h[1].begin(), hits.h[1].end(), HPionTrackerHitF::sortfunction);
    std::sort(hits.h[0].begin(), hits.h[0].end(), HPionTrackerHitF::sortfunction);
    
    for (Int_t p = 0; p < 2; ++p)
    {
	for (UInt_t h1 = 0; h1 < hits.h[p].size(); ++h1)
	{
	    if (hits.h[p][h1].chargeX == HPionTrackerCal::InvalidAdc())
		continue;
	    int eventnrX_ref = hits.h[p][h1].eventnrX;
	    int eventnrY_ref = hits.h[p][h1].eventnrY;
	    for (UInt_t h2 = h1+1; h2 < hits.h[p].size(); ++h2)
	    {
		int eventnrX = hits.h[p][h2].eventnrX;
		int eventnrY = hits.h[p][h2].eventnrY;
		if (eventnrX_ref == eventnrX or eventnrY_ref == eventnrY)
		{
		    hits.h[p].erase(hits.h[p].begin()+h2);
		    --h2;
		}
	    }
	}
    }
    
    
    for (Int_t p = 0; p < pHitfpar->getNumPlanes(); ++p)
    {
	Int_t planeX, planeY;
	pHitfpar->getPlanePair(p, planeX,  planeY); 
	HPionTrackerDetGeomPar * dgeo = pGeompar->getDetector(p); 
	HGeomTransform gt = dgeo->getLabTransform();           
	Int_t plane_hit_cnt = 0;
	loc[0] = p;
	for (size_t i = 0; i < hits.h[p].size(); ++i)
	{
	    loc[1] = plane_hit_cnt++; 
	    pHit = (HPionTrackerHit*)pHitCat->getObject(loc);
	    if (!pHit)
	    {
		pHit = (HPionTrackerHit *)pHitCat->getSlot(loc);
		if (pHit)
		{
		    pHit = new(pHit) HPionTrackerHit;
		    pHit->setPlaneHit(p, loc[1]); 
		    pHit->setTimeAndCharge(hits.h[p][i].timeX, hits.h[p][i].chargeX, hits.h[p][i].timeY, hits.h[p][i].chargeY);
		    pHit->setLocalPos(hits.h[p][i].posX, hits.h[p][i].posY);
		    
		    HGeomVector v1(
				   dgeo->getStripPos(hits.h[p][i].posX),
				   dgeo->getStripPos(hits.h[p][i].posY),
				   0);
		    
		    HGeomVector v2 = gt.getTransVector() + gt.getRotMatrix() * v1;
		    
		    pHit->setLabPos(v2.getX(), v2.getY(), v2.getZ());
		}
		else
		{
		    Error("execute()", "Can't get slot plane=%i, cnt=%i for hit %lu",
			  loc[0], loc[1], i+1);
		    return 0;
		}
	    }
	    else
	    {
		Error("execute()", "Slot already exists for plane=%i, cnt=%i", loc[0], loc[1]);
		return -1;
	    }
	}
    }
    return 0;
}