/***************************************************************************

  TTrimSPDataHandler.cpp

  Author: Bastian M. Wojek
  e-mail: bastian.wojek@psi.ch

  2009/05/15

***************************************************************************/

/***************************************************************************
 *   Copyright (C) 2009 by Bastian M. Wojek                                *
 *                                                                         *
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "TTrimSPDataHandler.h"
#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <cassert>
#include <algorithm>

using namespace std;

//--------------------
// Constructor of the TrimSPData class -- reading all available trim.SP-rge-files with a given name into std::vectors
//--------------------

TTrimSPData::TTrimSPData(const string &path, map<double, string> &energies, bool debug) {

  // sort the energies in ascending order - this might be useful for later applications (energy-interpolations etc.)
  // after the change from the vector to the map this is not necessary any more - since maps are always ordered!
  // sort(energies.begin(), energies.end());

  double zz(0.0), nzz(0.0);
  vector<double> vzz, vnzz;
  string word, energyStr;
  bool goodFile(false);

  for ( map<double, string>::const_iterator iter(energies.begin()); iter != energies.end(); ++iter ) {

    energyStr = path + iter->second + ".rge";

    ifstream *rgeFile = new ifstream(energyStr.c_str());
    if(! *rgeFile) {
      cerr << "TTrimSPData::TTrimSPData: file " << energyStr << " not found! Try next energy..." << endl;
      delete rgeFile;
      rgeFile = 0;
    } else {

      while(*rgeFile >> word) {
        if(word == "PARTICLES") {
          goodFile = true;
          break;
        }
      }

      if (goodFile) {

        fEnergy.push_back(iter->first);

        while(!rgeFile->eof()) {
          *rgeFile >> zz >> nzz;
          vzz.push_back(zz);
          vnzz.push_back(nzz);
        }

        fDZ.push_back(0.5*(vzz[2]-vzz[0])); // it happens that TRIM.SP uses different step sizes in one output file, therefore take the average

        while(zz < 2100.0){
          zz += fDZ.back();
          vzz.push_back(zz);
          vnzz.push_back(0.0);
        }

        fDataZ.push_back(vzz);
        fDataNZ.push_back(vnzz);


        rgeFile->close();
        delete rgeFile;
        rgeFile = 0;

        vzz.clear();
        vnzz.clear();
        goodFile = false;

      } else {
        cerr << "TTrimSPData::TTrimSPData: " << energyStr << " does not seem to be a valid unmodified TRIM.SP output file!" << endl;
        continue;
      }
    }
  }

  if (debug)
    cout << "TTrimSPData::TTrimSPData: Read in " << fDataNZ.size() << " implantation profiles in total." << endl;

  fOrigDataNZ = fDataNZ;

  for(unsigned int i(0); i<fEnergy.size();++i)
    fIsNormalized.push_back(false);

  fEnergyIter = fEnergy.end();
}

void TTrimSPData::UseHighResolution(double e) {

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    vector<double> vecZ;
    vector<double> vecNZ;
    for(double zz(1.); zz<2100.; zz+=1.) {
      vecZ.push_back(zz);
      vecNZ.push_back(GetNofZ(zz/10.0, e));
    }
    fDataZ[i] = vecZ;
    fDataNZ[i] = vecNZ;
    fOrigDataNZ[i] = vecNZ;
    fDZ[i] = 1.;
    fIsNormalized[i] = false;
    return;
  }

  cout << "TTrimSPData::DataZ: No implantation profile available for the specified energy... Nothing happens." << endl;
  return;
}

//---------------------
// Method returning z-vector calculated by trim.SP for given energy[keV]
//---------------------

vector<double> TTrimSPData::DataZ(double e) const {

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    return fDataZ[i];
  }
  // default
  cout << "TTrimSPData::DataZ: No implantation profile available for the specified energy... You get back the first one." << endl;
  return fDataZ[0];
}

//---------------------
// Method returning actual n(z)-vector calculated by trim.SP and
// potentially altered by the WeightLayers- or the Normalize-method for given energy[keV]
//---------------------

vector<double> TTrimSPData::DataNZ(double e) const {

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    return fDataNZ[i];
  }
  // default
  cout << "TTrimSPData::DataNZ: No implantation profile available for the specified energy... You get back the first one." << endl;
  return fDataNZ[0];

}

//---------------------
// Method returning original n(z)-vector calculated by trim.SP for given energy[keV]
//---------------------

vector<double> TTrimSPData::OrigDataNZ(double e) const {

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    return fOrigDataNZ[i];
  }
  // default
  cout << "TTrimSPData::OrigDataNZ: No implantation profile available for the specified energy... You get back the first one." << endl;
  return fOrigDataNZ[0];

}

//---------------------
// Method returning fraction of muons implanted in the specified layer for a given energy[keV]
// Parameters: Energy[keV], LayerNumber[1], Interfaces[nm]
//---------------------

double TTrimSPData::LayerFraction(double e, unsigned int layno, const vector<double>& interface) const {

  if(layno < 1 && layno > (interface.size()+1)) {
    cout << "TTrimSPData::LayerFraction: No such layer available according to your specified interfaces... Returning 0.0!" << endl;
    return 0.0;
  }

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    // Because we do not know if the implantation profile is normalized or not, do not care about this and calculate the fraction from the beginning
    // Total "number of muons"
    double totalNumber(0.0);
    for(unsigned int j(0); j<fDataZ[i].size(); j++)
      totalNumber += fDataNZ[i][j];
    // "number of muons" in layer layno
    double layerNumber(0.0);
    if(!(layno-1)){
      for(unsigned int j(0); j<fDataZ[i].size(); j++)
        if(fDataZ[i][j] < interface[0]*10.0)
          layerNumber += fDataNZ[i][j];
    } else if(!(layno-interface.size()-1)){
      for(unsigned int j(0); j<fDataZ[i].size(); j++)
        if(fDataZ[i][j] >= *(interface.end()-1)*10.0)
          layerNumber += fDataNZ[i][j];
    } else {
      for(unsigned int j(0); j<fDataZ[i].size(); j++)
        if(fDataZ[i][j] >= interface[layno-2]*10.0 && fDataZ[i][j] < interface[layno-1]*10.0)
          layerNumber += fDataNZ[i][j];
    }
    // fraction of muons in layer layno
    // cout << "Fraction of muons in layer " << layno << ": " << layerNumber/totalNumber << endl;
    return layerNumber/totalNumber;
  }

  // default
  cout << "TTrimSPData::LayerFraction: No implantation profile available for the specified energy... Returning 0.0" << endl;
  return 0.0;

}

//---------------------
// Method putting different weight to different layers of your thin film
// Parameters: Implantation Energy[keV], Interfaces[nm], Weights [0.0 <= w[i] <= 1.0]
// Example: 25.0, (50, 100), (1.0, 0.33, 1.0)
//          at 25keV consider 3 layers, where the first ends after 50nm, the second after 100nm (these are NOT the layer thicknesses!!)
//          the first and last layers get the full n(z), where only one third of the muons in the second layer will be taken into account
//---------------------

void TTrimSPData::WeightLayers(double e, const vector<double>& interface, const vector<double>& weight) const {

  if(weight.size()-interface.size()-1) {
    cout << "TTrimSPData::WeightLayers: For the weighting the number of interfaces has to be one less than the number of weights!" << endl;
    cout << "TTrimSPData::WeightLayers: No weighting of the implantation profile will be done unless you take care of that!" << endl;
    return;
  }

  for(unsigned int i(0); i<interface.size(); i++) {
    if (interface[i]<0.0) {
      cout << "TTrimSPData::WeightLayers: One of your layer interfaces has a negative coordinate! - No weighting will be done!" << endl;
      return;
    }
    else if (i>1) {
      if (interface[i]<interface[i-1]) {
        cout << "TTrimSPData::WeightLayers: The specified interfaces appear to be not in ascending order! - No weighting will be done!" << endl;
        return;
      }
    }
  }

  for(unsigned int i(0); i<weight.size(); i++) {
    if (weight[i]>1.0 || weight[i]<0.0) {
      cout << "TTrimSPData::WeightLayers: At least one of the specified weights is out of range - no weighting will be done!" << endl;
      return;
    }
  }

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  // If all weights are equal to one, use the original n(z) vector
  for(unsigned int i(0); i<weight.size(); i++) {
    if(weight[i]-1.0)
      break;
    if(i == weight.size() - 1) {
      if(fEnergyIter != fEnergy.end()) {
        unsigned int j(fEnergyIter - fEnergy.begin());
        fDataNZ[j] = fOrigDataNZ[j];
        fIsNormalized[j] = false;
        return;
      }
    }
  }

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    unsigned int k(0);
    for(unsigned int j(0); j<fDataZ[i].size(); j++) {
      if(k<interface.size()) {
        if(fDataZ[i][j] < interface[k]*10.0)
          fDataNZ[i][j] = fOrigDataNZ[i][j]*weight[k];
        else {
          k++;
          fDataNZ[i][j] = fOrigDataNZ[i][j]*weight[k];
        }
      }
      else
        fDataNZ[i][j] = fOrigDataNZ[i][j]*weight[k];
    }
  fIsNormalized[i] = false;
  return;
  }

  cout << "TTrimSPData::WeightLayers: No implantation profile available for the specified energy... No weighting done." << endl;
  return;
}

//---------------------
// Method returning n(z) for given z[nm] and energy[keV]
//---------------------

double TTrimSPData::GetNofZ(double zz, double e) const {

  vector<double> z, nz;

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    z = fDataZ[i];
    nz = fDataNZ[i];
  } else {
      cout << "TTrimSPData::GetNofZ: No implantation profile available for the specified energy... Quitting!" << endl;
      exit(-1);
  }

  if(zz < 0)
    return 0.0;

  bool found = false;
  unsigned int i;
  for (i=0; i<z.size(); i++) {
    if (z[i]/10.0 >= zz) {
     found = true;
     break;
    }
  }

  if (!found)
    return 0.0;

  if (i == 0)
    return nz[0]*10.0*zz/z[0];

  return fabs(nz[i-1]+(nz[i]-nz[i-1])*(10.0*zz-z[i-1])/(z[i]-z[i-1]));
}

//---------------------
// Method normalizing the n(z)-vector calculated by trim.SP for a given energy[keV]
//---------------------

void TTrimSPData::Normalize(double e) const {

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    double nZsum = 0.0;
    for (unsigned int j(0); j<fDataZ[i].size(); j++)
      nZsum += fDataNZ[i][j];
    nZsum *= fDZ[i];
    for (unsigned int j(0); j<fDataZ[i].size(); j++)
      fDataNZ[i][j] /= nZsum;

    fIsNormalized[i] = true;
    return;
  }
  // default
  cout << "TTrimSPData::Normalize: No implantation profile available for the specified energy... No normalization done." << endl;
  return;

}

//---------------------
// Method telling you if the n(z)-vector calculated by trim.SP for a given energy [keV] has been normalized
//---------------------

bool TTrimSPData::IsNormalized(double e) const {
  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    return fIsNormalized[i];
  }

  cout << "TTrimSPData::IsNormalized: No implantation profile available for the specified energy... Returning false! Check your code!" << endl;
  return false;
}

//---------------------
// Calculate the mean range in (nm) for a given energy e
//---------------------

double TTrimSPData::MeanRange(double e) const {
  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    if (!fIsNormalized[i])
      Normalize(e);
    double mean(0.0);
    for(unsigned int j(0); j<fDataNZ[i].size(); j++){
      mean += fDataNZ[i][j]*fDataZ[i][j];
    }
    mean *= fDZ[i]/10.0;
    return mean;
  }

  cout << "TTrimSPData::MeanRange: No implantation profile available for the specified energy... Returning -1! Check your code!" << endl;
  return -1.;
}

//---------------------
// Find the peak range in (nm) for a given energy e
//---------------------

double TTrimSPData::PeakRange(double e) const {

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());

    vector<double>::const_iterator nziter;
    nziter = max_element(fDataNZ[i].begin(),fDataNZ[i].end());

    if(nziter != fDataNZ[i].end()){
      unsigned int j(nziter - fDataNZ[i].begin());
      return fDataZ[i][j]/10.0;
    }
    cout << "TTrimSPData::PeakRange: No maximum found in the implantation profile... Returning -1! Please check the profile!" << endl;
    return -1.;
  }

  cout << "TTrimSPData::PeakRange: No implantation profile available for the specified energy... Returning -1! Check your code!" << endl;
  return -1.;
}

//---------------------
// Convolve the n(z)-vector calculated by trim.SP for a given energy e [keV] with a gaussian exp(-z^2/(2*w^2))
// No normalization is done!
//---------------------

void TTrimSPData::ConvolveGss(double w, double e) const {
  if(!w)
    return;

  vector<double> z, nz, gss;
  double nn;

  fEnergyIter = find(fEnergy.begin(), fEnergy.end(), e);

  if(fEnergyIter != fEnergy.end()) {
    unsigned int i(fEnergyIter - fEnergy.begin());
    z = fDataZ[i];
    nz = fOrigDataNZ[i];

    for(unsigned int k(0); k<z.size(); k++) {
      gss.push_back(exp(-z[k]*z[k]/200.0/w/w));
    }

    for(unsigned int k(0); k<nz.size(); k++) {
      nn = 0.0;
      for(unsigned int j(0); j<nz.size(); j++) {
        nn += nz[j]*gss[abs(int(k)-int(j))];
      }
      fDataNZ[i][k] = nn;
    }

    fIsNormalized[i] = false;

    return;
  }

  cout << "TTrimSPData::ConvolveGss: No implantation profile available for the specified energy... No convolution done!" << endl;
  return;
}