//  
//  deplTool.h
//  Tools and data for depletion voltage analysis
//
//  Created by Christian Elsasser on 01.05.12.
//  University of Zurich, elsasser@cern.ch
//  Copyright only for commercial use
//

//  General include
#include <iostream>
#include <math.h>
#include <map>


#include "TROOT.h"
#include "TSpline.h"

namespace STTool {
  
  
  // Cut on tracks
  TCut cu_track_TT("(TrChi2/TrNDoF<3.0) &&"
                   "(GhostP<0.01+0.1/1.6*TrChi2/TrNDoF) &&"
                   "(GhostP<0.01-0.1/3.4*(TrChi2/TrNDoF-5.0))");
  TCut cu_track_IT("(GhostP<0.1*TrChi2/TrNDoF) &&"
                   "(GhostP<0.2-0.1*(TrChi2/TrNDoF-5.0))");
  
  // Define first run of fill
  static std::map<int,int> zeroRunOfFill(){
    std::map<int,int> runMap;
    runMap[1020]      =    69606;
    runMap[1616]      =    87147;
    runMap[1944]      =    95936;
    runMap[2083]      =   101357;
    runMap[2252]      =   104091;
    runMap[2472]      =   111267;
    runMap[2797]      =   120006;
    runMap[3108]      =   129494;
    runMap[3478]      =   135605;
    return runMap;
  }
  std::map<int,int> zeroRunMap = zeroRunOfFill();
  
  // Value of one time step used in sampling time change
  double timestep = 0.10417; // ns
  
  // Calibration step residual -> time step (i.e. step%6)
  static std::map<int,double> createTimeMap(){
    std::map<int,double> timeMap;
    timeMap[0]      =    0.0;
    timeMap[1]      =  -60.0;
    timeMap[2]      =  -30.0;
    timeMap[3]      =   30.0;
    timeMap[4]      =   60.0;
    timeMap[5]      =   90.0;
    return timeMap;
  }
  std::map<int,double> timeMap = createTimeMap();
  
  // Calibration step residual -> TT voltage in volts (i.e. step/6)
  static std::map<int,double> createVoltMapTT(){
    std::map<int,double> voltMapTT; 
    voltMapTT[0]    =  400.0;
    voltMapTT[1]    =  350.0;
    voltMapTT[2]    =  300.0;
    voltMapTT[3]    =  250.0;
    voltMapTT[4]    =  225.0;
    voltMapTT[5]    =  200.0;
    voltMapTT[6]    =  175.0;
    voltMapTT[7]    =  150.0;
    voltMapTT[8]    =  125.0;
    voltMapTT[9]    =  100.0;
    voltMapTT[10]   =   60.0;
    voltMapTT[11]   =   20.0;  // Not used, could remove
    voltMapTT[12]   =   20.0;  // Not used, could remove
    return voltMapTT;
  }
  std::map<int,double> voltMapTT = createVoltMapTT();
  
  // Calibration step residual -> IT voltage in volts (i.e. step/6)
  static std::map<int,double> createVoltMapIT(){
    std::map<int,double> voltMapIT; 
    voltMapIT[0]    =  300.0;
    voltMapIT[1]    =  200.0;
    voltMapIT[2]    =  170.0;
    voltMapIT[3]    =  140.0;
    voltMapIT[4]    =  120.0;
    voltMapIT[5]    =  105.0;
    voltMapIT[6]    =   90.0;
    voltMapIT[7]    =   75.0;
    voltMapIT[8]    =   60.0;
    voltMapIT[9]    =   40.0;
    voltMapIT[10]   =   20.0;
    voltMapIT[11]   =   20.0;  // Not used, could remove
    voltMapIT[12]   =   20.0;  // Not used, could remove
    return voltMapIT;
  }
  std::map<int,double> voltMapIT = createVoltMapIT();
  
  
  // Ratio values
  
  double TTratio_val = 0.953; // with time correction
  double TTratio_err = 0.019;
  
  double ITratio_val = 0.956; // with time correction
  double ITratio_err = 0.017;
  
  
  // FLUKA conversion factor
  double FLUKAConvFac = 7.2*1e4;
  
  
  // Returns time step for a given calibration step
  double GetTimeStep(int step){
    if (step<0) {
      Error("GetTimeStep","Calibration step is smaller than zero, returning NaN");
      return sqrt(-1);
    }
    return timeMap[step%6];
  }
  
  // Returns time in ns for a given calibration step
  double GetTime(int step){
    if (step<0) {
      Error("GetTime","Calibration step is smaller than zero, returning NaN");
      return sqrt(-1);
    }
    return timeMap[step%6]*timestep;
  }
  
  // Returns TT voltage in V for a given calibration step
  double GetTTVoltage(int step){
    if (step<0 || step/6>12) {
      Error("GetTTVoltage","Calibration step is out of reach, returning NaN");
      return sqrt(-1);
    }
    return voltMapTT[step/6];
  }
  
  // Returns IT voltage in V for a given calibration step
  double GetITVoltage(int step){
    if (step<0 || step/6>12) {
      Error("GetITVoltage","Calibration step is out of reach, returning NaN");
      return sqrt(-1);
    }
    return voltMapIT[step/6];
  }
  
  // Return calibration step for a given time step and TT voltage
  int GetTTCalibStep(double time, double voltage){
    std::map<int,double>::const_iterator timeEnd = timeMap.end();
    std::map<int,double>::const_iterator voltEnd = voltMapTT.end();
    for (std::map<int,double>::const_iterator itTime = timeMap.begin(); 
         itTime != timeEnd; itTime++) {
      for (std::map<int,double>::const_iterator itVolt = voltMapTT.begin(); 
          itVolt != voltEnd; itVolt++) {
        if (itTime->second == time && itVolt->second == voltage) {
          return itVolt->first*6+itTime->first;
        }
      }
    }
    Warning("GetTTCalibStep","No corresponding calibration step found, returning -1");
    return -1;
  }
  
  // Return calibration step for a given time step and IT voltage
  int GetITCalibStep(double time, double voltage){
    std::map<int,double>::const_iterator timeEnd = timeMap.end();
    std::map<int,double>::const_iterator voltEnd = voltMapIT.end();
    for (std::map<int,double>::const_iterator itTime = timeMap.begin(); 
         itTime != timeEnd; itTime++) {
      for (std::map<int,double>::const_iterator itVolt = voltMapIT.begin(); 
           itVolt != voltEnd; itVolt++) {
        if (itTime->second == time && itVolt->second == voltage) {
          return itVolt->first*6+itTime->first;
        }
      }
    }
    Warning("GetITCalibStep","No corresponding calibration step found, returning -1");
    return -1;
  }
  
  // Half Gaussian for Pulse
  TF1* createHalfGauss(){
    TF1* func = new TF1("halfGauss",
                        "[0]*"
                        "(TMath::Power((x-[1])/[2],2)/2.0-"
                        "TMath::Power((x-[1])/[2],3)/6.0)*"
                          "TMath::Exp(-(x-[1])/[2])",-30.0,30.0);
    func->SetParNames("A_{0}","t_{0}","#tau");
    func->SetParameters(42,-15.0,10.0);
    func->SetParLimits(0, 10.0,800.0);
    func->SetParLimits(1,-50.0, 10.0);
    func->SetParLimits(2,  1.0, 60.0);
    func->SetNpx(1000);
    return func;
  }
  TF1* halfGauss = createHalfGauss();
  
  
  // Level as V_d
  double VdeplLevel = 0.8;
  
  // Create sigmoid function 1 for Voltage
  Double_t func_Sig1(Double_t *x, Double_t *par)
  // par[0] = V_d, par[1] = V_0, par[2] = A_0, par[3] = per
  {
    double per = par[3];
    double s   = TMath::Log(per/(1.0-per));
    double exp = par[2]/(1+TMath::Exp(-s*(x[0]-par[1])/(par[0]-par[1])));
    double lin = par[2]/2.0*(1+s/2.0*(x[0]-par[1])/(par[0]-par[1]));
    return TMath::Min(exp,lin);
  }
  
  TF1* createSigmoid1(){
    TF1* func = new TF1("Sigmoid1",func_Sig1,-100.0,800.0,5);
    func->SetParNames("V_{depl}","V_{0}","A_{0}","level");
    func->SetParameters(250.0,160.0,42,VdeplLevel);
    func->SetParLimits(0, 0.0, 300.0);
    func->SetParLimits(1,-20.0, 200.0);
    func->SetParLimits(2, 10.0,800.0);
    func->SetParLimits(4, 0.0,1.0);
    func->FixParameter(3,VdeplLevel);
    func->SetNpx(1000);
    return func;
  }
  TF1* sigmoid1 = createSigmoid1();
  
  // Create sigmoid function 2 for Voltage
  Double_t func_Sig2(Double_t *x, Double_t *par)
  // par[0] = V_d, par[1] = V_0, par[2] = A_0, par[3] = per
  {
  double x0 = par[2]/par[0]+par[0]/(4*par[1]);
  double x1 = x0 - par[0]/(2*par[1]);
  if(x[0]<x1){
    return par[0]*x[0];
  }
  if(x[0]<x0){
    return par[2]-(x[0]-x0)*(x[0]-x0)*par[1];
  }
  return par[2];
  }
  
  TF1* createSigmoid2(){
    TF1* func = new TF1("Sigmoid2",func_Sig2,-100.0,800.0,3);
    func->SetParNames("C_{0}","B_{0}","A_{0}");
    func->SetParameters(3.0,0.5,42);
    func->SetParLimits(0, 0.0,  10.0);
    func->SetParLimits(1, 0.0,  20.0);
    func->SetParLimits(2,10.0, 100.0);
    func->SetNpx(1000);
    return func;
  }
  TF1* sigmoid2 = createSigmoid2();
  
  
  // Create sigmoid function 3 for Voltage
  Double_t func_Sig3(Double_t *x, Double_t *par)
  // par[0] = V_d, par[1] = V_0, par[2] = A_0, par[3] = per
  {
  if(x[0]<par[0]){
    return -TMath::Sqrt(par[1]*(x[0]-par[0])*(x[0]-par[0])+par[2])+par[3]+TMath::Sqrt(par[2]);
  }
  return par[3];
  }
  
  TF1* createSigmoid3(){
    TF1* func = new TF1("Sigmoid2",func_Sig3,-100.0,800.0,4);
    func->SetParNames("X_{0}","C_{0}","B_{0}","A_{0}");
    func->SetParameters(250.0,0.5,2.0,42);
    func->SetParLimits(0, 0.0, 500.0);
    func->SetParLimits(1, 0.0, 200.0);
    func->SetParLimits(2, 0.0, 200.0);
    func->SetParLimits(3,10.0, 100.0);
    func->SetNpx(1000);
    return func;
  }
  TF1* sigmoid3 = createSigmoid3();
  
  
  // Create sigmoid function 4 for Voltage
  Double_t func_Sig4(Double_t *x, Double_t *par)
  // par[0] = V_d, par[1] = A_0, par[2] = per
  {
  double per = par[2];
  double s   = TMath::Log(per/(1.0-per));
  double V0  = 2.0*par[0]/(2.0+s);
  double exp = par[1]/(1+TMath::Exp(-s*(x[0]-V0)/(par[0]-V0)));
  double lin = par[1]/2.0*(1+s/2.0*(x[0]-V0)/(par[0]-V0));
  return TMath::Min(exp,lin);
  }
  
  TF1* createSigmoid4(){
    TF1* func = new TF1("Sigmoid4",func_Sig4,-100.0,800.0,3);
    func->SetParNames("V_{depl}","A_{0}","level");
    func->SetParameters(250.0,160.0,42,VdeplLevel);
    func->SetParLimits(0, 0.0, 300.0);
    func->SetParLimits(1, 10.0,800.0);
    func->FixParameter(2,VdeplLevel);
    func->SetNpx(1000);
    return func;
  }
  TF1* sigmoid4 = createSigmoid4();
  
  
  
  // Create function describing the ballistic deficit
  Double_t func_BD(Double_t *x, Double_t *par){
  // par[0] = V_d, par[1]= Q_0, par[2] = lambda_0, par[3] = lambda_1, par[4] = varepsilon_s
  // par[5] = d (fixed)
    
    double w = par[5]*TMath::Min(1.0,TMath::Sqrt(x[0]/par[0]));
    
    
    
    //Printf("V_d = %f",par[0]);
    
    //Printf("%f",TMath::Exp(-(par[5]-w)/par[2]));
  
    
    double y = par[1]/par[5]*TMath::Exp(-(par[5]-w)/par[2]);
    
    //Printf("y = %f",y);
    
    int nInt = 1000;
    double dt   = 1.0*w/nInt;
    double t    = dt/2.0;
    double integral = 0.0;
    
    
    
    for (int i=0; i<nInt; i++) {
      double eps = 1.0/par[5]*(TMath::Sqrt(x[0]*par[0])-t*par[0]/par[5]);
      double lam = par[2]+par[3]*TMath::Min(1.0,eps/par[4]);
      //Printf("w = %f, dt = %f, eps = %f",w,dt,eps);
      integral += TMath::Exp(-t/lam);
      t += dt;
    }
    
    
    return y*integral*dt;
  }
  
  
  TF1* createBD(){
    TF1* func = new TF1("BDFunc",func_BD,-100.0,800.0,6);
    func->SetParNames("V_{d}","Q_{0}","#lambda_{0}",
                      "#lambda_{1}","#varepsilon_{s}","d");
    func->SetParameters(200.0,40.0,500.0,1000.0,0.8,500.0);
    func->SetParLimits(0,20.0,  500.0);
    func->SetParLimits(1, 0.0,  200.0);
    func->SetParLimits(2,190.0,  210.0);
    func->SetParLimits(3,50.0, 3000.0);
    func->SetParLimits(4, 1.0,    1.5);
    func->FixParameter(5, 500.0);
    func->SetNpx(1000);
    return func;
  }
  
  
  TF1* DBfunc = createBD();
  
  
  // Constant function
  TF1* createConst(){
    TF1* func = new TF1("Constfunc","[0]",-100.0,800.0);
    func->SetParNames("A_{0}");
    func->SetParameter(0,40.0);
    func->SetParLimits(0,10.0,  500.0);;
    func->SetNpx(1000);
    return func;
  }
  
  
  TF1* Constfunc = createConst();
  
  
  
  // Generic get X
  double GetX(TObject* o,double y,double xmin, double xmax, int steps=1000){
    if (strcmp(o->IsA()->GetName(),"TF1")==0) {
      TF1* f = (TF1*)o;
      return f->GetX(y,xmin,xmax);
    }
    
    
    
    double x = xmin;
    double step = (xmax-xmin)/steps;
    TSpline* sp = (TSpline*)o;

    
    for (int i=0; i<steps; i++) {
      if ( (sp->Eval(x)<y && sp->Eval(x+step)>y) || (sp->Eval(x)>y && sp->Eval(x+step)<y) ) {
        if (TMath::Abs(sp->Eval(x)-y)==0.0) {
          return x;
        }else if (TMath::Abs(sp->Eval(x+step)-y)==0.0){
          return x+step;
        }
        return (x/TMath::Abs(sp->Eval(x)-y)+(x+step)/TMath::Abs(sp->Eval(x+step)-y))/(1.0/TMath::Abs(sp->Eval(x)-y)+1.0/TMath::Abs(sp->Eval(x+step)-y));
      }
      x+=step;
    }
    Warning("GetX","Has not found a suitable point, returning 0.0");
    return 0.0;
  }
  
  
  // List of sectors to be excluded
  bool IsExcluded(int sec){
    // create List
    std::vector<int> v_ex;
    // TT
    // Outermost regions -> low statistics
    v_ex.push_back(2593);
    v_ex.push_back(2594);
    v_ex.push_back(2595);
    v_ex.push_back(2596);
    v_ex.push_back(2597);
    v_ex.push_back(2598);
    v_ex.push_back(2599);
    v_ex.push_back(2600);
    v_ex.push_back(2601);
    v_ex.push_back(2602);
    v_ex.push_back(2603);
    v_ex.push_back(2604);
    v_ex.push_back(2605);
    v_ex.push_back(2606);
    v_ex.push_back(2607);
    v_ex.push_back(2608);
    v_ex.push_back(2665);
    v_ex.push_back(2666);
    v_ex.push_back(2667);
    v_ex.push_back(2668);
    v_ex.push_back(2669);
    v_ex.push_back(2670);
    v_ex.push_back(2671);
    v_ex.push_back(2672);
    v_ex.push_back(2673);
    v_ex.push_back(2674);
    v_ex.push_back(2675);
    v_ex.push_back(2676);
    v_ex.push_back(2677);
    v_ex.push_back(2678);
    v_ex.push_back(2679);
    v_ex.push_back(2680);
    
    return std::find(v_ex.begin(), v_ex.end(), sec)!=v_ex.end();
  }
  
  // Systematics on depletion voltage
  double voltSyst_TT = 13.4; // V
  double voltSyst_IT =  8.8; // V
  
  
  
  
};