//************************************************************************//
//									  //
//  Copyright 2013 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/>.	  //
//									  //
//************************************************************************//

// IsobarTensorPsiToGamXDecay class definition file. -*- C++ -*-
// Copyright 2014 Bertram Kopf

#include <getopt.h>
#include <fstream>
#include <algorithm>

#include "PwaUtils/IsobarTensorPsiToGamXDecay.hh"
#include "qft++/relativistic-quantum-mechanics/Utils.hh"
#include "qft++Extension/PawianUtils.hh"
#include "ErrLogger/ErrLogger.hh"
#include "Particle/Particle.hh"
#include "Utils/PawianCollectionUtils.hh"
#include "Utils/FunctionUtils.hh"
#include "PwaUtils/KinUtils.hh"
#include "PwaUtils/EvtDataBaseList.hh"
#include "PwaUtils/GlobalEnv.hh"
#include "PwaUtils/AbsDynamics.hh"
#include "PwaUtils/DynRegistry.hh"
#include "PwaUtils/ProdChannelInfo.hh"
#include "ConfigParser/ParserBase.hh"

IsobarTensorPsiToGamXDecay::IsobarTensorPsiToGamXDecay(Particle* mother, Particle* daughter1_gamma, 
						       Particle* daughter2, ChannelID channelID) :
  IsobarTensorDecay(mother, daughter1_gamma, daughter2, channelID, "IsobarTensorPsiToGamXDecay")
  ,_XisEven(false)
  ,_noOfAmps(0)
{
  if(mother->twoIso() !=0 || mother->theParity() != -1 || mother->twoJ() != 2 || mother->theParity() != -1 
     ||  mother->theCParity() || mother->mass() < 0.01){
    Alert << "mother particle does not have the quantum number combinations JPC=1-- "
	  << "or its mass is below 0.01 GeV/c2 !!!" << endmsg;
    exit(0); 
  } 

  if(daughter1_gamma->twoJ() != 2 || daughter1_gamma->theParity() != -1 ||  daughter1_gamma->theCParity() 
     || daughter1_gamma->mass() > 1.e-6){
    Alert << "daughter particle 1 is not a photon" << endmsg;
    exit(0); 
  } 
  if(daughter2->twoJ()%4 == 0) _XisEven=true;

  if(!_XisEven && mother->theParity() == -1 && daughter2->twoJ()>=6){
    Alert << "radiative decays of Psi -> odd^- + gamma with J>=3 are not supported so far" << endmsg;
    exit(0);
  }

  if(!_XisEven && mother->theParity() == 1 && daughter2->twoJ()>=6){
    Alert << "radiative decays of Psi -> odd^+ + gamma with J>=3 are not supported so far" << endmsg;
    exit(0);
  }

  if (daughter2->twoJ()==0) _noOfAmps=1;
  else if(daughter2->twoJ()==2) _noOfAmps=2;
  else _noOfAmps=3;  

  InfoMsg << "daughter2->twoJ()%2 = " << daughter2->twoJ()%2 << "_XisEven: " << _XisEven 
	  <<  "  daughter2->theParity(): " << daughter2->theParity() << endmsg;
  fillAmpLMap();  
}

IsobarTensorPsiToGamXDecay::IsobarTensorPsiToGamXDecay(std::shared_ptr<const IGJPC> motherIGJPCPtr, 
						       Particle* daughter1_gamma, Particle* daughter2, 
						       ChannelID channelID, std::string motherName) :
  IsobarTensorDecay(motherIGJPCPtr, daughter1_gamma, daughter2, channelID, motherName, "IsobarTensorPsiToGamXDecay")
  ,_XisEven(false)
{ 
  if(motherIGJPCPtr->I != 0 || motherIGJPCPtr->G != -1 ||  motherIGJPCPtr->J != 1 
     || motherIGJPCPtr->P != -1 || motherIGJPCPtr->C != -1 ){
    Alert << "mother particle does not have the quantum number combinations IG(JPC)=0-(1--) !!!" << endmsg;
    exit(0);
  }

  if(daughter1_gamma->twoJ() != 2 || daughter1_gamma->theParity() != -1 
     ||  daughter1_gamma->theCParity() !=-1 || daughter1_gamma->mass() > 1.e-6){
    Alert << "daughter particle 1 is not a photon" << endmsg;
    Alert << "daughter1_gamma->twoJ(): " << daughter1_gamma->twoJ() << "\n"
	  << "daughter1_gamma->theParity(): " << daughter1_gamma->theParity() << "\n"
	  << "daughter1_gamma->theCParity(): " << daughter1_gamma->theCParity() << "\n"
	  << "daughter1_gamma->mass(): " << daughter1_gamma->mass() << endmsg;
    exit(0); 
  }

  if(daughter2->twoJ()%4 ==0) _XisEven=true;

  if(!_XisEven && motherIGJPCPtr->P == -1 && daughter2->twoJ()>=6){
    Alert << "radiative decays of Psi -> odd^- + gamma with J>=3 are not supported so far" << endmsg;
    exit(0);
  }

  if(!_XisEven && motherIGJPCPtr->P == 1 && daughter2->twoJ()>=6){
    Alert << "radiative decays of Psi -> odd^+ + gamma with J>=3 are not supported so far" << endmsg;
    exit(0);
  }

  if (daughter2->twoJ()==0) _noOfAmps=1;
  else if(daughter2->twoJ()==2) _noOfAmps=2;
  else _noOfAmps=3;

  InfoMsg << "daughter2->twoJ()%2 = " << daughter2->twoJ()%2 << "_XisEven: " << _XisEven 
	  <<  "  daughter2->theParity(): " << daughter2->theParity() << endmsg;
  fillAmpLMap();  
}

IsobarTensorPsiToGamXDecay::~IsobarTensorPsiToGamXDecay(){
}

void IsobarTensorPsiToGamXDecay::print(std::ostream& os) const{
  os << "\nJPCLS tensor amplitudes for decay\t" << _name << ":\n";
  os << "suffix for fit parameter name:\t" << _fitParamSuffix << "\n";

  std::vector< std::shared_ptr<const JPCLS> >::const_iterator it;
  for (it = _JPCLSDecAmps.begin(); it!= _JPCLSDecAmps.end(); ++it){
    (*it)->print(os);
    os << "\n";
  }

  AbsDecay::print(os);
  os << "\n";
}

void IsobarTensorPsiToGamXDecay::fillWignerDs(std::map<std::string, Vector4<double> >& fsMap, 
					      Vector4<double>& prodParticle4Vec, EvtData* evtData){

 int evtNo=evtData->evtNo;
  std::map<int, bool>::const_iterator it = _alreadyFilledMap.find(evtNo);
  if(it!=_alreadyFilledMap.end() &&  it->second) return; //already filled

  Vector4<double> all4Vec(0.,0.,0.,0.);
  //fill all4Vec
  std::map<std::string , Vector4<double> >::iterator itMap;
  for(itMap=fsMap.begin(); itMap!=fsMap.end(); ++itMap){
    all4Vec+=itMap->second;
  }

  //fill daughter1 and daughter2 4Vec
  Vector4<double> daughter1Gam_4Vec(0.,0.,0.,0.);

  itMap=fsMap.find( _daughter1->name() );
  if(itMap!=fsMap.end())  daughter1Gam_4Vec=itMap->second;
  else {
    Alert << "particle with the name " << _daughter1->name() 
	  << " can not be found in the list of final state particles!!!" << endmsg;
    exit(0);     
  }

  Vector4<double> daughter2_4Vec(0.,0.,0.,0.);

  std::vector<Particle*> finalStatePart2;
  if(0!=_absDecDaughter2) finalStatePart2=_absDecDaughter2->finalStateParticles();
  else finalStatePart2.push_back(_daughter2);

  std::vector<Particle*>::iterator itParticle;
  for (itParticle= finalStatePart2.begin(); itParticle != finalStatePart2.end(); ++itParticle){
       itMap=fsMap.find( (*itParticle)->name() );
       daughter2_4Vec+=itMap->second;
  }

  if(_isProdAmp){
    if (!_daughter2IsStable) _absDecDaughter2->fillWignerDs(fsMap, daughter2_4Vec, evtData);
  }
  else{
    if (!_daughter2IsStable) _absDecDaughter2->fillWignerDs(fsMap, prodParticle4Vec, evtData);
  }


  Vector4<double> mother_4Vec=daughter1Gam_4Vec+daughter2_4Vec;

  Vector4<double> daughter1GamTensor4Vec=daughter1Gam_4Vec;
  Vector4<double> daughter2Tensor4Vec=daughter2_4Vec;
  Vector4<double> motherTensor4Vec=mother_4Vec;

  daughter1GamTensor4Vec.Boost(all4Vec);
  daughter2Tensor4Vec.Boost(all4Vec);
  motherTensor4Vec.Boost(all4Vec);

  Vector4<double> daughter2HelMother(0.,0.,0.,0.);
  if(_hasMotherPart){
    if(fabs(mother_4Vec==all4Vec)){
      daughter2HelMother=daughter2_4Vec;
      daughter2HelMother.Boost(daughter2HelMother); //is this correct????
    }
    else daughter2HelMother=helicityVec(prodParticle4Vec, mother_4Vec, daughter2_4Vec);
  }
  else{
    daughter2HelMother=daughter2_4Vec;
    daughter2HelMother.Boost(mother_4Vec);
  }

  Spin spinMother(1);
  Spin spinDaughter1Gam(1);
  Spin spinDaughter2=_daughter2IGJPCPtr->J;

  double motherMass=motherTensor4Vec.M();
  if (motherMass<1.e-5) motherMass=0.;
  else if(motherMass != motherMass) motherMass=0.;
  
  _polMother.SetP4(motherTensor4Vec, motherMass);


  double daughter1GamMass=0.;
  double daughter2Mass=daughter2Tensor4Vec.M();
  if(daughter2Mass!=daughter2Mass || daughter2Mass < 1.e-5) daughter2Mass=0.;

  _polDaughter1.SetP4(daughter1GamTensor4Vec, daughter1GamMass);
  _polDaughter2.SetP4(daughter2Tensor4Vec, daughter2Mass);
  Tensor<complex<double> > S_alpha_beta= (_lctTensor*daughter1GamTensor4Vec)*motherTensor4Vec;

  Tensor<complex<double> > currentAmp1;
  Tensor<complex<double> > currentAmp2;
  Tensor<complex<double> > currentAmp3;

  Spin lamMotherMax=spinMother;

  for (Spin lamMother=-lamMotherMax; lamMother<=lamMotherMax; ++lamMother){
    for (Spin lam1Gam=-1; lam1Gam<=1; lam1Gam+=2){
      Tensor<complex<double> > PsiGamPolProj=_polMother(lamMother)%conj(_polDaughter1(lam1Gam));

      for (Spin mX=-spinDaughter2; mX<=spinDaughter2; ++mX){
	Tensor<complex<double> > PpsySpinProjX_jg0=conj(_polDaughter2(mX));
	Tensor<complex<double> > PpsySpinProjX_jg1=conj(_polDaughter2(mX));	
	Tensor<complex<double> > PpsySpinProjX_jg2=conj(_polDaughter2(mX));

	if(spinDaughter2>0){
	  //J contractions
	  for (int i=0; i<spinDaughter2; ++i) PpsySpinProjX_jg0*=motherTensor4Vec;
	}
	if(spinDaughter2>1){
	  //J-1 contractions
	  for (int i=0; i<spinDaughter2-1; ++i) PpsySpinProjX_jg1*=motherTensor4Vec;
	}

	if(spinDaughter2>2){
	  //J-2 contractions
	  for (int i=0; i<spinDaughter2-2; ++i) PpsySpinProjX_jg2*=motherTensor4Vec;
	}

	DebugMsg << "PpsySpinProjX_jg0.Rank(): " << PpsySpinProjX_jg0.Rank() << "\nval: " << PpsySpinProjX_jg0 << endmsg;
	DebugMsg << "PpsySpinProjX_jg1.Rank(): " << PpsySpinProjX_jg1.Rank() << "\nval: " << PpsySpinProjX_jg1 << endmsg;
	DebugMsg << "PpsySpinProjX_jg2.Rank(): " << PpsySpinProjX_jg2.Rank() << "\nval: " << PpsySpinProjX_jg2 << endmsg;

	//case J^PC(X)=0+, 2+, 4+, ... and 1-, 3-, ...
	if((_XisEven && _daughter2IGJPCPtr->P==1) || (!_XisEven && _daughter2IGJPCPtr->P==-1) ){ //0++, 2++, 4++, ...
	  //first amplitude
	  MetricTensor g_munu;
 	  
	  currentAmp1=(PsiGamPolProj | g_munu) * PpsySpinProjX_jg0;

	  DebugMsg << "currentAmp1.Rank(): " << currentAmp1.Rank() << "\nval: " << currentAmp1 << endmsg;

	  Id3StringType IdLamMotherLamGamLamX=FunctionUtils::spin3Index(lamMother, lam1Gam, mX);
	  evtData->ComplexN3Spin[_nameId][0][IdLamMotherLamGamLamX]=currentAmp1(0);
	  if(_noOfAmps>1){
	    //second amplitude
	    currentAmp2=(_polMother(lamMother)*daughter1GamTensor4Vec)*( conj(_polDaughter1(lam1Gam)) | PpsySpinProjX_jg1 ); 

	    DebugMsg << "currentAmp2.Rank(): " << currentAmp2.Rank() << "\nval: " << currentAmp2 << endmsg;
	    
	    evtData->ComplexN3Spin[_nameId][1][IdLamMotherLamGamLamX]=currentAmp2(0);
	    if(_noOfAmps>2){
	      //3. amplitude
	      currentAmp3= PsiGamPolProj | PpsySpinProjX_jg2;
	      DebugMsg << "currentAmp3.Rank(): " << currentAmp3.Rank() << "\nval: " << currentAmp3 << endmsg;
	      evtData->ComplexN3Spin[_nameId][2][IdLamMotherLamGamLamX]=currentAmp3(0);
	    }
	  }  	  
	}

	//case J^PC(X)=0-, 2-, 4-, ... and 1+, 3+, ...
	if((_XisEven && _daughter2IGJPCPtr->P==-1) || (!_XisEven && _daughter2IGJPCPtr->P==1)){ //0-, 2-, 4-, ...
	  //if(_XisEven && _daughter2IGJPCPtr->P==-1){
	    //1. amplitude 
	    Tensor<complex<double> > U_numu1;
            U_numu1 = S_alpha_beta*PpsySpinProjX_jg0;
	    currentAmp1=PsiGamPolProj | U_numu1;
	    DebugMsg << "currentAmp1.Rank(): " << currentAmp1.Rank() << "\nval: " << currentAmp1(0) << endmsg;

	    Id3StringType IdLamMotherLamGamLamX=FunctionUtils::spin3Index(lamMother, lam1Gam, mX);
	    evtData->ComplexN3Spin[_nameId][0][IdLamMotherLamGamLamX]=currentAmp1(0);
	    
	    if(spinDaughter2>0){
	      //2. amplitude 
	      Tensor<complex<double> > U_numu2= daughter1GamTensor4Vec % (S_alpha_beta*PpsySpinProjX_jg1);
	      currentAmp2=PsiGamPolProj | U_numu2;
	      DebugMsg << "currentAmp2.Rank(): " << currentAmp2.Rank() << "\nval: " << currentAmp2(0) << endmsg;
	      evtData->ComplexN3Spin[_nameId][1][IdLamMotherLamGamLamX]=currentAmp2(0);

	      if(spinDaughter2>1){	      
		//3. amplitude ?????
		Tensor<complex<double> > U_numu3= (_lctTensor*(PpsySpinProjX_jg2*daughter1GamTensor4Vec))*motherTensor4Vec;
		currentAmp3=PsiGamPolProj | U_numu3;
		evtData->ComplexN3Spin[_nameId][2][IdLamMotherLamGamLamX]=currentAmp3(0);
	      }
	    }
	}

      }
    }
  }

  bool fillqVals=false;
  if(_isProdAmp && _useProdBarrier) fillqVals=true;
  else if(0!=_absDynPtr){
    if(_absDynPtr->type()=="BlattWBarrierTensorDynamics") fillqVals=true; 
  }
  
  if(fillqVals){
    double qVal=daughter2HelMother.P();
    double qValNorm=PawianQFT::breakupMomQDefault(mother_4Vec.M(), massSumFsParticlesDec1(), massSumFsParticlesDec2()).real();
    evtData->DoubleMassId[_wignerDqId]=qVal;
    evtData->DoubleMassId[_wignerDqNormId]=qValNorm;
  }
}


void IsobarTensorPsiToGamXDecay::fillAmpLMap(){

//case J^PC(X)=0+, 2+, 4+, ... and 1-, 3-, ...
  if((_XisEven && _daughter2IGJPCPtr->P==1) || (!_XisEven && _daughter2IGJPCPtr->P==-1) ){
    _ampLMap[0]=_daughter2IGJPCPtr->J;
    if(_noOfAmps>1) _ampLMap[1]=_daughter2IGJPCPtr->J;
    if(_noOfAmps>2) _ampLMap[2]=_daughter2IGJPCPtr->J-2; 
  }
  else{	//case J^PC(X)=0-, 2-, 4-, ... and 1+, 3+, ...
    _ampLMap[0]=_daughter2IGJPCPtr->J+1;
    if(_noOfAmps>1) _ampLMap[1]=_daughter2IGJPCPtr->J+1;
    if(_noOfAmps>2) _ampLMap[2]=_daughter2IGJPCPtr->J-1;
  }
}

void IsobarTensorPsiToGamXDecay::enableProdBarrier(){
  if(_dynEnabled){
    Alert << "dynamics already enabled for " << name() << endmsg;
    exit(1);
  }
  if(!_prodChannelInfo->isProductionChannel()){
    WarningMsg << name() << " is not a production amplitide! Barrier factors for the production can not be enabled!" << endmsg;
    return;
  }
  if(!_prodChannelInfo->withProdBarrier()){
    WarningMsg << name() << "production barrier disabled" << endmsg;
    return;
  }
  if(_prodChannelInfo->prodBarrierType()!="BlattWBarrierTensor"){
    Alert << name() << "production barrier enabled with type " << _prodChannelInfo->prodBarrierType() 
	  << "\tIsobarTensorPsiToGamXDecay only supports type BlattWBarrierTensor" << endmsg;
    exit(1);
  }


  if(!_isProdAmp){
    Alert << name() << " is not a production amplitide! Barrier factors for the production can not be enabled!" << endmsg;
    exit(1);
  }
  _useProdBarrier=true;
  _dynType="BlattWBarrierTensor";
  _qR=_prodChannelInfo->qRPod();
  InfoMsg << "Barrier factors for production amplitude " << name() << " enabled!" << endmsg;
  _absDynPtr=DynRegistry::instance()->getDynamics(shared_from_this()); 
  _dynEnabled=true;
}

