#include "DataFormats/EgammaCandidates/interface/GsfElectron.h"
#include "DataFormats/GsfTrackReco/interface/GsfTrack.h"
#include "DataFormats/EgammaReco/interface/BasicCluster.h"
#include "DataFormats/EgammaReco/interface/BasicClusterFwd.h"

using namespace reco;

GsfElectronCoreRef GsfElectron::core() const { return core_; }

void GsfElectron::init() {
  passCutBasedPreselection_ = false;
  passPflowPreselection_ = false;
  passMvaPreslection_ = false;
  ambiguous_ = true;
  class_ = UNKNOWN;
}

GsfElectron::GsfElectron() { init(); }

GsfElectron::GsfElectron(const GsfElectronCoreRef& core) : core_(core) { init(); }

GsfElectron::GsfElectron(int charge,
                         const ChargeInfo& chargeInfo,
                         const GsfElectronCoreRef& core,
                         const TrackClusterMatching& tcm,
                         const TrackExtrapolations& te,
                         const ClosestCtfTrack& ctfInfo,
                         const FiducialFlags& ff,
                         const ShowerShape& ss,
                         const ConversionRejection& crv)
    : chargeInfo_(chargeInfo),
      core_(core),
      trackClusterMatching_(tcm),
      trackExtrapolations_(te),
      //closestCtfTrack_(ctfInfo),
      fiducialFlags_(ff),
      showerShape_(ss),
      conversionRejection_(crv) {
  init();
  setCharge(charge);
  setVertex(math::XYZPoint(te.positionAtVtx.x(), te.positionAtVtx.y(), te.positionAtVtx.z()));
  setPdgId(-11 * charge);
  /*if (ecalDrivenSeed())*/ corrections_.correctedEcalEnergy = superCluster()->energy();
  //  assert(ctfInfo.ctfTrack==(GsfElectron::core()->ctfTrack())) ;
  //  assert(ctfInfo.shFracInnerHits==(GsfElectron::core()->ctfGsfOverlap())) ;
}

GsfElectron::GsfElectron(int charge,
                         const ChargeInfo& chargeInfo,
                         const GsfElectronCoreRef& core,
                         const TrackClusterMatching& tcm,
                         const TrackExtrapolations& te,
                         const ClosestCtfTrack& ctfInfo,
                         const FiducialFlags& ff,
                         const ShowerShape& ss,
                         const ShowerShape& full5x5_ss,
                         const ConversionRejection& crv,
                         const SaturationInfo& si)
    : chargeInfo_(chargeInfo),
      core_(core),
      trackClusterMatching_(tcm),
      trackExtrapolations_(te),
      fiducialFlags_(ff),
      showerShape_(ss),
      full5x5_showerShape_(full5x5_ss),
      saturationInfo_(si),
      conversionRejection_(crv) {
  init();
  setCharge(charge);
  setVertex(math::XYZPoint(te.positionAtVtx.x(), te.positionAtVtx.y(), te.positionAtVtx.z()));
  setPdgId(-11 * charge);
  corrections_.correctedEcalEnergy = superCluster()->energy();
}

GsfElectron::GsfElectron(const GsfElectron& electron, const GsfElectronCoreRef& core)
    : RecoCandidate(electron),
      chargeInfo_(electron.chargeInfo_),
      core_(core),
      trackClusterMatching_(electron.trackClusterMatching_),
      trackExtrapolations_(electron.trackExtrapolations_),
      //closestCtfTrack_(electron.closestCtfTrack_),
      fiducialFlags_(electron.fiducialFlags_),
      showerShape_(electron.showerShape_),
      full5x5_showerShape_(electron.full5x5_showerShape_),
      saturationInfo_(electron.saturationInfo_),
      dr03_(electron.dr03_),
      dr04_(electron.dr04_),
      conversionRejection_(electron.conversionRejection_),
      pfIso_(electron.pfIso_),
      mvaInput_(electron.mvaInput_),
      mvaOutput_(electron.mvaOutput_),
      passCutBasedPreselection_(electron.passCutBasedPreselection_),
      passPflowPreselection_(electron.passPflowPreselection_),
      passMvaPreslection_(electron.passMvaPreslection_),
      ambiguous_(electron.ambiguous_),
      ambiguousGsfTracks_(electron.ambiguousGsfTracks_),
      classVariables_(electron.classVariables_),
      class_(electron.class_),
      corrections_(electron.corrections_),
      pixelMatchVariables_(electron.pixelMatchVariables_) {
  //assert(electron.core()->ctfTrack()==core->ctfTrack()) ;
  //assert(electron.core()->ctfGsfOverlap()==core->ctfGsfOverlap()) ;
}

GsfElectron::GsfElectron(const GsfElectron& electron,
                         const GsfElectronCoreRef& core,
                         const CaloClusterPtr& electronCluster,
                         const TrackRef& closestCtfTrack,
                         const TrackBaseRef& conversionPartner,
                         const GsfTrackRefVector& ambiguousTracks)
    : RecoCandidate(electron),
      chargeInfo_(electron.chargeInfo_),
      core_(core),
      trackClusterMatching_(electron.trackClusterMatching_),
      trackExtrapolations_(electron.trackExtrapolations_),
      //closestCtfTrack_(electron.closestCtfTrack_),
      fiducialFlags_(electron.fiducialFlags_),
      showerShape_(electron.showerShape_),
      full5x5_showerShape_(electron.full5x5_showerShape_),
      saturationInfo_(electron.saturationInfo_),
      dr03_(electron.dr03_),
      dr04_(electron.dr04_),
      conversionRejection_(electron.conversionRejection_),
      pfIso_(electron.pfIso_),
      mvaInput_(electron.mvaInput_),
      mvaOutput_(electron.mvaOutput_),
      passCutBasedPreselection_(electron.passCutBasedPreselection_),
      passPflowPreselection_(electron.passPflowPreselection_),
      passMvaPreslection_(electron.passMvaPreslection_),
      ambiguous_(electron.ambiguous_),
      ambiguousGsfTracks_(ambiguousTracks),
      //mva_(electron.mva_),
      classVariables_(electron.classVariables_),
      class_(electron.class_),
      corrections_(electron.corrections_),
      pixelMatchVariables_(electron.pixelMatchVariables_) {
  trackClusterMatching_.electronCluster = electronCluster;
  //closestCtfTrack_.ctfTrack = closestCtfTrack ;
  conversionRejection_.partner = conversionPartner;
  //assert(closestCtfTrack==core->ctfTrack()) ;
  //assert(electron.core()->ctfGsfOverlap()==core->ctfGsfOverlap()) ;
  // TO BE DONE
  // Check that the new edm references are really
  // the clones of the former references, and therefore other attributes
  // stay valid :
  // * electron.core_ ~ core ?
  // * electron.trackClusterMatching_.electronCluster ~ electronCluster ?
  // * electron.closestCtfTrack_.ctfTrack ~ closestCtfTrack ?
  // * electron.ambiguousGsfTracks_ ~ ambiguousTracks ?
}

bool GsfElectron::overlap(const Candidate& c) const {
  const RecoCandidate* o = dynamic_cast<const RecoCandidate*>(&c);
  return (o != nullptr && (checkOverlap(gsfTrack(), o->gsfTrack()) || checkOverlap(superCluster(), o->superCluster())));
  //?? return false;
}

GsfElectron* GsfElectron::clone() const { return new GsfElectron(*this); }

GsfElectron* GsfElectron::clone(const GsfElectronCoreRef& core,
                                const CaloClusterPtr& electronCluster,
                                const TrackRef& closestCtfTrack,
                                const TrackBaseRef& conversionPartner,
                                const GsfTrackRefVector& ambiguousTracks) const {
  return new GsfElectron(*this, core, electronCluster, closestCtfTrack, conversionPartner, ambiguousTracks);
}

bool GsfElectron::ecalDriven() const { return (ecalDrivenSeed() && passingCutBasedPreselection()); }

void GsfElectron::setCorrectedEcalEnergyError(float energyError) {
  corrections_.correctedEcalEnergyError = energyError;
}

void GsfElectron::setCorrectedEcalEnergy(float newEnergy) { setCorrectedEcalEnergy(newEnergy, true); }

void GsfElectron::setCorrectedEcalEnergy(float newEnergy, bool rescaleDependentValues) {
  math::XYZTLorentzVectorD momentum = p4();
  momentum *= newEnergy / momentum.e();
  setP4(momentum);
  if (corrections_.correctedEcalEnergy > 0. && rescaleDependentValues) {
    for (size_t id = 0; id < showerShape_.hcalOverEcal.size(); ++id) {
      showerShape_.hcalOverEcal[id] *= corrections_.correctedEcalEnergy / newEnergy;
      showerShape_.hcalOverEcalBc[id] *= corrections_.correctedEcalEnergy / newEnergy;
    }
    trackClusterMatching_.eSuperClusterOverP *= newEnergy / corrections_.correctedEcalEnergy;
    corrections_.correctedEcalEnergyError *= newEnergy / corrections_.correctedEcalEnergy;
  }
  corrections_.correctedEcalEnergy = newEnergy;
  corrections_.isEcalEnergyCorrected = true;
}

void GsfElectron::setTrackMomentumError(float trackErr) { corrections_.trackMomentumError = trackErr; }

void GsfElectron::setP4(P4Kind kind, const reco::Candidate::LorentzVector& p4, float error, bool setCandidate) {
  switch (kind) {
    case P4_FROM_SUPER_CLUSTER:
      corrections_.fromSuperClusterP4 = p4;
      corrections_.fromSuperClusterP4Error = error;
      break;
    case P4_COMBINATION:
      corrections_.combinedP4 = p4;
      corrections_.combinedP4Error = error;
      break;
    case P4_PFLOW_COMBINATION:
      corrections_.pflowP4 = p4;
      corrections_.pflowP4Error = error;
      break;
    default:
      throw cms::Exception("GsfElectron") << "unexpected p4 kind: " << kind;
  }
  if (setCandidate) {
    setP4(p4);
    corrections_.candidateP4Kind = kind;
  }
}

const Candidate::LorentzVector& GsfElectron::p4(P4Kind kind) const {
  switch (kind) {
    case P4_FROM_SUPER_CLUSTER:
      return corrections_.fromSuperClusterP4;
    case P4_COMBINATION:
      return corrections_.combinedP4;
    case P4_PFLOW_COMBINATION:
      return corrections_.pflowP4;
    default:
      throw cms::Exception("GsfElectron") << "unexpected p4 kind: " << kind;
  }
}

float GsfElectron::p4Error(P4Kind kind) const {
  switch (kind) {
    case P4_FROM_SUPER_CLUSTER:
      return corrections_.fromSuperClusterP4Error;
    case P4_COMBINATION:
      return corrections_.combinedP4Error;
    case P4_PFLOW_COMBINATION:
      return corrections_.pflowP4Error;
    default:
      throw cms::Exception("GsfElectron") << "unexpected p4 kind: " << kind;
  }
}

void GsfElectron::hcalToRun2EffDepth() {
  auto& ss1 = showerShape_;
  auto& ss2 = full5x5_showerShape_;
  auto& iv1 = dr03_;
  auto& iv2 = dr04_;

  for (uint id = 2u; id < ss1.hcalOverEcal.size(); ++id) {
    ss1.hcalOverEcal[1] += ss1.hcalOverEcal[id];
    ss1.hcalOverEcalBc[1] += ss1.hcalOverEcalBc[id];

    ss1.hcalOverEcal[id] = 0.f;
    ss1.hcalOverEcalBc[id] = 0.f;

    ss2.hcalOverEcal[1] += ss2.hcalOverEcal[id];
    ss2.hcalOverEcalBc[1] += ss2.hcalOverEcalBc[id];

    ss2.hcalOverEcal[id] = 0.f;
    ss2.hcalOverEcalBc[id] = 0.f;

    iv1.hcalRecHitSumEt[1] += iv1.hcalRecHitSumEt[id];
    iv1.hcalRecHitSumEtBc[1] += iv1.hcalRecHitSumEtBc[id];

    iv1.hcalRecHitSumEt[id] = 0.f;
    iv1.hcalRecHitSumEtBc[id] = 0.f;

    iv2.hcalRecHitSumEt[1] += iv2.hcalRecHitSumEt[id];
    iv2.hcalRecHitSumEtBc[1] += iv2.hcalRecHitSumEtBc[id];

    iv2.hcalRecHitSumEt[id] = 0.f;
    iv2.hcalRecHitSumEtBc[id] = 0.f;
  }
}
