//____________________________________________________________________________
/*!

\class   genie::flux::GSaeRealAtmoFlux

\brief   A flux driver for the Atmospheric and Diffuse Neutrino Fluxes

\author  Carla Distefano <distefano_c@lns.infn.it>
         LNS-INFN, Catania

\created September 13, 2012

\cpright  Copyright (c) 2012-2019, The KM3NeT Collaboration
          For the full text of the license see $GSEAGEN/LICENSE

*/
//____________________________________________________________________________

#include <fstream>
#include <cassert>
#include <cstdio>
#include <iostream>
#include <fstream>

#include <TH2D.h>
#include <TH3D.h>
#include <TMath.h>
#include <TF1.h>
#include <TF3.h>
#include <TFile.h>

#ifdef _GENIEVERSIONGTEQ3__
#include "Framework/Conventions/Constants.h"
#include "Framework/Messenger/Messenger.h"
#include "Framework/ParticleData/PDGLibrary.h"
#else
#include "Conventions/Constants.h"
#include "Messenger/Messenger.h"
#include "PDG/PDGLibrary.h"
#endif

#include "SeaNuDrivers/GSeaRealAtmoFlux.h"


using namespace std;
using namespace genie;
using namespace genie::flux;
using namespace genie::constants;


//___________________________________________________________________________________________________
double GaisserFluxParam(double *x, double *param) {

  double gamma=2.7;
  double eps_pi=115;
  double eps_K=850;

  double Phi0=param[0];

  double Apinu=param[1];
  double Aknu=param[2];

  double Bpinu=param[3];
  double Bknu=param[4];

  double energy=x[0];
  double costheta=fabs(x[1]);

  double PhiN=Phi0*pow(energy/1E3,-gamma);
  double frac1=Apinu/(1.+ Bpinu*energy*costheta/eps_pi);
  double frac2= Aknu/(1.+ Bknu*energy*costheta/eps_K);
  double flux=PhiN*(frac1+frac2);

  return flux;
}

//___________________________________________________________________________
GSeaRealAtmoFlux::GSeaRealAtmoFlux(GenParam * GenPar) :
GSeaAtmoFlux(GenPar)
{
  LOG("GSeaRealAtmoFlux", pNOTICE)
       << "Instantiating the real atmospheric neutrino flux driver";
  this->LoadFluxFile();

}
//___________________________________________________________________________
GSeaRealAtmoFlux::~GSeaRealAtmoFlux()
{

}

//___________________________________________________________________________
void GSeaRealAtmoFlux::LoadFluxFile(void){


  for(unsigned i=0; i<fGenPar->FluxFiles.size(); i++){

    this->SetBinSizes(fGenPar->FluxFiles[i].FluxSimul,fGenPar->EvMin,fGenPar->EvMax);

    string hname=fGenPar->FluxFiles[i].FluxSimul;
    hname.append("_");
    hname.append(PDGLibrary::Instance()->Find(fGenPar->FluxFiles[i].NuType)->GetName());

    if(fGenPar->FluxFiles[i].FluxSimul.find("FUNC")!=string::npos){
      this->FillFuncFlux(i, hname);
    }
    else if(fGenPar->FluxFiles[i].FluxSimul == "BARTOL" || fGenPar->FluxFiles[i].FluxSimul == "FLUKA"){

      TH2D * histo = this->CreateFluxHisto2D(hname.c_str(),hname.c_str());
      for(unsigned j=0; j<fGenPar->FluxFiles[i].FileNames.size(); j++)
        this->FillFluxHisto2D(fGenPar->FluxFiles[i].FluxSimul,histo, fGenPar->FluxFiles[i].FileNames[j]);

      FluxHisto2D flux;
      flux.Flux2D=histo;
      flux.Range[0]=fMinEv;
      flux.Range[1]=fMaxEv;
      flux.Range[2]=fCosThetaMin;
      flux.Range[3]=fCosThetaMax;

      map<int,vector<FluxHisto2D> >::iterator MapEntry = fFlux2DRange.find(fGenPar->FluxFiles[i].NuType);
      MapEntry->second.push_back(flux);

      if(fGenPar->FluxFiles[i].FluxSimul == "BARTOL"){  // no extrapolation for fluka at the moment
        if(fGenPar->EvMax>fMaxEv){
          FluxFunc2D fluxhe=this->ExtrapolateFlux(histo);
          map<int,vector<FluxFunc2D> >::iterator MapEntryHe = fFlux2FRange.find(fGenPar->FluxFiles[i].NuType);
          MapEntryHe->second.push_back(fluxhe);
          fMaxEv=fGenPar->EvMax;
        }
      }



    }
    else if(fGenPar->FluxFiles[i].FluxSimul == "HONDA"){

      TH2D * histo2D = this->CreateFluxHisto2D(hname.c_str(),hname.c_str());
      TH3D * histo3D = this->CreateFluxHisto3D(hname.c_str(),hname.c_str());

//      for(unsigned j=0; j<fGenPar->FluxFiles[i].FileNames.size(); j++)
//        this->FillFluxHisto2D(fGenPar->FluxFiles[i].FluxSimul,histo, fGenPar->FluxFiles[i].FileNames[j]);

      bool Only1PhiBin = this->FillHondaFlux(fGenPar->FluxFiles[i].NuType, histo2D, histo3D, fGenPar->FluxFiles[i].FileNames[0]);   // only 1 file per nu type!!!

      if(Only1PhiBin){
        FluxHisto2D flux;
        flux.Flux2D=histo2D;
        flux.Range[0]=fMinEv;
        flux.Range[1]=fMaxEv;
        flux.Range[2]=fCosThetaMin;
        flux.Range[3]=fCosThetaMax;
        map<int,vector<FluxHisto2D> >::iterator MapEntry = fFlux2DRange.find(fGenPar->FluxFiles[i].NuType);
        MapEntry->second.push_back(flux);
      }
      else {
        FluxHisto3D flux;
        flux.Flux3D=histo3D;
        flux.Range[0]=fMinEv;
        flux.Range[1]=fMaxEv;
        flux.Range[2]=fCosThetaMin;
        flux.Range[3]=fCosThetaMax;
        flux.Range[4]=fPhiMin;
        flux.Range[5]=fPhiMax;
        map<int,vector<FluxHisto3D> >::iterator MapEntry = fFlux3DRange.find(fGenPar->FluxFiles[i].NuType);
        MapEntry->second.push_back(flux);
      }


      if(fGenPar->EvMax>fMaxEv){
        FluxFunc2D flux=this->ExtrapolateFlux(histo2D);
        map<int,vector<FluxFunc2D> >::iterator MapEntry = fFlux2FRange.find(fGenPar->FluxFiles[i].NuType);
        MapEntry->second.push_back(flux);
        fMaxEv=fGenPar->EvMax;
      }

      if(Only1PhiBin)delete histo3D;
      else delete histo2D;

    }
    else {
      LOG("GSeaRealAtmoFlux", pFATAL) << "Uknonwn flux simulation: " << fGenPar->FluxFiles[i].FluxSimul;
      gAbortingInErr = true;
      exit(1);
    }
  }

// in case of atmospheric neutrinos we have to check is an extrapolation to highe energies is needed

  LOG("GSeaAtmoFlux", pNOTICE) << "Neutrino flux simulation data loaded!";
//exit(1);
  return;
}

//___________________________________________________________________________
void GSeaRealAtmoFlux::SetBinSizes(string FluxSim, double Emin, double Emax)
{

  if(FluxSim == "BARTOL") {//                           BARTOL FLUX
    fNumCosThetaBins            = 20;
    fCosThetaMin                = -1.;
    fCosThetaMax                =  1.;
    fNumPhiBins                 =  1;
    fPhiMin                     =  0.;
    fPhiMax                     =  2.*kPi;
    fNumLogEvBins               = 50;
    fNumLogEvBinsPerDecade      = 10;
    fMinEv                      = 0.1; // GeV
    fDeltaLogEv = 1./fNumLogEvBinsPerDecade;
    fMaxEv = TMath::Power(10.,TMath::Log10(fMinEv)+fDeltaLogEv*fNumLogEvBins);
  }
  else if(FluxSim == "FLUKA") {//                       FLUKA3D FLUX
    fNumCosThetaBins            = 40;
    fCosThetaMin                = -1.;
    fCosThetaMax                =  1.;
    fNumPhiBins                 =  1;
    fPhiMin                     =  0.;
    fPhiMax                     =  2.*kPi;
    fNumLogEvBins               = 61;
    fNumLogEvBinsPerDecade      = 20;
    fMinEv                      = 0.1; // GeV
    fDeltaLogEv = 1./fNumLogEvBinsPerDecade;
    fMaxEv = TMath::Power(10.,TMath::Log10(fMinEv)+fDeltaLogEv*fNumLogEvBins);
  }
  else if(FluxSim == "HONDA"){//                        HONDA FLUX
    fNumCosThetaBins            = 20;
    fCosThetaMin                = -1.;
    fCosThetaMax                =  1.;
    fNumPhiBins                 =  12;
    fPhiMin                     =  0.;
    fPhiMax                     =  2.*kPi;
    fNumLogEvBins               = 101;
    fNumLogEvBinsPerDecade      = 20;
    fDeltaLogEv = 1./fNumLogEvBinsPerDecade;
    fMinEv = TMath::Power(10.,TMath::Log10(0.1)-fDeltaLogEv/2.); // GeV
    fMaxEv = TMath::Power(10.,TMath::Log10(fMinEv)+fDeltaLogEv*fNumLogEvBins);
  }
  else {                        // no histos created or histo read from files, we need just fMinEv and fMaxEv for GENIE
    fNumCosThetaBins            =  1;
    fCosThetaMin                = -1.;
    fCosThetaMax                =  1.;
    fNumPhiBins                 =  1;
    fPhiMin                     =  0.;
    fPhiMax                     =  2.*kPi;
    fMinEv                      = Emin;
    fMaxEv                      = Emax;
    fNumLogEvBinsPerDecade      = 1;
    fDeltaLogEv = 1./fNumLogEvBinsPerDecade;
    fNumLogEvBins               = (int)(TMath::Log10(fMaxEv)-TMath::Log10(fMinEv))*fNumLogEvBinsPerDecade;
  }


  return;
}


//___________________________________________________________________________
bool GSeaRealAtmoFlux::FillFluxHisto2D(string FluxSim, TH2D * histo, string fluxname)
{
  if(FluxSim == "BARTOL")
    this->FillBartolFlux(histo, fluxname);
  else if (FluxSim == "FLUKA")
    this->FillFlukaFlux(histo, fluxname);
  else
    return false;


  return true;
}
//___________________________________________________________________________
bool GSeaRealAtmoFlux::FillBartolFlux(TH2D * histo, string filename)
{

  LOG("GSeaRealAtmoFlux", pNOTICE) << "Loading: " << filename;

  if(!histo) {
     LOG("GSeaRealAtmoFlux", pERROR) << "Null flux histogram!";
     return false;
  }
  ifstream flux_stream(filename.c_str(), ios::in);
  if(!flux_stream) {
     LOG("GSeaRealAtmoFlux", pERROR) << "Could not open file: " << filename;
     return false;
  }

  double energy, costheta, flux;
  double junkd; // throw away error estimates

  // throw away comment line
  flux_stream.ignore(99999, '\n');

  while ( !flux_stream.eof() ) {
    flux = 0.0;
    flux_stream >> energy >> costheta >> flux >> junkd >> junkd;

    if( flux>0.0 ){
      // Compensate for logarithmic units - dlogE=dE/E
      // [Note: should do this explicitly using bin widths]
      flux /= energy;
      LOG("GSeaRealAtmoFlux", pDEBUG)
        << "Flux[Ev = " << energy
        << ", cosTheta = " << costheta << "] = " << flux;

      histo->Fill(energy,costheta,flux);

    }
  }

  return true;
}
//___________________________________________________________________________
bool GSeaRealAtmoFlux::FillFlukaFlux(TH2D * histo, string filename)
{
  LOG("GSeaRealAtmoFlux", pNOTICE) << "Loading: " << filename;

  if(!histo) {
     LOG("GSeaRealAtmoFlux", pERROR) << "Null flux histogram!";
     return false;
  }
  ifstream flux_stream(filename.c_str(), ios::in);
  if(!flux_stream) {
     LOG("GSeaRealAtmoFlux", pERROR) << "Could not open file: " << filename;
     return false;
  }


  double energy, costheta, flux;
  char   j1, j2;

  while ( !flux_stream.eof() ) {
    flux = 0.0;
    flux_stream >> energy >> j1 >> costheta >> j2 >> flux;
    if( flux>0.0 ){
      LOG("GSeaRealAtmoFlux", pDEBUG)
        << "Flux[Ev = " << energy
        << ", cosTheta = " << costheta << "] = " << flux;
      // note: reversing the Fluka sign convention for zenith angle

      histo->Fill(energy,-costheta,flux);
    }
  }
  return true;
}

//___________________________________________________________________________
bool GSeaRealAtmoFlux::FillHondaFlux(int NuType, TH2D * histo2D, TH3D * histo3D, string filename)
{

  LOG("GSeaRealAtmoFlux", pNOTICE) << "Loading: " << filename;
  ifstream file(filename.c_str());


  string line;

  // check if Azimuth angle dependent or Azimuth angle averaged flux Tables.
  bool Only1PhiBin=true;
  float cosZmin,cosZmax;
  float PhiMin, PhiMax, PhiMin0=-1;
  int NumPhiBins=0;

  while(getline(file,line)){
    size_t average = line.find("average");
     if (average!=string::npos){
       char c[10];
       size_t found1 = line.rfind("=");
       size_t found2 = line.rfind("]");
       string str = line.substr(found1+1, found2-found1-1);
       sscanf (str.c_str(), "%f %s %f ",&PhiMin, c,&PhiMax);
       if(PhiMin>PhiMin0){
         NumPhiBins++;
         PhiMin0=PhiMin;
       }
     }
  }

  if(NumPhiBins==1)LOG("GSeaRealAtmoFlux", pNOTICE) << "HONDA flux: Azimuth angle averaged flux file";
  else if(NumPhiBins==12){
    Only1PhiBin=false;
    LOG("GSeaRealAtmoFlux", pNOTICE) << "HONDA flux: Azimuth angle dependent flux file";
  }
  else {
    LOG("GSeaRealAtmoFlux", pFATAL) << "HONDA flux: No standard file";
    gAbortingInErr = true;
    exit(1);
  }


  file.clear();
  file.seekg(0, ios::beg);

  float PhiMean=0.,CosZMean=0.;
  float energy=0.,flux=0.,NuMuFlux,NuMubarFlux,NuEFlux,NuEbarFlux;

  double PhiCenter[histo3D->GetZaxis()->GetNbins()];
  histo3D->GetZaxis()->GetCenter(PhiCenter);

  while(getline(file,line)){
    size_t average = line.find("average");
    if (average!=string::npos){
      char c[10];
      size_t found1 = line.find("=");
      size_t found2 = line.find(",");
      string str = line.substr(found1+1, found2-found1-1);
      sscanf (str.c_str(), "%f %s %f ",&cosZmin, c,&cosZmax);
      found1 = line.rfind("=");
      found2 = line.rfind("]");
      str = line.substr(found1+1, found2-found1-1);
      sscanf (str.c_str(), "%f %s %f ",&PhiMin, c,&PhiMax);
      CosZMean=(cosZmin+cosZmax)/2.;
      PhiMean=(PhiMax+PhiMin+180.)/2.*kPi/180.;         // PhiMax+PhiMin+180. --> phi angles are measured counter-clockwise from the south and not from north.
      PhiMean=fmod(PhiMean+2*kPi,2*kPi);                // normalization between 0 e 180 deg
    }
    else {
      if(line.find("Enu")==string::npos){
        sscanf (line.c_str(),"%f %f %f %f %f",&energy,&NuMuFlux,&NuMubarFlux,&NuEFlux,&NuEbarFlux);
        if(NuType==14)flux=NuMuFlux;
        else if(NuType==-14)flux=NuMubarFlux;
        else if(NuType==12)flux=NuEFlux;
        else if(NuType==-12)flux=NuEbarFlux;

        if(Only1PhiBin){
          for(int i=0; i<histo3D->GetZaxis()->GetNbins(); i++)
            histo3D->Fill(energy,CosZMean,PhiCenter[i],flux);
          histo2D->Fill(energy,CosZMean,flux);
        }
        else {
          histo3D->Fill(energy,CosZMean,PhiMean,flux);
          histo2D->Fill(energy,CosZMean,flux/(double)NumPhiBins);
        }
      }
    }
  }

  file.close();

  return Only1PhiBin;
}

//___________________________________________________________________________
FluxFunc2D GSeaRealAtmoFlux::ExtrapolateFlux(TH2D * histo2D){

  const int npar = 5;
  double GaisserParam[npar];

  GaisserParam[0]=1e-4;
  GaisserParam[1]=8.9E-3;
  GaisserParam[2]=2.6E-3;
  GaisserParam[3]=2.77;
  GaisserParam[4]=1.18;

  string name=histo2D->GetName();
  name.append("_fit");

// ci facciamo restituire direttamente la funzione di fit
  TF2 * f2 = new TF2(name.c_str(),GaisserFluxParam,1.E3,1.E8,-1.,1.,npar);
  f2->SetParameters(GaisserParam);
  f2->SetParLimits(0,0.5*1E-4,1.5*1E-4);
  f2->SetParLimits(1,8.5E-3,10.5E-3);
  f2->SetParLimits(2,2.E-3,3E-3);
  f2->SetParLimits(3,2.5,3);
  f2->SetParLimits(4,1.,2);

  histo2D->Fit(name.c_str(),"LRQ","",1.E3,1.E4);

  name.append(".root");

  TFile OutFile( name.c_str(), "recreate");
  histo2D->Write();
  OutFile.Close();

  FluxFunc2D flux;
  flux.Flux2D=f2;
  flux.Range[0]=histo2D->GetXaxis()->GetXmax();
  flux.Range[1]=fGenPar->EvMax;
  flux.Range[2]=fCosThetaMin;
  flux.Range[3]=fCosThetaMax;

  return flux;
}