//
// ********************************************************************
// * License and Disclaimer                                           *
// *                                                                  *
// * The  Geant4 software  is  copyright of the Copyright Holders  of *
// * the Geant4 Collaboration.  It is provided  under  the terms  and *
// * conditions of the Geant4 Software License,  included in the file *
// * LICENSE and available at  http://cern.ch/geant4/license .  These *
// * include a list of copyright holders.                             *
// *                                                                  *
// * Neither the authors of this software system, nor their employing *
// * institutes,nor the agencies providing financial support for this *
// * work  make  any representation or  warranty, express or implied, *
// * regarding  this  software system or assume any liability for its *
// * use.  Please see the license in the file  LICENSE  and URL above *
// * for the full disclaimer and the limitation of liability.         *
// *                                                                  *
// * This  code  implementation is the result of  the  scientific and *
// * technical work of the GEANT4 collaboration.                      *
// * By using,  copying,  modifying or  distributing the software (or *
// * any work based  on the software)  you  agree  to acknowledge its *
// * use  in  resulting  scientific  publications,  and indicate your *
// * acceptance of all terms of the Geant4 Software license.          *
// ********************************************************************
//
//
// Author: Mathieu Karamitros
//
// WARNING : This class is released as a prototype.
// It might strongly evolve or even disapear in the next releases.
//
// History:
// -----------
// 10 Oct 2011 M.Karamitros created
//
// -------------------------------------------------------------------

#include <algorithm>
#include "G4DNAOneStepThermalizationModel.hh"
#include "globals.hh"
#include "G4Exp.hh"
#include "G4RandomDirection.hh"
#include "G4Electron.hh"
#include "G4EmParameters.hh"

//------------------------------------------------------------------------------

namespace DNA {
namespace Penetration {
  
const double
Meesungnoen2002::gCoeff[13] =
{ -4.06217193e-08,  3.06848412e-06,  -9.93217814e-05,
  1.80172797e-03,  -2.01135480e-02,   1.42939448e-01,
  -6.48348714e-01,  1.85227848e+00,  -3.36450378e+00,
  4.37785068e+00,  -4.20557339e+00,   3.81679083e+00,
  -2.34069784e-01 };
// fit from Meesungnoen, 2002

const double
Meesungnoen2002_amorphous::gCoeff[7] =
{ 7.3144e-05,	-2.2474e-03,	3.4555e-02,
  -4.3574e-01,	2.8954e+00,	-1.0381e+00,
  1.4300e+00 };
// fit from Meesungnoen, 2002

const double
Terrisol1990::gEnergies_T1990[11] =
{ 0.2, 0.5, 1, 2, 3, 4, 5, 6, 7,
  // The two last are not in the dataset
  8, 9}; // eV

const double
Terrisol1990::gStdDev_T1990[11] =
{ 17.68*CLHEP::angstrom,
  22.3*CLHEP::angstrom,
  28.49*CLHEP::angstrom,
  45.35*CLHEP::angstrom,
  70.03*CLHEP::angstrom,
  98.05*CLHEP::angstrom,
  120.56*CLHEP::angstrom,
  132.73*CLHEP::angstrom,
  142.60*CLHEP::angstrom,
  // the above value as given in the paper's table does not match
  // b=27.22 nm nor the mean value. 129.62*CLHEP::angstrom could be
  // a better fit.
  //
  // The two last are made up
  137.9*CLHEP::angstrom,
  120.7*CLHEP::angstrom
}; // angstrom

//----------------------------------------------------------------------------

double Meesungnoen2002::GetRmean(double k){
  G4double k_eV = k/eV;

  if(k_eV>0.1){ // data until 0.2 eV
    G4double r_mean = 0;
    for(int8_t i=12; i!=-1 ; --i){
      r_mean+=gCoeff[12-i]*std::pow(k_eV,i);
    }
    r_mean*=CLHEP::nanometer;
    return r_mean;
  }
  return 0;
}

double Meesungnoen2002_amorphous::GetRmean(double k){
  G4double k_eV = k/eV;

  if(k_eV>0.1){ // data until 0.2 eV
	G4double r_mean = 0;
	for(int8_t i=6; i!=-1 ; --i){
	  r_mean+=gCoeff[6-i]*std::pow(k_eV,i);
	}
	r_mean*=CLHEP::nanometer;
	return r_mean;
  }
  return 0;
}

void GetGaussianPenetrationFromRmean3D(G4double r_mean,
                                       G4ThreeVector& displacement)
{
  if(r_mean == 0)
  {
    // rare events:
    // prevent H2O and secondary electron from being placed at the same position
    displacement = G4RandomDirection() * (1e-3*CLHEP::nanometer);
    return;
  }
  
  static constexpr double convertRmean3DToSigma1D = 0.62665706865775006;
  // = sqrt(CLHEP::pi)/pow(2,3./2.)
    
  // Use r_mean to build a 3D gaussian
  const double sigma1D = r_mean * convertRmean3DToSigma1D;
  displacement = G4ThreeVector(G4RandGauss::shoot(0, sigma1D),
                               G4RandGauss::shoot(0, sigma1D),
                               G4RandGauss::shoot(0, sigma1D));
}
  
void Meesungnoen2002::GetPenetration(G4double k,
                                     G4ThreeVector& displacement)
{
  GetGaussianPenetrationFromRmean3D(GetRmean(k), displacement);
}

void Meesungnoen2002_amorphous::GetPenetration(G4double k,
                                     G4ThreeVector& displacement)
{
  GetGaussianPenetrationFromRmean3D(GetRmean(k), displacement);
}


void Kreipl2009::GetPenetration(G4double k,
                                     G4ThreeVector& displacement)
{
	G4double r_mean = Meesungnoen2002::GetRmean(k);

  if(r_mean == 0)
  {
	// rare events:
	// prevent H2O and secondary electron from being placed at the same position
	displacement = G4RandomDirection() * (1e-3*CLHEP::nanometer);
	return;
  }

  double r = G4RandGamma::shoot(2,2);

  displacement = G4RandomDirection() * r * r_mean;
}
//----------------------------------------------------------------------------

void Ritchie1994::GetPenetration(G4double k,
                                 G4ThreeVector& displacement)
{
  GetGaussianPenetrationFromRmean3D(k/eV * 1.8 * nm, // r_mean
                                    displacement);
}

//----------------------------------------------------------------------------

double Terrisol1990::Get3DStdDeviation(double energy){
  G4double k_eV = energy/eV;
  if(k_eV < 0.2){
    // rare events:
    //  prevent H2O and secondary electron to be at the spot
    return 1e-3*CLHEP::nanometer;
  }
  if(k_eV == 9.){
    return gStdDev_T1990[10];
  }
  if(k_eV > 9.){
    G4ExceptionDescription description;
    description << "Terrisol1990 is not tabulated for energies greater than 9eV";
    G4Exception("Terrisol1990::Get3DStdDeviation",
                "INVALID_ARGUMENT",
                FatalErrorInArgument,
                description);
  }

  size_t lowBin, upBin;

  if(k_eV >= 1.){
    lowBin=std::floor(k_eV)+1;
    upBin=std::min(lowBin+1, size_t(10));
  }
  else{
    auto it=std::lower_bound(&gEnergies_T1990[0],
                             &gEnergies_T1990[2],
                             k_eV);
    lowBin = it-&gEnergies_T1990[0];
    upBin = lowBin+1;
  }

  double lowE = gEnergies_T1990[lowBin];
  double upE = gEnergies_T1990[upBin];

  double lowS = gStdDev_T1990[lowBin];
  double upS = gStdDev_T1990[upBin];

  double tanA = (lowS-upS)/(lowE-upE);
  double sigma3D = lowS + (k_eV-lowE)*tanA;
  return sigma3D;
}

double Terrisol1990::GetRmean(double energy){
  double sigma3D=Get3DStdDeviation(energy);

  static constexpr double s2r=1.595769121605731; // = pow(2,3./2.)/sqrt(CLHEP::pi)

  double r_mean=sigma3D*s2r;
  return r_mean;
}

void Terrisol1990::GetPenetration(G4double energy,
                                  G4ThreeVector& displacement){
  double sigma3D = Get3DStdDeviation(energy);

  static constexpr double factor = 2.20496999539; // = 1./(3. - 8./CLHEP::pi);

  double sigma1D = std::sqrt(std::pow(sigma3D, 2.)*factor);

  displacement = G4ThreeVector(G4RandGauss::shoot(0, sigma1D),
                               G4RandGauss::shoot(0, sigma1D),
                               G4RandGauss::shoot(0, sigma1D));
}

} // Penetration
} // DNA

//------------------------------------------------------------------------------

G4VEmModel* G4DNASolvationModelFactory::Create(const G4String& penetrationModel)
{
  G4String modelNamePrefix("DNAOneStepThermalizationModel_");
  
  if(penetrationModel == "Terrisol1990")
  {
    return new G4TDNAOneStepThermalizationModel<DNA::Penetration::Terrisol1990>(G4Electron::Definition(), modelNamePrefix + penetrationModel);
  }
  if(penetrationModel == "Meesungnoen2002")
  {
    return new G4TDNAOneStepThermalizationModel<DNA::Penetration::Meesungnoen2002>(G4Electron::Definition(), modelNamePrefix + penetrationModel);
  }
  if(penetrationModel == "Meesungnoen2002_amorphous")
  {
	return new G4TDNAOneStepThermalizationModel<DNA::Penetration::Meesungnoen2002_amorphous>(G4Electron::Definition(), modelNamePrefix + penetrationModel);
  }
  if(penetrationModel == "Kreipl2009")
  {
	return new G4TDNAOneStepThermalizationModel<DNA::Penetration::Kreipl2009>(G4Electron::Definition(), modelNamePrefix + penetrationModel);
  }
  if(penetrationModel == "Ritchie1994")
  {
    return new G4TDNAOneStepThermalizationModel<DNA::Penetration::Ritchie1994>(G4Electron::Definition(), modelNamePrefix + penetrationModel);
  }
  
  G4ExceptionDescription description;
  description << penetrationModel + " is not a valid model name.";
  G4Exception("G4DNASolvationModelFactory::Create",
              "INVALID_ARGUMENT",
              FatalErrorInArgument,
              description,
              "Options are: Terrisol1990, Meesungnoen2002, Ritchie1994.");
  return nullptr;
}

//------------------------------------------------------------------------------
G4VEmModel* G4DNASolvationModelFactory::GetMacroDefinedModel()
{
  auto dnaSubType = G4EmParameters::Instance()->DNAeSolvationSubType();
  
  switch(dnaSubType)
  {
  case fRitchie1994eSolvation:
    return Create("Ritchie1994");
  case fTerrisol1990eSolvation:
    return Create("Terrisol1990");
  case fKreipl2009eSolvation:
	return Create("Kreipl2009");
  case fMeesungnoensolid2002eSolvation:
	return Create("Meesungnoen2002_amorphous");
  case fMeesungnoen2002eSolvation:
  case fDNAUnknownModel:
    return Create("Meesungnoen2002");
  default:
    G4Exception("G4DNASolvationModelFactory::GetMacroDefinedModel",
                "DnaSubType",
                FatalErrorInArgument,
                "The solvation parameter stored in G4EmParameters is unknown. Supported types are: fRitchie1994eSolvation, fTerrisol1990eSolvation, fMeesungnoen2002eSolvation.");
  }

  return nullptr;
}
