//____________________________________________________________________________
/*
 Copyright (c) 2003-2009, GENIE Neutrino MC Generator Collaboration
 For the full text of the license visit http://copyright.genie-mc.org
 or see $GENIE/LICENSE

 Author: Costas Andreopoulos <costas.andreopoulos \at stfc.ac.uk>
         STFC, Rutherford Appleton Laboratory - May 06, 2004

 For documentation see the corresponding header file.

 Important revisions after version 2.0.0 :

 @ Sep 19, 2007 - CA
   Copied here the nuclear density methods used by intranuke so that they
   can be used by other modules as well (eg position vertex selection)
 @ Nov 30, 2007 - CA
   Added possibility to increase the nuclear size (intranuke may increase the
   nuclear size by a const times the de Broglie wavelength of a transported
   hadron). Density() methods have a new default argument (ring) which is 0
   if not explicity set.

*/
//____________________________________________________________________________

#include <cstdlib>

#include <TMath.h>

#include "Conventions/Constants.h"
#include "Interaction/Interaction.h"
#include "Messenger/Messenger.h"
#include "Nuclear/FermiMomentumTablePool.h"
#include "Nuclear/FermiMomentumTable.h"
#include "PDG/PDGUtils.h"
#include "Utils/NuclearUtils.h"

using namespace genie;
using namespace genie::constants;

//____________________________________________________________________________
double genie::utils::nuclear::BindEnergy(const Target & target)
{
// Compute the average binding energy (in GeV) using the semi-empirical
// formula from Wapstra (Handbuch der Physik, XXXVIII/1)

  if(!target.IsNucleus()) return 0;

  double a = 15.835;
  double b =  18.33;
  double s =  23.20;
  double d =   0.714;

  double delta = 0;                       /*E-O*/
  if (target.IsOddOdd()  ) delta =  11.2; /*O-O*/
  if (target.IsEvenEven()) delta = -11.2; /*E-E*/

  double N = (double) target.N();
  double Z = (double) target.Z();
  double A = (double) target.A();

  double BE =  a * A
             - b * TMath::Power(A,0.667)
             - s * TMath::Power(N-Z,2.0)/A
             - d * TMath::Power(Z,2.0)/TMath::Power(A,0.333)
             - delta / TMath::Sqrt(A); // MeV

  return (1e-3 * BE); // GeV
}
//___________________________________________________________________________
double genie::utils::nuclear::BindEnergyPerNucleon(const Target & target)
{
// Compute the average binding energy per nucleon (in GeV)

  if(!target.IsNucleus()) return 0;

  return (utils::nuclear::BindEnergy(target) / target.A());
}
//___________________________________________________________________________
double genie::utils::nuclear::BindEnergyLastNucleon(const Target & target)
{
// Compute the binding for the most loose nucleon (in GeV)

  if(!target.IsNucleus()) return 0;

  //-- temporarily, return the binding energy per nucleon rather than the
  //   separation energy of the last nucleon
   return (utils::nuclear::BindEnergy(target) / target.A());
}
//___________________________________________________________________________
double genie::utils::nuclear::Radius(const Target & target)
{
// Compute the nuclear radius (in GeV^-1)
// (to get it in F, for example, use: Radius(target)/genie::units::fermi)

  return Radius(target.A());
}
//___________________________________________________________________________
double genie::utils::nuclear::Radius(int A)
{
// Compute the nuclear radius (in GeV^-1)
// (to get it in F, for example, use: Radius(target)/genie::units::fermi)

  if(A<1) return 0;

  double Rn = kNucRo * TMath::Power(A, 0.3333333); // in GeV^-1
  return Rn;
}
//___________________________________________________________________________
double genie::utils::nuclear::NuclQELXSecSuppression(
                string kftable, double pmax, const Interaction * interaction)
{
// Computes the suppression of the EL/QEL differential cross section due to
// nuclear effects.
//
// A direct adaptation of NeuGEN's qelnuc() and r_factor()
//
// Hugh's comments from the original code: 
// "This routine is based on an analytic calculation of the rejection factor 
//  in the Fermi Gas model using the form for the fermi momentum distribution 
//  given in the  Bodek and Ritchie paper.  [P.R.  D23 (1981) 1070]
//  R is the ratio of the differential cross section from the nuclear material 
//  specified by (kf,pf) to the differential cross section for a free nucleon".
//  (kf,pf = Fermi Gas model Fermi momentum for initial,final nucleons)
//
  const InitialState & init_state = interaction->InitState();
  const ProcessInfo &  proc_info  = interaction->ProcInfo();
  const Target &       target     = init_state.Tgt();

  if (!target.IsNucleus()) return 1.0; // no suppression for free nucleon tgt

  double R = 1; // computed suppression factor

  int target_pdgc         = target.Pdg();
  int struck_nucleon_pdgc = target.HitNucPdg();
  int final_nucleon_pdgc  = struck_nucleon_pdgc;

  if(proc_info.IsWeakCC()) 
          final_nucleon_pdgc = pdg::SwitchProtonNeutron(struck_nucleon_pdgc);

  // get the requested Fermi momentum table 
  FermiMomentumTablePool * kftp = FermiMomentumTablePool::Instance();
  const FermiMomentumTable * kft  = kftp->GetTable(kftable);

  // Fermi Gas model Fermi momentum for initial,final nucleons

  double kFi = kft->FindClosestKF(target_pdgc, struck_nucleon_pdgc);
  double kFf = (struck_nucleon_pdgc==final_nucleon_pdgc) ? kFi : 
               kft->FindClosestKF(target_pdgc, final_nucleon_pdgc );

  // Compute magnitude of the 3-momentum transfer to the nucleon

  double Mn2 = target.HitNucP4Ptr()->M2(); // can be off m/shell

  const Kinematics & kine = interaction->Kine();
  double q2     = kine.q2();
  double magq2  = q2 * (0.25*q2/Mn2 - 1.);
  double q      = TMath::Sqrt(TMath::Max(0.,magq2));

  double kfa   = kFi * 2./kPi;
  double kfa2  = TMath::Power(kfa,2);
  double kFi4  = TMath::Power(kFi,4);
  double rkf   = 1./(1. - kFi/4.);
  double alpha = 1. - 6.*kfa2;
  double beta  = 2. * rkf * kfa2 * kFi4;

  double fm_area = FmArea(alpha,beta,kFi,pmax);

  if (q <= kFf) {

     double p1   = kFf - q;
     double p2   = kFf + q;
     double fmi1 = FmI1(alpha,beta,p1,p2,  kFi,kFf,q);
     double fmi2 = FmI2(alpha,beta,p2,pmax,kFi,kFf,q);

     R = 2*kPi * (fmi1 + 2*fmi2) / fm_area;

  } else if (q > kFf && q <= (pmax-kFf)) {

     double p1    = q - kFf;
     double p2    = q + kFf;
     double fmi1  = FmI1(alpha,beta, p1, p2,   kFi, kFf,q);
     double fmi2a = FmI2(alpha,beta, 0., p1,   kFi, kFf,q);
     double fmi2b = FmI2(alpha,beta, p2, pmax, kFi, kFf,q);

     R = 2*kPi * (fmi1 + 2*(fmi2a+fmi2b)) / fm_area;

  } else if (q > (pmax-kFf) && q <= (pmax+kFf)) {

     double p1   = q - kFf;
     double fmi2 = FmI2(alpha,beta, 0.,p1,  kFi,kFf,q);
     double fmi1 = FmI1(alpha,beta, p1,pmax,kFi,kFf,q);

     R = 2*kPi * (2.*fmi2 + fmi1) / fm_area;

  } else if (q > (pmax+kFf)) {
     R = 1.; 

  } else {
     LOG("Nuclear", pFATAL) << "Illegal input q = " << q;
     exit(1);
  }
  return R;
}
//___________________________________________________________________________
double genie::utils::nuclear::FmI1(double alpha, double beta, 
                       double a, double b,  double kFi, double kFf, double q)
{
// Adapted from NeuGEN's fm_integral1() used in r_factor()

  double f=0;

  double q2   = TMath::Power(q,  2);
  double a2   = TMath::Power(a,  2);
  double b2   = TMath::Power(b,  2);
  double kFi2 = TMath::Power(kFi,2);
  double kFf2 = TMath::Power(kFf,2);

  if(kFi < a) {
     double lg = TMath::Log(b/a);

     f = -beta * (kFf2-q2)/(4.*q) * (1./a2 - 1./b2) + beta/(2.*q) * lg;

  } else if (kFi > b) {
     double a4 = TMath::Power(a2,2);
     double b4 = TMath::Power(b2,2);

     f = - (kFf2-q2) * alpha/(4.*q) * (b2-a2) + alpha/(8.*q) * (b4-a4);

  } else {
     double a4   = TMath::Power(a2,2);
     double kFi4 = TMath::Power(kFi2,2);
     double lg   = TMath::Log(b/kFi);

     f = - alpha*(kFf2-q2)/(4.*q)*(kFi2-a2) + alpha/(8.*q)*(kFi4 - a4)
         - beta*(kFf2-q2)/(4.*q)*(1./kFi2 - 1./b2) + beta/(2.*q)*lg;
  }

  double integral2 = FmI2(alpha,beta,a,b,kFi,kFf,q);
  double integral1 = integral2 + f;
  
  return integral1;
}
//___________________________________________________________________________
double genie::utils::nuclear::FmI2(double alpha, double beta,
               double a, double b,  double kFi, double /*kFf*/, double /*q*/)
{
// Adapted from NeuGEN's fm_integral2() used in r_factor()

  double integral2 = 0;

  if(kFi < a) {
     integral2 = beta * (1./a - 1./b);

  } else if(kFi > b) {
     double a3 = TMath::Power(a,3);
     double b3 = TMath::Power(b,3);
     integral2 = alpha/3. * (b3 - a3);

  } else {
     double a3   = TMath::Power(a,3);
     double kFi3 = TMath::Power(kFi,3);

     integral2 = alpha/3. * (kFi3 - a3) + beta * (1./kFi - 1./b);
  }
  return integral2;
}
//___________________________________________________________________________
double genie::utils::nuclear::FmArea(
                         double alpha, double beta, double kf, double pmax)
{
// Adapted from NeuGEN's fm_area() used in r_factor()

  double kf3 = TMath::Power(kf,3.);
  double sum = 4.*kPi* (alpha * kf3/3. + beta*(1./kf - 1./pmax));
  return sum;
}
//___________________________________________________________________________
double genie::utils::nuclear::DISNuclFactor(double x, int A)
{
// Adapted from NeuGEN's nuc_factor(). Kept original comments from Hugh.

  double xv     = TMath::Min(0.75, x);
  double xv2    = xv  * xv;
  double xv3    = xv2 * xv;
  double xv4    = xv3 * xv;
  double xv5    = xv4 * xv;
  double xvp    = TMath::Power(xv, 14.417);
  double expaxv = TMath::Exp(-21.94*xv);

  double f = 1.;

  // first factor goes from free nucleons to deuterium
  if(A >= 2) {
    f= 0.985*(1.+0.422*xv - 2.745*xv2 + 7.570*xv3 - 10.335*xv4 + 5.422*xv5);
  }
  // 2nd factor goes from deuterium to iso-scalar iron
  if(A > 2) {
    f *= (1.096 - 0.364*xv - 0.278*expaxv + 2.722*xvp);
  }
  return f;
}
//___________________________________________________________________________
double genie::utils::nuclear::RModelMod(double x, double Q2)
{
// Adapted from NeuGEN's rmodel_mod()
//
// Hugh's comments in original code:
//   from NuTeV code provided by Donna Naples, May 2005
//   added form for R below qsq=.35 GEV**2 from hep-ex/030807
//
// NuTEV comments
//C-------------------------------------------------------------------
//C  Revised to make HTWIST select between more than two different
//C  values of R.
//C
//C     HTWIST = 'F'     ==> RQCD   - WITH LIMIT R > (2MX)**2/Q2
//C     HTWIST = 'T'     ==> RSLAC  - WITH LIMIT R > (2MX)**2/Q2
//C     HTWIST = '0'     ==> R = 0 (NOT TRUE, BUT USEFUL FOR STUDIES)
//C     HTWIST = '2'     ==> R =.2 (NOT TRUE, BUT USEFUL FOR STUDIES)
//C     HTWIST = 'C'     ==> R-CALLAN-GROSS = (2MX)**2/Q2
//C
//C     HTWIST = 'W'     ==> R PARAMETERIZATION FROM WHITLOW'S THESIS
//C     HTWIST = '+'     ==> R PARAMETRIZATION FROM WHITLOW +15%
//C     HTWIST = 'P'     ==> R PARAMETRIZATION FROM WHITLOW +0.03
//C     HTWIST = 'M'     ==> R PARAMETRIZATION FROM WHITLOW -0.03
//C
//C----------------------------------------22-DEC-92 WGS--------------
//
  const double C2 = TMath::Power(0.125, 2);
  const double B1 =  0.0635;
  const double B2 =  0.5747;
  const double B3 = -0.3534;

  double Q2R   = TMath::Max(Q2, 0.35);
  double ss    = TMath::Log(Q2R/.04);

  double x2    = TMath::Power(x,   2.);
  double Q4R   = TMath::Power(Q2R, 2.);
  double Q4    = TMath::Power(Q2,  2.);

  double theta = 1. + (12.*Q2R/(Q2R+1.)) * (C2/(C2+x2));
  double R     = (B1/ss)*theta + B2/Q2R + B3/(Q4R+.09);

  R = TMath::Max(R,0.);

  if(Q2 < 0.35)  {
      R *= ( 3.207*(Q2/(Q4+1.)) );
  }
  return R;
}
//___________________________________________________________________________
double genie::utils::nuclear::Density(double r, int A, double ring)
{
// [by S.Dytman]
//
  if(A>20) {
    double c = 1., z = 1.;

    if      (A ==  27) { c = 3.07; z = 0.52; }  // aluminum
    else if (A ==  28) { c = 3.07; z = 0.54; }  // silicon
    else if (A ==  40) { c = 3.53; z = 0.54; }  // argon
    else if (A ==  56) { c = 4.10; z = 0.56; }  // iron
    else if (A == 208) { c = 6.62; z = 0.55; }  // lead
    else { 
       c = TMath::Power(A,0.35); z = 0.54; 
    } //others

    LOG("Nuclear",pINFO)
	<< "r= " << r << ", ring= " << ring;
    double rho = DensityWoodsSaxon(r,c,z,ring);
    return rho;
  }
  else if (A>4) {
    double ap = 1., alf = 1.;

    if      (A ==  7) { ap = 1.77; alf = 0.327; } // lithium
    else if (A == 12) { ap = 1.69; alf = 1.08;  } // carbon
    else if (A == 14) { ap = 1.76; alf = 1.23;  } // nitrogen
    else if (A == 16) { ap = 1.83; alf = 1.54;  } // oxygen
    else  { 
      ap=1.75; alf=-0.4+.12*A; 
    }  //others- alf=0.08 if A=4

    double rho = DensityGaus(r,ap,alf,ring);
    return rho;
  }
  else {
    // helium
    double ap = 1.9/TMath::Sqrt(2.);  
    double alf=0.;    
    double rho = DensityGaus(r,ap,alf,ring);
    return rho;
  }

  return 0;
}
//___________________________________________________________________________
double genie::utils::nuclear::DensityGaus(
                             double r, double a, double alf, double ring)
{
// [adapted from neugen3 density_gaus.F written by S.Dytman]
//
// Modified harmonic osc density distribution. 
// Norm gives normalization to 1
//
// input  : radial distance in nucleus [units: fm]
// output : nuclear density            [units: fm^-3]

  ring = TMath::Min(ring, 0.3*a);

  double aeval = a + ring;
  double norm  = 1./((5.568 + alf*8.353)*TMath::Power(a,3.));  //0.0132;
  double b     = TMath::Power(r/aeval, 2.);
  double dens  = norm * (1. + alf*b) * TMath::Exp(-b);

  LOG("Nuclear", pINFO) 
        << "r = " << r << ", norm = " << norm << ", dens = " << dens << 
", aeval= " << aeval;

  return dens;
}
//___________________________________________________________________________
double genie::utils::nuclear::DensityWoodsSaxon(
                              double r, double c, double z, double ring)
{
// [adapted from neugen3 density_ws.F written by S.Dytman]
//
// Woods-Saxon desity distribution. Norn gives normalization to 1
//
// input  : radial distance in nucleus [units: fm]
// output : nuclear density            [units: fm^-3]

  LOG ("Nuclear",pINFO)
	<< "c= " << c << ", z= " << z << ",ring= " << ring;
  ring = TMath::Min(ring, 0.75*c);

  double ceval = c + ring;
  double norm  = (3./(4.*kPi*TMath::Power(c,3)))*1./(1.+TMath::Power((kPi*z/c),2));
  double dens  = norm / (1 + TMath::Exp((r-ceval)/z));

  LOG("Nuclear", pINFO) 
        << "r = " << r << ", norm = " << norm << ", dens = " << dens << 
" , ceval= " << ceval;

  return dens;
}
//___________________________________________________________________________

