//
// ********************************************************************
// * 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: Riccardo Capra <capra@ge.infn.it>
// Code review by MGP October 2007: removed inheritance from concrete class
//                                  more improvements needed
//
// History:
// -----------
// 30 Jun 2005  RC           Created
// 14 Oct 2007  MGP          Removed inheritance from concrete class G4ShellEMDataSet
//
// 15 Jul 2009   N.A.Karakatsanis
//
//                           - LoadNonLogData method was created to load only the non-logarithmic data from G4EMLOW
//                             dataset. It is essentially performing the data loading operations as in the past.
//
//                           - LoadData method was revised in order to calculate the logarithmic values of the data
//                             It retrieves the data values from the G4EMLOW data files but, then, calculates the
//                             respective log values and loads them to seperate data structures. 
//
//                           - SetLogEnergiesData method was cretaed to set logarithmic values to G4 data vectors.
//                             The EM data sets, initialized this way, contain both non-log and log values.
//                             These initialized data sets can enhance the computing performance of data interpolation
//                             operations
//
//
// -------------------------------------------------------------------


#include "G4CrossSectionDataSet.hh"
#include "G4VDataSetAlgorithm.hh"
#include "G4EMDataSet.hh"
#include <vector>
#include <fstream>
#include <sstream>

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

G4CrossSectionDataSet::G4CrossSectionDataSet(G4VDataSetAlgorithm* argAlgorithm, 
					     G4double argUnitEnergies, 
					     G4double argUnitData) :
   algorithm(argAlgorithm), unitEnergies(argUnitEnergies), unitData(argUnitData)
{;}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

G4CrossSectionDataSet::~G4CrossSectionDataSet()
{
  CleanUpComponents();
 
  if (algorithm)
    delete algorithm;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

G4bool G4CrossSectionDataSet::LoadData(const G4String & argFileName)
{
  CleanUpComponents();

  G4String fullFileName(FullFileName(argFileName));
  std::ifstream in(fullFileName, std::ifstream::binary|std::ifstream::in);

  if (!in.is_open())
    {
      G4String message("data file \"");
      message+=fullFileName;
      message+="\" not found";
      G4Exception("G4CrossSectionDataSet::LoadData",
		    "em0003",FatalException,message);
      return false;
    }

  std::vector<G4DataVector *> columns;
  std::vector<G4DataVector *> log_columns;

  std::stringstream *stream(new std::stringstream);
  char c;
  G4bool comment(false);
  G4bool space(true);
  G4bool first(true);

  try
    {
      while (!in.eof())
	{
	  in.get(c);
   
	  switch (c)
	    {
	    case '\r':
	    case '\n':
	      if (!first)
		{
		  unsigned long i(0);
		  G4double value;
      
		  while (!stream->eof())
		    {
		      (*stream) >> value;
       
		      while (i>=columns.size())
                        {
			 columns.push_back(new G4DataVector);
                         log_columns.push_back(new G4DataVector);
                        }
      
		      columns[i]->push_back(value);

// N. A. Karakatsanis
// A condition is applied to check if negative or zero values are present in the dataset.
// If yes, then a near-zero value is applied to allow the computation of the logarithmic value
// If a value is zero, this simplification is acceptable
// If a value is negative, then it is not acceptable and the data of the particular column of
// logarithmic values should not be used by interpolation methods.
//
// Therefore, G4LogLogInterpolation and G4LinLogLogInterpolation should not be used if negative values are present.
// Instead, G4LinInterpolation is safe in every case
// SemiLogInterpolation is safe only if the energy columns are non-negative
// G4LinLogInterpolation is safe only if the cross section data columns are non-negative

                      if (value <=0.) value = 1e-300;
                      log_columns[i]->push_back(std::log10(value));
       
		      i++;
		    }
      
		  delete stream;
		  stream=new std::stringstream;
		}
     
	      first=true;
	      comment=false;
	      space=true;
	      break;
     
	    case '#':
	      comment=true;
	      break;
     
	    case '\t':
	    case ' ':
	      space = true;
              break;

	    default:
	      if (comment) { break; }
	      if (space && (!first)) { (*stream) << ' '; }

	      first=false;
	      (*stream) << c;
	      space=false;
	    }
	}
    }
  catch(const std::ios::failure &e)
    {
      // some implementations of STL could throw a "failture" exception
      // when read wants read characters after end of file
    }
 
  delete stream;
 
  G4int maxI = (G4int)columns.size();
 
  if (maxI<2)
    {
      G4String message("data file \"");
      message+=fullFileName;
      message+="\" should have at least two columns";
      G4Exception("G4CrossSectionDataSet::LoadData",
		    "em0005",FatalException,message);
      return false;
    }
 
  G4int i(1);
  while (i<maxI)
    {
      std::size_t maxJ(columns[i]->size());

      if (maxJ!=columns[0]->size())
	{
	  G4String message("data file \"");
	  message+=fullFileName;
	  message+="\" has lines with a different number of columns";
          G4Exception("G4CrossSectionDataSet::LoadData",
		    "em0005",FatalException,message);
	  return false;
	}

      std::size_t j(0);

      G4DataVector *argEnergies=new G4DataVector;
      G4DataVector *argData=new G4DataVector;
      G4DataVector *argLogEnergies=new G4DataVector;
      G4DataVector *argLogData=new G4DataVector;

      while(j<maxJ)
	{
	  argEnergies->push_back(columns[0]->operator[] (j)*GetUnitEnergies());
	  argData->push_back(columns[i]->operator[] (j)*GetUnitData());
	  argLogEnergies->push_back(log_columns[0]->operator[] (j) + std::log10(GetUnitEnergies()));
	  argLogData->push_back(log_columns[i]->operator[] (j) + std::log10(GetUnitData()));
	  j++;
	}

      AddComponent(new G4EMDataSet(i-1, argEnergies, argData, argLogEnergies, argLogData, GetAlgorithm()->Clone(), GetUnitEnergies(), GetUnitData()));
  
      i++;
    }

  i=maxI;
  while (i>0)
    {
      i--;
      delete columns[i];
      delete log_columns[i];
    }

  return true;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

G4bool G4CrossSectionDataSet::LoadNonLogData(const G4String & argFileName)
{
  CleanUpComponents();

  G4String fullFileName(FullFileName(argFileName));
  std::ifstream in(fullFileName, std::ifstream::binary|std::ifstream::in);

  if (!in.is_open())
    {
      G4String message("data file \"");
      message+=fullFileName;
      message+="\" not found";
      G4Exception("G4CrossSectionDataSet::LoadNonLogData",
		    "em0003",FatalException,message);
      return false;
    }

  std::vector<G4DataVector *> columns;

  std::stringstream *stream(new std::stringstream);
  char c;
  G4bool comment(false);
  G4bool space(true);
  G4bool first(true);

  try
    {
      while (!in.eof())
	{
	  in.get(c);
   
	  switch (c)
	    {
	    case '\r':
	    case '\n':
	      if (!first)
		{
		  unsigned long i(0);
		  G4double value;
      
		  while (!stream->eof())
		    {
		      (*stream) >> value;
       
		      while (i>=columns.size())
                        {
			 columns.push_back(new G4DataVector);
                        }
      
		      columns[i]->push_back(value);
       
		      i++;
		    }
      
		  delete stream;
		  stream=new std::stringstream;
		}
     
	      first=true;
	      comment=false;
	      space=true;
	      break;
     
	    case '#':
	      comment=true;
	      break;
     
	    case '\t':
	    case ' ':
	      space = true;
              break;

	    default:
	      if (comment) { break; }
	      if (space && (!first)) { (*stream) << ' '; }

	      first=false;
	      (*stream) << c;
	      space=false;
	    }
	}
    }
  catch(const std::ios::failure &e)
    {
      // some implementations of STL could throw a "failture" exception
      // when read wants read characters after end of file
    }
 
  delete stream;
 
  G4int maxI = (G4int)columns.size();
 
  if (maxI<2)
    {
      G4String message("data file \"");
      message+=fullFileName;
      message+="\" should have at least two columns";
      G4Exception("G4CrossSectionDataSet::LoadNonLogData",
		    "em0005",FatalException,message);
      return false;
    }
 
  G4int i(1);
  while (i<maxI)
    {
      std::size_t maxJ(columns[i]->size());

      if (maxJ!=columns[0]->size())
	{
	  G4String message("data file \"");
	  message+=fullFileName;
	  message+="\" has lines with a different number of columns";
          G4Exception("G4CrossSectionDataSet::LoadNonLogData",
		    "em0005",FatalException,message);
	  return false;
	}

      std::size_t j(0);

      G4DataVector *argEnergies=new G4DataVector;
      G4DataVector *argData=new G4DataVector;

      while(j<maxJ)
	{
	  argEnergies->push_back(columns[0]->operator[] (j)*GetUnitEnergies());
	  argData->push_back(columns[i]->operator[] (j)*GetUnitData());
	  j++;
	}

      AddComponent(new G4EMDataSet(i-1, argEnergies, argData, GetAlgorithm()->Clone(), GetUnitEnergies(), GetUnitData()));
  
      i++;
    }

  i=maxI;
  while (i>0)
    {
      i--;
      delete columns[i];
    }

  return true;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

G4bool G4CrossSectionDataSet::SaveData(const G4String & argFileName) const
{
  const G4int n = (G4int)NumberOfComponents();
 
  if (n==0)
    {
      G4Exception("G4CrossSectionDataSet::SaveData",
		    "em0005",FatalException,"expected at least one component");
      return false;
    }
 
  G4String fullFileName(FullFileName(argFileName));
  std::ofstream out(fullFileName);

  if (!out.is_open())
    {
      G4String message("cannot open \"");
      message+=fullFileName;
      message+="\"";
      G4Exception("G4CrossSectionDataSet::SaveData",
		    "em0003",FatalException,message);
      return false;
    }
 
  G4DataVector::const_iterator iEnergies(GetComponent(0)->GetEnergies(0).begin());
  G4DataVector::const_iterator iEnergiesEnd(GetComponent(0)->GetEnergies(0).end());
  G4DataVector::const_iterator * iData(new G4DataVector::const_iterator[n]);
 
  G4int k(n);
 
  while (k>0)
    {
      k--;
      iData[k]=GetComponent(k)->GetData(0).cbegin();
    }
 
  while (iEnergies!=iEnergiesEnd)
    {
      out.precision(10);
      out.width(15);
      out.setf(std::ofstream::left);
      out << ((*iEnergies)/GetUnitEnergies());
  
      k=0;
  
      while (k<n)
	{
	  out << ' ';
	  out.precision(10);
	  out.width(15);
	  out.setf(std::ofstream::left);
	  out << ((*(iData[k]))/GetUnitData());

	  iData[k]++;
	  k++;
	}
  
      out << std::endl;
  
      iEnergies++;
    }
 
  delete[] iData;

  return true;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

G4String G4CrossSectionDataSet::FullFileName(const G4String& argFileName) const
{
  const char* path = G4FindDataDir("G4LEDATA");
  if (!path)
    {
      G4Exception("G4CrossSectionDataSet::FullFileName",
		   "em0006",FatalException,"G4LEDATA environment variable not set");
      return "NULL";
    }  

  std::ostringstream fullFileName;
 
  fullFileName << path << "/" << argFileName << ".dat";
                      
  return G4String(fullFileName.str().c_str());
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

G4double G4CrossSectionDataSet::FindValue(G4double argEnergy, G4int /* argComponentId */) const
{
  // Returns the sum over the shells corresponding to e
  G4double value = 0.;

  std::vector<G4VEMDataSet *>::const_iterator i(components.begin());
  std::vector<G4VEMDataSet *>::const_iterator end(components.end());

  while (i!=end)
    {
      value+=(*i)->FindValue(argEnergy);
      i++;
    }

  return value;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

void G4CrossSectionDataSet::PrintData(void) const
{
  const G4int n = (G4int)NumberOfComponents();

  G4cout << "The data set has " << n << " components" << G4endl;
  G4cout << G4endl;
 
  G4int i(0);
 
  while (i<n)
    {
      G4cout << "--- Component " << i << " ---" << G4endl;
      GetComponent(i)->PrintData();
      ++i;
    }
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

void G4CrossSectionDataSet::SetEnergiesData(G4DataVector* argEnergies, 
					 G4DataVector* argData, 
					 G4int argComponentId)
{
  G4VEMDataSet * component(components[argComponentId]);
 
  if (component)
    {
      component->SetEnergiesData(argEnergies, argData, 0);
      return;
    }

  std::ostringstream message;
  message << "component " << argComponentId << " not found";
 
  G4Exception("G4CrossSectionDataSet::SetEnergiesData",
		   "em0005",FatalException,message.str().c_str());
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

void G4CrossSectionDataSet::SetLogEnergiesData(G4DataVector* argEnergies, 
					 G4DataVector* argData,
                                         G4DataVector* argLogEnergies,
                                         G4DataVector* argLogData, 
					 G4int argComponentId)
{
  G4VEMDataSet * component(components[argComponentId]);
 
  if (component)
    {
      component->SetLogEnergiesData(argEnergies, argData, argLogEnergies, argLogData, 0);
      return;
    }

  std::ostringstream message;
  message << "component " << argComponentId << " not found";
 
  G4Exception("G4CrossSectionDataSet::SetLogEnergiesData",
		   "em0005",FatalException,message.str().c_str());
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo....

void G4CrossSectionDataSet::CleanUpComponents()
{
  while (!components.empty())
    {
      if (components.back()) delete components.back();
      components.pop_back();
    }
}


