//
// ********************************************************************
// * 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.          *
// ********************************************************************
//
//
// 070606 fix with Valgrind by T. Koi
// 080409 Fix div0 error with G4FPE by T. Koi
// 080811 Comment out unused method SetBlocked and SetBuffered
//        Add required cleaning up in CleanUp by T. Koi
//
// P. Arce, June-2014 Conversion neutron_hp to particle_hp
//
#ifndef G4ParticleHPVector_h
#define G4ParticleHPVector_h 1

#include "G4Exp.hh"
#include "G4InterpolationManager.hh"
#include "G4Log.hh"
#include "G4ParticleHPDataPoint.hh"
#include "G4ParticleHPHash.hh"
#include "G4ParticleHPInterpolator.hh"
#include "G4PhysicsVector.hh"
#include "G4Pow.hh"
#include "G4ios.hh"
#include "Randomize.hh"

#include <cmath>
#include <fstream>
#include <vector>

#if defined WIN32 - VC
#  include <float.h>
#endif

class G4ParticleHPVector
{
    friend G4ParticleHPVector& operator+(G4ParticleHPVector& left, G4ParticleHPVector& right);

  public:
    G4ParticleHPVector();

    G4ParticleHPVector(G4int n);

    ~G4ParticleHPVector();

    G4ParticleHPVector& operator=(const G4ParticleHPVector& right);

    inline void SetVerbose(G4int ff) { Verbose = ff; }

    inline void Times(G4double factor)
    {
      G4int i;
      for (i = 0; i < nEntries; i++) {
        theData[i].SetY(theData[i].GetY() * factor);
      }
      if (theIntegral != nullptr) {
        theIntegral[i] *= factor;
      }
    }

    inline void SetPoint(G4int i, const G4ParticleHPDataPoint& it)
    {
      G4double x = it.GetX();
      G4double y = it.GetY();
      SetData(i, x, y);
    }

    inline void SetData(G4int i, G4double x, G4double y)
    {
      //    G4cout <<"G4ParticleHPVector::SetData called"<<nPoints<<" "<<nEntries<<G4endl;
      Check(i);
      if (y > maxValue) maxValue = y;
      theData[i].SetData(x, y);
    }
    inline void SetX(G4int i, G4double e)
    {
      Check(i);
      theData[i].SetX(e);
    }
    inline void SetEnergy(G4int i, G4double e)
    {
      Check(i);
      theData[i].SetX(e);
    }
    inline void SetY(G4int i, G4double x)
    {
      Check(i);
      if (x > maxValue) maxValue = x;
      theData[i].SetY(x);
    }
    inline void SetXsec(G4int i, G4double x)
    {
      Check(i);
      if (x > maxValue) maxValue = x;
      theData[i].SetY(x);
    }
    inline G4double GetEnergy(G4int i) const { return theData[i].GetX(); }
    inline G4double GetXsec(G4int i) { return theData[i].GetY(); }
    inline G4double GetX(G4int i) const
    {
      if (i < 0) i = 0;
      if (i >= GetVectorLength()) i = GetVectorLength() - 1;
      return theData[i].GetX();
    }
    inline const G4ParticleHPDataPoint& GetPoint(G4int i) const { return theData[i]; }

    void Hash()
    {
      G4int i;
      G4double x, y;
      for (i = 0; i < nEntries; i++) {
        if (0 == (i + 1) % 10) {
          x = GetX(i);
          y = GetY(i);
          theHash.SetData(i, x, y);
        }
      }
    }

    void ReHash()
    {
      theHash.Clear();
      Hash();
    }

    G4double GetXsec(G4double e);
    G4double GetXsec(G4double e, G4int min)
    {
      G4int i;
      for (i = min; i < nEntries; i++) {
        if (theData[i].GetX() > e) break;
      }
      G4int low = i - 1;
      G4int high = i;
      if (i == 0) {
        low = 0;
        high = 1;
      }
      else if (i == nEntries) {
        low = nEntries - 2;
        high = nEntries - 1;
      }
      G4double y;
      if (e < theData[nEntries - 1].GetX()) {
        // Protect against doubled-up x values
        if ((theData[high].GetX() - theData[low].GetX()) / theData[high].GetX() < 0.000001) {
          y = theData[low].GetY();
        }
        else {
          y = theInt.Interpolate(theManager.GetScheme(high), e, theData[low].GetX(),
                                 theData[high].GetX(), theData[low].GetY(), theData[high].GetY());
        }
      }
      else {
        y = theData[nEntries - 1].GetY();
      }
      return y;
    }

    inline G4double GetY(G4double x) { return GetXsec(x); }
    inline G4int GetVectorLength() const { return nEntries; }

    inline G4double GetY(G4int i)
    {
      if (i < 0) i = 0;
      if (i >= GetVectorLength()) i = GetVectorLength() - 1;
      return theData[i].GetY();
    }

    inline G4double GetY(G4int i) const
    {
      if (i < 0) i = 0;
      if (i >= GetVectorLength()) i = GetVectorLength() - 1;
      return theData[i].GetY();
    }
    void Dump();

    inline void InitInterpolation(std::istream& aDataFile) { theManager.Init(aDataFile); }

    void Init(std::istream& aDataFile, G4int total, G4double ux = 1., G4double uy = 1.)
    {
      G4double x, y;
      for (G4int i = 0; i < total; i++) {
        aDataFile >> x >> y;
        x *= ux;
        y *= uy;
        SetData(i, x, y);
        if (0 == nEntries % 10) {
          theHash.SetData(nEntries - 1, x, y);
        }
      }
    }

    void Init(std::istream& aDataFile, G4double ux = 1., G4double uy = 1.)
    {
      G4int total;
      aDataFile >> total;
      delete[] theData;
      theData = new G4ParticleHPDataPoint[total];
      nPoints = total;
      nEntries = 0;
      theManager.Init(aDataFile);
      Init(aDataFile, total, ux, uy);
    }

    void ThinOut(G4double precision);

    inline void SetLabel(G4double aLabel) { label = aLabel; }

    inline G4double GetLabel() { return label; }

    inline void CleanUp()
    {
      nEntries = 0;
      theManager.CleanUp();
      maxValue = -DBL_MAX;
      theHash.Clear();
      // 080811 TK DB
      delete[] theIntegral;
      theIntegral = nullptr;
    }

    // merges the vectors active and passive into *this
    inline void Merge(G4ParticleHPVector* active, G4ParticleHPVector* passive)
    {
      CleanUp();
      G4int s_tmp = 0, n = 0, m_tmp = 0;
      G4ParticleHPVector* tmp;
      G4int a = s_tmp, p = n, t;
      while (a < active->GetVectorLength()
             && p < passive->GetVectorLength())  // Loop checking, 11.05.2015, T. Koi
      {
        if (active->GetEnergy(a) <= passive->GetEnergy(p)) {
          G4double xa = active->GetEnergy(a);
          G4double yy = active->GetXsec(a);
          SetData(m_tmp, xa, yy);
          theManager.AppendScheme(m_tmp, active->GetScheme(a));
          m_tmp++;
          a++;
          G4double xp = passive->GetEnergy(p);

          // 080409 TKDB
          // if( std::abs(std::abs(xp-xa)/xa)<0.001 ) p++;
          if (!(xa == 0) && std::abs(std::abs(xp - xa) / xa) < 0.001) p++;
        }
        else {
          tmp = active;
          t = a;
          active = passive;
          a = p;
          passive = tmp;
          p = t;
        }
      }
      while (a != active->GetVectorLength())  // Loop checking, 11.05.2015, T. Koi
      {
        SetData(m_tmp, active->GetEnergy(a), active->GetXsec(a));
        theManager.AppendScheme(m_tmp++, active->GetScheme(a));
        a++;
      }
      while (p != passive->GetVectorLength())  // Loop checking, 11.05.2015, T. Koi
      {
        if (std::abs(GetEnergy(m_tmp - 1) - passive->GetEnergy(p)) / passive->GetEnergy(p) > 0.001)
        // if(std::abs(GetEnergy(m)-passive->GetEnergy(p))/passive->GetEnergy(p)>0.001)
        {
          SetData(m_tmp, passive->GetEnergy(p), passive->GetXsec(p));
          theManager.AppendScheme(m_tmp++, active->GetScheme(p));
        }
        p++;
      }
    }

    void Merge(G4InterpolationScheme aScheme, G4double aValue, G4ParticleHPVector* active,
               G4ParticleHPVector* passive);

    G4double SampleLin()  // Samples X according to distribution Y, linear int
    {
      G4double result;
      if (theIntegral == nullptr) IntegrateAndNormalise();
      if (GetVectorLength() == 1) {
        result = theData[0].GetX();
      }
      else {
        G4int i;
        G4double rand = G4UniformRand();

        // this was replaced
        //      for(i=1;i<GetVectorLength();i++)
        //      {
        //	if(rand<theIntegral[i]/theIntegral[GetVectorLength()-1]) break;
        //      }

        // by this (begin)
        for (i = GetVectorLength() - 1; i >= 0; i--) {
          if (rand > theIntegral[i] / theIntegral[GetVectorLength() - 1]) break;
        }
        if (i != GetVectorLength() - 1) i++;
        // until this (end)

        G4double x1, x2, y1, y2;
        y1 = theData[i - 1].GetX();
        x1 = theIntegral[i - 1];
        y2 = theData[i].GetX();
        x2 = theIntegral[i];
        if (std::abs((y2 - y1) / y2)
            < 0.0000001)  // not really necessary, since the case is excluded by construction
        {
          y1 = theData[i - 2].GetX();
          x1 = theIntegral[i - 2];
        }
        result = theLin.Lin(rand, x1, x2, y1, y2);
      }
      return result;
    }

    G4double Sample();  // Samples X according to distribution Y

    G4double* Debug() { return theIntegral; }

    inline void IntegrateAndNormalise()
    {
      G4int i;
      if (theIntegral != nullptr) return;
      theIntegral = new G4double[nEntries];
      if (nEntries == 1) {
        theIntegral[0] = 1;
        return;
      }
      theIntegral[0] = 0;
      G4double sum = 0;
      G4double x1 = 0;
      G4double x0 = 0;
      for (i = 1; i < GetVectorLength(); i++) {
        x1 = theData[i].GetX();
        x0 = theData[i - 1].GetX();
        if (std::abs(x1 - x0) > std::abs(x1 * 0.0000001)) {
          //********************************************************************
          // EMendoza -> the interpolation scheme is not always lin-lin
          /*
                sum+= 0.5*(theData[i].GetY()+theData[i-1].GetY())*
                          (x1-x0);
          */
          //********************************************************************
          G4InterpolationScheme aScheme = theManager.GetScheme(i);
          G4double y0 = theData[i - 1].GetY();
          G4double y1 = theData[i].GetY();
          G4double integ = theInt.GetBinIntegral(aScheme, x0, x1, y0, y1);
#if defined WIN32 - VC
          if (!_finite(integ)) {
            integ = 0;
          }
#elif defined __IBMCPP__
          if (isinf(integ) || isnan(integ)) {
            integ = 0;
          }
#else
          if (std::isinf(integ) || std::isnan(integ)) {
            integ = 0;
          }
#endif
          sum += integ;
          //********************************************************************
        }
        theIntegral[i] = sum;
      }
      G4double total = theIntegral[GetVectorLength() - 1];
      for (i = 1; i < GetVectorLength(); i++) {
        theIntegral[i] /= total;
      }
    }

    inline void Integrate()
    {
      G4int i;
      if (nEntries == 1) {
        totalIntegral = 0;
        return;
      }
      G4double sum = 0;
      for (i = 1; i < GetVectorLength(); i++) {
        if (std::abs((theData[i].GetX() - theData[i - 1].GetX()) / theData[i].GetX()) > 0.0000001) {
          G4double x1 = theData[i - 1].GetX();
          G4double x2 = theData[i].GetX();
          G4double y1 = theData[i - 1].GetY();
          G4double y2 = theData[i].GetY();
          G4InterpolationScheme aScheme = theManager.GetScheme(i);
          if (aScheme == LINLIN || aScheme == CLINLIN || aScheme == ULINLIN) {
            sum += 0.5 * (y2 + y1) * (x2 - x1);
          }
          else if (aScheme == LINLOG || aScheme == CLINLOG || aScheme == ULINLOG) {
            G4double a = y1;
            G4double b = (y2 - y1) / (G4Log(x2) - G4Log(x1));
            sum += (a - b) * (x2 - x1) + b * (x2 * G4Log(x2) - x1 * G4Log(x1));
          }
          else if (aScheme == LOGLIN || aScheme == CLOGLIN || aScheme == ULOGLIN) {
            G4double a = G4Log(y1);
            G4double b = (G4Log(y2) - G4Log(y1)) / (x2 - x1);
            sum += (G4Exp(a) / b) * (G4Exp(b * x2) - G4Exp(b * x1));
          }
          else if (aScheme == HISTO || aScheme == CHISTO || aScheme == UHISTO) {
            sum += y1 * (x2 - x1);
          }
          else if (aScheme == LOGLOG || aScheme == CLOGLOG || aScheme == ULOGLOG) {
            G4double a = G4Log(y1);
            G4double b = (G4Log(y2) - G4Log(y1)) / (G4Log(x2) - G4Log(x1));
            sum +=
              (G4Exp(a) / (b + 1))
              * (G4Pow::GetInstance()->powA(x2, b + 1) - G4Pow::GetInstance()->powA(x1, b + 1));
          }
          else {
            throw G4HadronicException(
              __FILE__, __LINE__, "Unknown interpolation scheme in G4ParticleHPVector::Integrate");
          }
        }
      }
      totalIntegral = sum;
    }

    inline G4double GetIntegral()  // linear interpolation; use with care
    {
      if (totalIntegral < -0.5) Integrate();
      return totalIntegral;
    }

    inline void SetInterpolationManager(const G4InterpolationManager& aManager)
    {
      theManager = aManager;
    }

    inline const G4InterpolationManager& GetInterpolationManager() const { return theManager; }

    inline void SetInterpolationManager(G4InterpolationManager& aMan) { theManager = aMan; }

    inline void SetScheme(G4int aPoint, const G4InterpolationScheme& aScheme)
    {
      theManager.AppendScheme(aPoint, aScheme);
    }

    inline G4InterpolationScheme GetScheme(G4int anIndex) { return theManager.GetScheme(anIndex); }

    G4double GetMeanX()
    {
      G4double result;
      G4double running = 0;
      G4double weighted = 0;
      for (G4int i = 1; i < nEntries; i++) {
        running +=
          theInt.GetBinIntegral(theManager.GetScheme(i - 1), theData[i - 1].GetX(),
                                theData[i].GetX(), theData[i - 1].GetY(), theData[i].GetY());
        weighted += theInt.GetWeightedBinIntegral(theManager.GetScheme(i - 1),
                                                  theData[i - 1].GetX(), theData[i].GetX(),
                                                  theData[i - 1].GetY(), theData[i].GetY());
      }
      result = weighted / running;
      return result;
    }

    // Finds maximum cross section between two values of kinetic energy
    G4double GetMaxY(G4double emin, G4double emax);

    /*
      void Block(G4double aX)
      {
        theBlocked.push_back(aX);
      }

      void Buffer(G4double aX)
      {
        theBuffered.push_back(aX);
      }
    */

    std::vector<G4double> GetBlocked() { return theBlocked; }
    std::vector<G4double> GetBuffered() { return theBuffered; }

    //  void SetBlocked(const std::vector<G4double> &aBlocked) {theBlocked = aBlocked;}
    //  void SetBuffered(const std::vector<G4double> &aBuffer) {theBuffered = aBuffer;}

    G4double Get15percentBorder();
    G4double Get50percentBorder();

  private:
    void Check(G4int i);

    G4bool IsBlocked(G4double aX);

  private:
    G4ParticleHPInterpolator theLin;

  private:
    G4double totalIntegral;

    G4ParticleHPDataPoint* theData;  // the data
    G4InterpolationManager theManager;  // knows how to interpolate the data.
    G4double* theIntegral;
    G4int nEntries;
    G4int nPoints;
    G4double label;

    G4ParticleHPInterpolator theInt;
    G4int Verbose;
    // debug only
    G4int isFreed;

    G4ParticleHPHash theHash;
    G4double maxValue;

    std::vector<G4double> theBlocked;
    std::vector<G4double> theBuffered;
    G4double the15percentBorderCash;
    G4double the50percentBorderCash;
};

#endif
