#ifndef UtilAlgos_Matcher_h
#define UtilAlgos_Matcher_h
/* \class Matcher
 *
 * \author Luca Lista, INFN
 *
 */
#include "FWCore/Framework/interface/global/EDProducer.h"
#include "FWCore/ParameterSet/interface/ParameterSet.h"
#include "FWCore/Utilities/interface/InputTag.h"
#include "CommonTools/UtilAlgos/interface/DeltaR.h"
#include "FWCore/Framework/interface/Event.h"
#include "DataFormats/Common/interface/Handle.h"
#include "DataFormats/Common/interface/AssociationMap.h"
#include "DataFormats/Common/interface/OneToOne.h"
#include "DataFormats/Common/interface/getRef.h"

namespace reco {
  namespace modules {
    template <typename C1, typename C2, typename M = edm::AssociationMap<edm::OneToOne<C1, C2> > >
    class MatcherBase : public edm::global::EDProducer<> {
    public:
      MatcherBase(const edm::ParameterSet&);
      ~MatcherBase() override;

    protected:
      typedef typename C1::value_type T1;
      typedef typename C2::value_type T2;
      typedef M MatchMap;

    private:
      void produce(edm::StreamID, edm::Event&, const edm::EventSetup&) const override;
      edm::EDGetTokenT<C1> srcToken_;
      edm::EDGetTokenT<C2> matchedToken_;
      double distMin_;
      virtual double matchDistance(const T1&, const T2&) const = 0;
      virtual bool select(const T1&, const T2&) const = 0;
    };

    template <typename C1,
              typename C2,
              typename S,
              typename D = DeltaR<typename C1::value_type, typename C2::value_type>,
              typename M = edm::AssociationMap<edm::OneToOne<C1, C2> > >
    class Matcher : public MatcherBase<C1, C2, M> {
    public:
      Matcher(const edm::ParameterSet& cfg)
          : MatcherBase<C1, C2, M>(cfg), select_(reco::modules::make<S>(cfg)), distance_(reco::modules::make<D>(cfg)) {}
      ~Matcher() override {}

    private:
      typedef typename MatcherBase<C1, C2, M>::T1 T1;
      typedef typename MatcherBase<C1, C2, M>::T2 T2;
      typedef typename MatcherBase<C1, C2, M>::MatchMap MatchMap;

      double matchDistance(const T1& c1, const T2& c2) const override { return distance_(c1, c2); }
      bool select(const T1& c1, const T2& c2) const override { return select_(c1, c2); }
      S select_;
      D distance_;
    };

    namespace helper {
      typedef std::pair<size_t, double> MatchPair;

      struct SortBySecond {
        bool operator()(const MatchPair& p1, const MatchPair& p2) const { return p1.second < p2.second; }
      };
    }  // namespace helper

    template <typename C1, typename C2, typename M>
    MatcherBase<C1, C2, M>::MatcherBase(const edm::ParameterSet& cfg)
        : srcToken_(consumes<C1>(cfg.template getParameter<edm::InputTag>("src"))),
          matchedToken_(consumes<C2>(cfg.template getParameter<edm::InputTag>("matched"))),
          distMin_(cfg.template getParameter<double>("distMin")) {
      produces<MatchMap>();
    }

    template <typename C1, typename C2, typename M>
    MatcherBase<C1, C2, M>::~MatcherBase() {}

    template <typename C1, typename C2, typename M>
    void MatcherBase<C1, C2, M>::produce(edm::StreamID, edm::Event& evt, const edm::EventSetup&) const {
      using namespace edm;
      using namespace std;
      Handle<C2> matched;
      evt.getByToken(matchedToken_, matched);
      Handle<C1> cands;
      evt.getByToken(srcToken_, cands);
      typedef typename MatchMap::ref_type ref_type;
      typedef typename ref_type::key_type key_ref_type;
      typedef typename ref_type::value_type value_ref_type;
      unique_ptr<MatchMap> matchMap(new MatchMap(ref_type(key_ref_type(cands), value_ref_type(matched))));
      for (size_t c = 0; c != cands->size(); ++c) {
        const T1& cand = (*cands)[c];
        vector<helper::MatchPair> v;
        for (size_t m = 0; m != matched->size(); ++m) {
          const T2& match = (*matched)[m];
          if (select(cand, match)) {
            double dist = matchDistance(cand, match);
            if (dist < distMin_)
              v.push_back(make_pair(m, dist));
          }
        }
        if (!v.empty()) {
          size_t mMin = min_element(v.begin(), v.end(), helper::SortBySecond())->first;
          typedef typename MatchMap::key_type key_type;
          typedef typename MatchMap::data_type data_type;
          matchMap->insert(edm::getRef(cands, c), edm::getRef(matched, mMin));
        }
      }
      evt.put(std::move(matchMap));
    }

  }  // namespace modules
}  // namespace reco

#endif
