//
// Original Author:  Fedor Ratnikov Nov 9, 2007
// $Id: JetCorrectorParameters.h,v 1.15 2012/03/01 18:24:52 srappocc Exp $
//
// Generic parameters for Jet corrections
//
#ifndef JetCorrectorParameters_h
#define JetCorrectorParameters_h

#include "CondFormats/Serialization/interface/Serializable.h"
#include "CondFormats/JetMETObjects/interface/Utilities.h"

#include <string>
#include <vector>
#include <tuple>
#include <algorithm>
#include <functional>
#include <iostream>
#include <memory>
#include "FWCore/Utilities/interface/Exception.h"
#include "FWCore/MessageLogger/interface/MessageLogger.h"

class JetCorrectorParametersHelper;

class JetCorrectorParameters {
  //---------------- JetCorrectorParameters class ----------------
  //-- Encapsulates all the information of the parametrization ---
public:
  //---------------- Definitions class ---------------------------
  //-- Global iformation about the parametrization is kept here --
  class Definitions {
  public:
    //-------- Constructors --------------
    Definitions() : mIsResponse(false) {}
    Definitions(const std::vector<std::string>& fBinVar,
                const std::vector<std::string>& fParVar,
                const std::string& fFormula,
                bool fIsResponse);
    Definitions(const std::string& fLine);
    //-------- Member functions ----------
    unsigned nBinVar() const { return mBinVar.size(); }
    unsigned nParVar() const { return mParVar.size(); }
    std::vector<std::string> parVar() const { return mParVar; }
    std::vector<std::string> binVar() const { return mBinVar; }
    std::string parVar(unsigned fIndex) const { return mParVar[fIndex]; }
    std::string binVar(unsigned fIndex) const { return mBinVar[fIndex]; }
    std::string formula() const { return mFormula; }
    std::string level() const { return mLevel; }
    bool isResponse() const { return mIsResponse; }

  private:
    //-------- Member variables ----------
    bool mIsResponse;
    std::string mLevel;
    std::string mFormula;
    std::vector<std::string> mParVar;
    std::vector<std::string> mBinVar;

    COND_SERIALIZABLE;
  };
  //---------------- Record class --------------------------------
  //-- Each Record holds the properties of a bin -----------------
  class Record {
  public:
    //-------- Constructors --------------
    Record() : mNvar(0), mMin(0), mMax(0) {}
    Record(unsigned fNvar,
           const std::vector<float>& fXMin,
           const std::vector<float>& fXMax,
           const std::vector<float>& fParameters)
        : mNvar(fNvar), mMin(fXMin), mMax(fXMax), mParameters(fParameters) {}
    Record(const std::string& fLine, unsigned fNvar);
    //-------- Member functions ----------
    unsigned nVar() const { return mNvar; }
    float xMin(unsigned fVar) const { return mMin[fVar]; }
    float xMax(unsigned fVar) const { return mMax[fVar]; }
    float xMiddle(unsigned fVar) const { return 0.5 * (xMin(fVar) + xMax(fVar)); }
    float parameter(unsigned fIndex) const { return mParameters[fIndex]; }
    std::vector<float> parameters() const { return mParameters; }
    unsigned nParameters() const { return mParameters.size(); }
    bool operator<(const Record& other) const {
      if (xMin(0) < other.xMin(0))
        return true;
      if (xMin(0) > other.xMin(0))
        return false;
      auto const sz = mMin.size();
      auto const otherSz = other.mMin.size();
      if (sz > 1 and otherSz > 1) {
        if (xMin(1) < other.xMin(1))
          return true;
        if (xMin(1) > other.xMin(1))
          return false;
        if (sz > 2 and otherSz > 2) {
          return (xMin(2) < other.xMin(2));
        }
      }
      //xMins were equal up until one of them
      // ran out of values
      return sz < otherSz;
    }

  private:
    //-------- Member variables ----------
    unsigned mNvar;
    std::vector<float> mMin;
    std::vector<float> mMax;
    std::vector<float> mParameters;

    COND_SERIALIZABLE;
  };

  //-------- Constructors --------------
  JetCorrectorParameters() { valid_ = false; }
  JetCorrectorParameters(const std::string& fFile, const std::string& fSection = "");
  JetCorrectorParameters(const JetCorrectorParameters::Definitions& fDefinitions,
                         const std::vector<JetCorrectorParameters::Record>& fRecords)
      : mDefinitions(fDefinitions), mRecords(fRecords) {
    valid_ = true;
  }
  //-------- Member functions ----------
  const Record& record(unsigned fBin) const { return mRecords[fBin]; }
  const Definitions& definitions() const { return mDefinitions; }
  unsigned size() const { return mRecords.size(); }
  unsigned size(unsigned fVar) const;
  int binIndex(const std::vector<float>& fX) const;
  int binIndexN(const std::vector<float>& fX) const;
  int neighbourBin(unsigned fIndex, unsigned fVar, bool fNext) const;
  std::vector<float> binCenters(unsigned fVar) const;
  void printScreen() const;
  void printFile(const std::string& fFileName) const;
  bool isValid() const { return valid_; }
  void init();

  static const int MAX_SIZE_DIMENSIONALITY = 3 COND_TRANSIENT;

private:
  //-------- Member variables ----------
  JetCorrectorParameters::Definitions mDefinitions;
  std::vector<JetCorrectorParameters::Record> mRecords;
  bool valid_;  /// is this a valid set?

  std::shared_ptr<JetCorrectorParametersHelper> helper COND_TRANSIENT;

  COND_SERIALIZABLE;
};
std::ostream& operator<<(std::ostream& out, const JetCorrectorParameters::Record& fBin);

class JetCorrectorParametersCollection {
  //---------------- JetCorrectorParametersCollection class ----------------
  //-- Adds several JetCorrectorParameters together by algorithm type ---
  //--     to reduce the number of payloads in the Database ---
  //-- NB: The enum is listed in "human-logical" order, but the actual
  //       enum value is in order of appearance when people thought of them.
public:
  enum Level_t {
    L1Offset = 0,
    L1JPTOffset = 7,
    L1FastJet = 10,
    L2Relative = 1,
    L3Absolute = 2,
    L2L3Residual = 8,
    L4EMF = 3,
    L5Flavor = 4,
    L6UE = 5,
    L7Parton = 6,
    Uncertainty = 9,
    UncertaintyAbsolute = 11,
    UncertaintyHighPtExtra = 12,
    UncertaintySinglePionECAL = 13,
    UncertaintySinglePionHCAL = 27,
    UncertaintyFlavor = 14,
    UncertaintyTime = 15,
    UncertaintyRelativeJEREC1 = 16,
    UncertaintyRelativeJEREC2 = 17,
    UncertaintyRelativeJERHF = 18,
    UncertaintyRelativePtEC1 = 28,
    UncertaintyRelativePtEC2 = 29,
    UncertaintyRelativePtHF = 30,
    UncertaintyRelativeStatEC2 = 19,
    UncertaintyRelativeStatHF = 20,
    UncertaintyRelativeFSR = 21,
    UncertaintyRelativeSample = 31,
    UncertaintyPileUpDataMC = 22,
    UncertaintyPileUpOOT = 23,
    UncertaintyPileUpPtBB = 24,
    UncertaintyPileUpPtEC = 32,
    UncertaintyPileUpPtHF = 33,
    UncertaintyPileUpBias = 25,
    UncertaintyPileUpJetRate = 26,
    L1RC = 34,
    L1Residual = 35,
    UncertaintyAux3 = 36,
    UncertaintyAux4 = 37,
    N_LEVELS = 38
  };

  enum L5_Species_t { L5_bJ = 0, L5_cJ, L5_qJ, L5_gJ, L5_bT, L5_cT, L5_qT, L5_gT, N_L5_SPECIES };
  enum L7_Species_t { L7_gJ = 0, L7_qJ, L7_cJ, L7_bJ, L7_jJ, L7_qT, L7_cT, L7_bT, L7_jT, N_L7_SPECIES };
  typedef int key_type;
  typedef std::string label_type;
  typedef JetCorrectorParameters value_type;
  typedef std::pair<key_type, value_type> pair_type;
  typedef std::vector<pair_type> collection_type;

  // Constructor... initialize all three vectors to zero
  JetCorrectorParametersCollection() {
    corrections_.clear();
    correctionsL5_.clear();
    correctionsL7_.clear();
  }

  // Add a JetCorrectorParameter object, possibly with flavor.
  void push_back(key_type i, value_type const& j, label_type const& flav = "");

  // Access the JetCorrectorParameter via the key k.
  // key_type is hashed to deal with the three collections
  JetCorrectorParameters const& operator[](key_type k) const;

  // Access the JetCorrectorParameter via a string.
  // Will find the hashed value for the label, and call via that
  // operator.
  JetCorrectorParameters const& operator[](std::string const& label) const { return operator[](findKey(label)); }

  // Get a list of valid keys. These will contain hashed keys
  // that are aware of all three collections.
  void validKeys(std::vector<key_type>& keys) const;

  // Helper method to find all of the sections in a given
  // parameters file
  static void getSections(std::string inputFile, std::vector<std::string>& outputs);

  // Find the L5 bin for hashing
  static key_type getL5Bin(std::string const& flav);
  // Find the L7 bin for hashing
  static key_type getL7Bin(std::string const& flav);
  // Check if this is an L5 hashed value
  static bool isL5(key_type k);
  // Check if this is an L7 hashed value
  static bool isL7(key_type k);

  static std::string findLabel(key_type k);
  static std::string findL5Flavor(key_type k);
  static std::string findL7Parton(key_type k);

protected:
  // Find the key corresponding to each label
  key_type findKey(std::string const& label) const;

  collection_type corrections_;
  collection_type correctionsL5_;
  collection_type correctionsL7_;

  collection_type& getCorrections() { return corrections_; }
  collection_type& getCorrectionsL5() { return correctionsL5_; }
  collection_type& getCorrectionsL7() { return correctionsL7_; }

  friend struct JetCorrectorParametersInitializeTransients;

  COND_SERIALIZABLE;
};

struct JetCorrectorParametersInitializeTransients {
  void operator()(JetCorrectorParametersCollection& jcpc) {
    for (auto& ptype : jcpc.getCorrections()) {
      ptype.second.init();
    }
    for (auto& ptype : jcpc.getCorrectionsL5()) {
      ptype.second.init();
    }
    for (auto& ptype : jcpc.getCorrectionsL7()) {
      ptype.second.init();
    }
  }
};

#endif
