//************************************************************************//
//                                                                        //
//  Copyright 2014 Bertram Kopf (bertram@ep1.rub.de)                      //
//                 Julian Pychy (julian@ep1.rub.de)                       //
//                 - Ruhr-Universit??t Bochum                              //
//                                                                        //
//  This file is part of Pawian.                                          //
//                                                                        //
//  Pawian is free software: you can redistribute it and/or modify        //
//  it under the terms of the GNU General Public License as published by  //
//  the Free Software Foundation, either version 3 of the License, or     //
//  (at your option) any later version.                                   //
//                                                                        //
//  Pawian 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 General Public License for more details.                          //
//                                                                        //
//  You should have received a copy of the GNU General Public License     //
//  along with Pawian.  If not, see <http://www.gnu.org/licenses/>.       //
//                                                                        //
//************************************************************************//

#include <fstream>
#include <sstream>
#include <string>
#include <iomanip>
#include <vector>

#include "PwaUtils/PwaGen.hh"
#include "PwaUtils/AbsLh.hh"
#include "PwaUtils/EvtDataBaseList.hh"
#include "ConfigParser/ParserBase.hh"
#include "PwaUtils/GlobalEnv.hh"

#include "Particle/Particle.hh"
#include "ErrLogger/ErrLogger.hh"

#include "Utils/IdStringMapRegistry.hh"

#include "PspGen/EvtGenKine.hh"
#include "PspGen/EvtRandom.hh"
#include "PspGen/EvtMTRandomEngine.hh"
#include "PspGen/EvtVector4R.hh"

#include "Event/Event.hh"
#include "Event/EventList.hh"
#include "Event/MassRangeCut.hh"

#include "TFile.h"
#include "TH1F.h"

const unsigned int EVENTS_PER_ITERATION = 100000;

PwaGen::PwaGen() :
   _energyFirst(false)
  ,_useEvtWeight(GlobalEnv::instance()->parser()->useDataEvtWeight())
  ,_genWithModel(GlobalEnv::instance()->parser()->generateWithModel())
  ,_unitScaleFactor(1.)
  , _maxFitWeight(0.)
  ,_initial4Vec(EvtVector4R(GlobalEnv::instance()->Channel()->initial4Vec().E(), GlobalEnv::instance()->Channel()->initial4Vec().Px(),
                            GlobalEnv::instance()->Channel()->initial4Vec().Py(), GlobalEnv::instance()->Channel()->initial4Vec().Pz()))
  ,_finalStateParticles(GlobalEnv::instance()->Channel()->finalStateParticles())
 {
  std::ostringstream datFileName;
  datFileName << "evtGen" << GlobalEnv::instance()->outputFileNameSuffix() << ".dat";
  _stream = new std::ofstream(datFileName.str());
  std::ostringstream rootFileName;
  rootFileName << "evtGen" << GlobalEnv::instance()->outputFileNameSuffix() << ".root";
  _theTFile=new TFile(rootFileName.str().c_str(),"recreate");

  inv01MassH1=new TH1F("inv01MassH1","inv01MassH1",500, 0., 3.);
  inv02MassH1=new TH1F("inv02MassH1","inv02MassH1",500, 0., 3.);
  inv12MassH1=new TH1F("inv12MassH1","inv12MassH1",500, 0., 3.);
  inv01MassWeightH1=new TH1F("inv01MassWeightH1","inv01MassWeightH1",500, 0., 3.);
  inv02MassWeightH1=new TH1F("inv02MassWeightH1","inv02MassWeightH1",500, 0., 3.);
  inv12MassWeightH1=new TH1F("inv12MassWeightH1","inv12MassWeightH1",500, 0., 3.);

  std::string unit=GlobalEnv::instance()->parser()->unitInFile();

  if(unit=="GEV"){
    _unitScaleFactor=1.;
  }
  else if(unit=="MEV"){
    _unitScaleFactor=1000.;
  }
  else{
    Alert << "unit " << unit << " does not exist!!!" <<endmsg;
    exit(0);
  }

  std::string order = GlobalEnv::instance()->parser()->orderInFile();
  if(order=="E Px Py Pz"){
    _energyFirst=true;
  }
  else if(order=="Px Py Pz E"){
    _energyFirst=false;
  }
  else{
    Alert << "order " << order << " does not exist!!!" <<endmsg;
    exit(0);
  }

  for(unsigned int i=0; i<_finalStateParticles.size(); i++){
     _fspMasses[i]=_finalStateParticles.at(i)->mass();
  }
}

PwaGen::~PwaGen()
{
  _theTFile->Write();
  _theTFile->Close();
  _stream->close();
}




std::shared_ptr<EventList> PwaGen::GeneratePspEventList(unsigned int numEvents){
  bool useMassRange = GlobalEnv::instance()->Channel()->useMassRange();

  std::vector< std::shared_ptr<MassRangeCut> > massRangeCuts= GlobalEnv::instance()->Channel()->massRangeCuts();
  std::vector< std::shared_ptr<MassRangeCut> >::iterator itMassRangeCut;

  std::shared_ptr<EventList> eventList(new EventList);

  for(unsigned int i=0; i<numEvents; i++){
     EvtVector4R p4[_finalStateParticles.size()];
     EvtGenKine::PhaseSpace(_finalStateParticles.size(), _fspMasses, p4, _initial4Vec.mass());
     for(unsigned int j=0; j<_finalStateParticles.size(); j++){
	p4[j].applyBoostTo(_initial4Vec);
     }

     bool acceptEvt=true;
     if(useMassRange){
       for (itMassRangeCut=massRangeCuts.begin(); itMassRangeCut!=massRangeCuts.end(); ++itMassRangeCut){
	 EvtVector4R particleSystem(0,0,0,0);
	 std::vector<unsigned int> particleIndices=(*itMassRangeCut)->particleIds();
	 for(auto it = particleIndices.begin(); it!=particleIndices.end(); ++it){
	   particleSystem = particleSystem + p4[*it];
	 }
	 double invMass = particleSystem.mass();
//         Info << "invMass: " << invMass << endmsg; 
	 if(invMass < (*itMassRangeCut)->massMin() || invMass > (*itMassRangeCut)->massMax()){
	   acceptEvt=false;
//	   Info << "event not accepted" << endmsg; 
	   break;
	 }
       }
     } 

     if(acceptEvt){
       Info << "event no " << i << " accepted" << endmsg;
       AddEventToEventList(eventList, p4, i);
       
       inv01MassH1->Fill((p4[0]+p4[1]).mass());
       if(_finalStateParticles.size()>2){
	 inv02MassH1->Fill((p4[0]+p4[2]).mass());
	 inv12MassH1->Fill((p4[1]+p4[2]).mass());
       }
     }
     else{
       i--;
     }
  }
     
    eventList->rewind();
  return eventList;
}


void PwaGen::generate(std::shared_ptr<AbsLh> theLh, std::shared_ptr<AbsPawianParameters> theFitParams){

  Info << "\n******** PAWIAN Event Generator **********\n" << endmsg;

  EvtMTRandomEngine myRandom(GlobalEnv::instance()->parser()->randomSeed());
  EvtRandom::setRandomEngine(&myRandom);
  bool generateEvents=true;
  int noOfAcceptedEvts=0;
  int noOfEvtsToGenerate=GlobalEnv::instance()->parser()->noOfGenEvts();
  int noOfIterations=0;
  int currentEvtNo=0;
  _maxFitWeight=0.;

  theLh->updateFitParams(theFitParams);

  while(generateEvents){
    noOfIterations++;
    
    if(((!_genWithModel || _useEvtWeight) && noOfIterations == 1) || 
       (_genWithModel && !_useEvtWeight && noOfIterations == 2)){
       Info << "Starting event generation" << endmsg;
    }
    if(_genWithModel && !_useEvtWeight && noOfIterations == 1){
       Info << "Getting max weight" << endmsg;
    }   

    Event* anEvent;
    std::shared_ptr<EventList> currentEvtList = GeneratePspEventList(EVENTS_PER_ITERATION);
    std::shared_ptr<EvtDataBaseList> eventListPtr(new EvtDataBaseList(0));
    
    while ((anEvent = currentEvtList->nextEvent()) !=0){
      currentEvtNo++;
      std::shared_ptr<EvtData> currentEvtData(eventListPtr->convertEvent(anEvent, currentEvtNo));

      if(!_genWithModel){
	  DumpEventToAsciiFile(currentEvtData); 
	  noOfAcceptedEvts++;
      }
      else if(_genWithModel && _useEvtWeight){
	 double fitWeight= theLh->calcEvtIntensity(currentEvtData.get(), theFitParams);
	 DumpEventToAsciiFile(currentEvtData, fitWeight);
	 noOfAcceptedEvts++;
      }
      else if(_genWithModel && !_useEvtWeight){
	 double fitWeight= theLh->calcEvtIntensity(currentEvtData.get(), theFitParams);
	 UpdateMaxFitWeight(fitWeight, noOfIterations);
	 
	 if(noOfIterations > 1){
	    double randWeight = EvtRandom::Flat(0., _maxFitWeight );
	    if (randWeight < fitWeight){
	       DumpEventToAsciiFile(currentEvtData);
	       noOfAcceptedEvts++;
	    }
	 }	 
      }

      if(noOfAcceptedEvts==noOfEvtsToGenerate){
	 generateEvents=false;
	 break;
      }
    }

    Info << "Iteration " << noOfIterations << " finished. Accepted events:\t" << noOfAcceptedEvts << endmsg;

    currentEvtList->rewind();
    currentEvtList->removeAndDeleteEvents(0,currentEvtList->size()); 
  }
}



void PwaGen::AddEventToEventList(std::shared_ptr<EventList> evtList, EvtVector4R* evt4Vec4Rs,int evtNumber, double weight){
  Event* newEvent = new Event();
  newEvent->addWeight(weight);
  for(unsigned int t=0; t<_finalStateParticles.size(); ++t){
    newEvent->addParticle(evt4Vec4Rs[t].get(0), evt4Vec4Rs[t].get(1), evt4Vec4Rs[t].get(2), evt4Vec4Rs[t].get(3));
  }

  evtList->add(newEvent);
}


void PwaGen::DumpEventToAsciiFile(std::shared_ptr<EvtData> evtData, double weight){

  std::vector<Particle* > fsParticles = GlobalEnv::instance()->Channel()->finalStateParticles();
  std::vector<Vector4<double>> current4Vecs;

  if(_useEvtWeight && _genWithModel){
      *_stream << weight << std::endl;
  }

  for(auto fspIter = fsParticles.begin(); fspIter != fsParticles.end(); ++fspIter ) {
    std::string currentName=(*fspIter)->name();
    Vector4<double> tmp4vec = evtData->FourVecsId.at(IdStringMapRegistry::instance()->stringId(currentName));
    current4Vecs.push_back(tmp4vec);
    if(_energyFirst){
      *_stream << std::setprecision(8)  << tmp4vec.E()*_unitScaleFactor  << "\t" 
	       << tmp4vec.Px()*_unitScaleFactor << "\t" << tmp4vec.Py()*_unitScaleFactor << "\t" 
	       << tmp4vec.Pz()*_unitScaleFactor << "\t" << std::endl;
    }
    else{
      *_stream << std::setprecision(8)  << tmp4vec.Px()*_unitScaleFactor << "\t" 
	       << tmp4vec.Py()*_unitScaleFactor << "\t" << tmp4vec.Pz()*_unitScaleFactor << "\t" 
	       << tmp4vec.E()*_unitScaleFactor << std::endl;
    }
  }

  inv01MassWeightH1->Fill((current4Vecs.at(0)+current4Vecs.at(1)).M(), weight);
  if(_finalStateParticles.size()>2){
    inv02MassWeightH1->Fill((current4Vecs.at(0)+current4Vecs.at(2)).M(), weight);
    inv12MassWeightH1->Fill((current4Vecs.at(1)+current4Vecs.at(2)).M(), weight);
  }
}



void PwaGen::UpdateMaxFitWeight(double weight, int currentIteration){
   if(_maxFitWeight < weight){
      _maxFitWeight = weight;

      if(currentIteration <= 1){
	 Info << "Current max weight = " << _maxFitWeight << endmsg;
      }
      else{
	 Warning << "Raised max weight to " << _maxFitWeight << " while generating!" << endmsg;
      }
   }
}