#ifndef CandUtils_CandCombiner_h
#define CandUtils_CandCombiner_h
/** \class CandCombiner
 *
 * \author Luca Lista, INFN
 *
 */
#include "CommonTools/CandUtils/interface/CandCombinerBase.h"
#include "CommonTools/CandUtils/interface/AddFourMomenta.h"
#include "CommonTools/CandUtils/interface/CandSelector.h"
#include "CommonTools/UtilAlgos/interface/AnyPairSelector.h"
#include "DataFormats/Candidate/interface/CompositeCandidate.h"
#include "DataFormats/Candidate/interface/NamedCompositeCandidate.h"
#include "DataFormats/Candidate/interface/ShallowCloneCandidate.h"
#include "DataFormats/Candidate/interface/ShallowClonePtrCandidate.h"

namespace combiner {
  namespace helpers {
    struct NormalClone {
      typedef reco::CandidateBaseRef CandPtr;
      template <typename Ref>
      static void addDaughter(reco::CompositeCandidate& cmp, const Ref& c, const std::string name = "") {
        cmp.addDaughter(*c, name);
      }
    };

    struct ShallowClone {
      typedef reco::CandidateBaseRef CandPtr;
      static void addDaughter(reco::CompositeCandidate& cmp,
                              const reco::CandidateBaseRef& c,
                              const std::string name = "") {
        cmp.addDaughter(reco::ShallowCloneCandidate(c), name);
      }
    };
    struct ShallowClonePtr {
      typedef reco::CandidatePtr CandPtr;
      static void addDaughter(reco::CompositeCandidate& cmp, const reco::CandidatePtr& c, const std::string name = "") {
        cmp.addDaughter(reco::ShallowClonePtrCandidate(c), name);
      }
    };
  }  // namespace helpers
}  // namespace combiner

template <typename Selector,
          typename PairSelector = AnyPairSelector,
          typename Cloner = combiner::helpers::NormalClone,
          typename OutputCollection = reco::CompositeCandidateCollection,
          typename Setup = AddFourMomenta>
class CandCombiner : public CandCombinerBase<OutputCollection, typename Cloner::CandPtr> {
public:
  typedef typename Cloner::CandPtr CandPtr;
  typedef CandCombinerBase<OutputCollection, CandPtr> base;
  /// default constructor
  CandCombiner() : base(), select_(), selectPair_(), setup_() {}
  /// constructor from a selector and two charges
  CandCombiner(int q1, int q2) : base(q1, q2), select_(), selectPair_(), setup_() {}
  /// constructor from a selector and three charges
  CandCombiner(int q1, int q2, int q3) : base(q1, q2, q3), select_(), selectPair_(), setup_() {}
  /// constructor from a selector and four charges
  CandCombiner(int q1, int q2, int q3, int q4) : base(q1, q2, q3, q4), select_(), selectPair_(), setup_() {}
  /// default constructor
  CandCombiner(const Selector& select) : base(), select_(select), selectPair_(), setup_() {}
  /// constructor from a selector and two charges
  CandCombiner(const Selector& select, int q1, int q2) : base(q1, q2), select_(select), selectPair_(), setup_() {}
  /// constructor from a selector and three charges
  CandCombiner(const Selector& select, int q1, int q2, int q3)
      : base(q1, q2, q3), select_(select), selectPair_(), setup_() {}
  /// constructor from a selector and four charges
  CandCombiner(const Selector& select, int q1, int q2, int q3, int q4)
      : base(q1, q2, q3, q4), select_(select), selectPair_(), setup_() {}
  /// constructor from selector
  CandCombiner(const Selector& select, const PairSelector& selectPair)
      : base(), select_(select), selectPair_(selectPair), setup_() {}
  /// constructor from a selector and two charges
  CandCombiner(const Selector& select, const PairSelector& selectPair, int q1, int q2)
      : base(q1, q2), select_(select), selectPair_(selectPair), setup_() {}
  /// constructor from a selector and three charges
  CandCombiner(const Selector& select, const PairSelector& selectPair, int q1, int q2, int q3)
      : base(q1, q2, q3), select_(select), selectPair_(selectPair), setup_() {}
  /// constructor from a selector and four charges
  CandCombiner(const Selector& select, const PairSelector& selectPair, int q1, int q2, int q3, int q4)
      : base(q1, q2, q3, q4), select_(select), selectPair_(selectPair), setup_() {}
  CandCombiner(const Selector& select, const PairSelector& selectPair, const Setup& setup)
      : base(), select_(select), selectPair_(selectPair), setup_(setup) {}
  /// constructor from a selector and two charges
  CandCombiner(const Selector& select, const PairSelector& selectPair, const Setup& setup, int q1, int q2)
      : base(q1, q2), select_(select), selectPair_(selectPair), setup_(setup) {}
  /// constructor from a selector and three charges
  CandCombiner(const Selector& select, const PairSelector& selectPair, const Setup& setup, int q1, int q2, int q3)
      : base(q1, q2, q3), select_(select), selectPair_(selectPair), setup_(setup) {}
  /// constructor from a selector and four charges
  CandCombiner(
      const Selector& select, const PairSelector& selectPair, const Setup& setup, int q1, int q2, int q3, int q4)
      : base(q1, q2, q3, q4), select_(select), selectPair_(selectPair), setup_(setup) {}
  /// constructor from a selector, specifying to check for charge
  CandCombiner(const Selector& select,
               const PairSelector& selectPair,
               const Setup& setup,
               const std::vector<int>& dauCharge)
      : base(true, dauCharge), select_(select), selectPair_(selectPair), setup_(setup) {}
  /// constructor from a selector, specifying to check for charge
  CandCombiner(const Selector& select, const PairSelector& selectPair, const std::vector<int>& dauCharge)
      : base(true, dauCharge), select_(select), selectPair_(selectPair), setup_() {}
  /// constructor from a selector, specifying to check for charge
  CandCombiner(const std::vector<int>& dauCharge) : base(true, dauCharge), select_(), selectPair_(), setup_() {}
  /// constructor from a selector, specifying optionally to check for charge
  CandCombiner(const Selector& select,
               const PairSelector& selectPair,
               const Setup& setup,
               bool checkCharge,
               bool checkOverlap,
               const std::vector<int>& dauCharge)
      : base(checkCharge, checkOverlap, dauCharge), select_(select), selectPair_(selectPair), setup_(setup) {}
  /// return reference to setup object to allow its initialization
  Setup& setup() { return setup_; }

private:
  /// select a candidate
  bool select(const reco::Candidate& c) const override { return select_(c); }
  /// select a candidate
  bool selectPair(const reco::Candidate& c1, const reco::Candidate& c2) const override { return selectPair_(c1, c2); }
  /// set kinematics to reconstructed composite
  void setup(typename OutputCollection::value_type& c) const override { setup_.set(c); }
  /// add candidate daughter
  void addDaughter(typename OutputCollection::value_type& cmp,
                   const CandPtr& c,
                   const std::string name = "") const override {
    Cloner::addDaughter(cmp, c, name);
  }
  /// candidate selector
  Selector select_;
  /// candidate pair selector
  PairSelector selectPair_;
  /// utility to setup composite candidate kinematics from daughters
  Setup setup_;
};

#endif
