#ifndef L1TStage2CaloLayer1_H
#define L1TStage2CaloLayer1_H

// system include files
#include <memory>
#include <string>
#include <array>
#include <map>

#include "DQMServices/Core/interface/DQMGlobalEDAnalyzer.h"

// user include files
#include "FWCore/Framework/interface/Frameworkfwd.h"

#include "FWCore/Framework/interface/Event.h"
#include "FWCore/Framework/interface/MakerMacros.h"

#include "FWCore/ParameterSet/interface/ParameterSet.h"

#include "FWCore/ServiceRegistry/interface/Service.h"
#include "FWCore/MessageLogger/interface/MessageLogger.h"

#include "DQMServices/Core/interface/DQMStore.h"

#include "DataFormats/EcalDigi/interface/EcalDigiCollections.h"
#include "DataFormats/HcalDigi/interface/HcalDigiCollections.h"

#include "DataFormats/FEDRawData/interface/FEDRawDataCollection.h"

#include "DataFormats/L1TCalorimeter/interface/CaloTower.h"

namespace ComparisonHelper {
  // Designed for edm::SortedCollection<T>
  // Iterators should have a value type that derives from DetID, or
  // otherwise implements value_type::id().
  // Compare should be strict weak ordering (e.g. edm::SortedCollection<T>::key_compare)
  // Mostly http://www.cplusplus.com/reference/algorithm/set_union/
  // But with both collections saved in std::pair
  // Is this necessary? Absolutely not... but it was fun to make!
  template <class InputIterator1, class InputIterator2, class OutputIterator, class Compare>
  OutputIterator zip(InputIterator1 first1,
                     InputIterator1 last1,
                     InputIterator2 first2,
                     InputIterator2 last2,
                     OutputIterator result,
                     Compare comp) {
    typedef typename InputIterator1::value_type dummy1;
    typedef typename InputIterator2::value_type dummy2;
    while (first1 != last1 || first2 != last2) {
      if (first1 == last1) {
        while (first2 != last2) {
          *result = std::make_pair(dummy1{first2->id()}, *first2);
          ++first2;
          ++result;
        }
        return result;
      }
      if (first2 == last2) {
        while (first1 != last1) {
          *result = std::make_pair(*first1, dummy2{first1->id()});
          ++first1;
          ++result;
        }
        return result;
      }
      if (comp(*first1, *first2)) {
        *result = std::make_pair(*first1, dummy2{first1->id()});
        ++first1;
      } else if (comp(*first2, *first1)) {
        *result = std::make_pair(dummy1{first2->id()}, *first2);
        ++first2;
      } else {
        *result = std::make_pair(*first1, *first2);
        ++first1;
        ++first2;
      }
      ++result;
    }
    return result;
  }
}  // namespace ComparisonHelper

namespace CaloL1Information {

  struct monitoringDataHolder {
    dqm::reco::MonitorElement *ecalDiscrepancy_;
    dqm::reco::MonitorElement *ecalLinkError_;
    dqm::reco::MonitorElement *ecalOccupancy_;
    dqm::reco::MonitorElement *hcalDiscrepancy_;
    dqm::reco::MonitorElement *hcalLinkError_;
    dqm::reco::MonitorElement *hcalOccupancy_;

    dqm::reco::MonitorElement *ecalOccEtDiscrepancy_;
    dqm::reco::MonitorElement *ecalOccFgDiscrepancy_;
    dqm::reco::MonitorElement *ecalOccLinkMasked_;
    dqm::reco::MonitorElement *ecalOccRecdEtWgt_;
    dqm::reco::MonitorElement *ecalOccRecdFgVB_;
    dqm::reco::MonitorElement *ecalOccSentAndRecd_;
    dqm::reco::MonitorElement *ecalOccSentFgVB_;
    dqm::reco::MonitorElement *ecalOccSent_;
    dqm::reco::MonitorElement *ecalOccTowerMasked_;
    dqm::reco::MonitorElement *ecalTPRawEtCorrelation_;
    dqm::reco::MonitorElement *ecalTPRawEtDiffNoMatch_;
    dqm::reco::MonitorElement *ecalTPRawEtRecd_;
    dqm::reco::MonitorElement *ecalTPRawEtSentAndRecd_;
    dqm::reco::MonitorElement *ecalTPRawEtSent_;

    dqm::reco::MonitorElement *ecalOccSentNotRecd_;
    dqm::reco::MonitorElement *ecalOccRecdNotSent_;
    dqm::reco::MonitorElement *ecalOccNoMatch_;

    dqm::reco::MonitorElement *hcalOccEtDiscrepancy_;
    dqm::reco::MonitorElement *hcalOccLLPFbDiscrepancy_;
    dqm::reco::MonitorElement *hcalOccLLPFbExpd_;
    dqm::reco::MonitorElement *hcalOccLLPFbData_;
    dqm::reco::MonitorElement *hcalOccLinkMasked_;
    dqm::reco::MonitorElement *hcalOccRecdEtWgt_;
    dqm::reco::MonitorElement *hcalOccFg0Discrepancy_;
    dqm::reco::MonitorElement *hcalOccFg1Discrepancy_;
    dqm::reco::MonitorElement *hcalOccFg2Discrepancy_;
    dqm::reco::MonitorElement *hcalOccFg3Discrepancy_;
    //dqm::reco::MonitorElement *hcalOccFg4Discrepancy_;
    //dqm::reco::MonitorElement *hcalOccFg5Discrepancy_;
    dqm::reco::MonitorElement *hcalOccRecdFg0_;
    dqm::reco::MonitorElement *hcalOccRecdFg1_;
    dqm::reco::MonitorElement *hcalOccRecdFg2_;
    dqm::reco::MonitorElement *hcalOccRecdFg3_;
    dqm::reco::MonitorElement *hcalOccRecdFg4_;
    dqm::reco::MonitorElement *hcalOccRecdFg5_;
    dqm::reco::MonitorElement *hcalOccSentAndRecd_;
    dqm::reco::MonitorElement *hcalOccSentFg0_;
    dqm::reco::MonitorElement *hcalOccSentFg1_;
    dqm::reco::MonitorElement *hcalOccSentFg2_;
    dqm::reco::MonitorElement *hcalOccSentFg3_;
    dqm::reco::MonitorElement *hcalOccSentFg4_;
    dqm::reco::MonitorElement *hcalOccSentFg5_;
    dqm::reco::MonitorElement *hcalOccSent_;
    dqm::reco::MonitorElement *hcalOccTowerMasked_;
    dqm::reco::MonitorElement *hcalTPRawEtCorrelationHBHE_;
    dqm::reco::MonitorElement *hcalTPRawEtCorrelationHF_;
    dqm::reco::MonitorElement *hcalTPRawEtDiffNoMatch_;
    dqm::reco::MonitorElement *hcalTPRawEtRecd_;
    dqm::reco::MonitorElement *hcalTPRawEtSentAndRecd_;
    dqm::reco::MonitorElement *hcalTPRawEtSent_;

    dqm::reco::MonitorElement *hcalOccSentNotRecd_;
    dqm::reco::MonitorElement *hcalOccRecdNotSent_;
    dqm::reco::MonitorElement *hcalOccNoMatch_;

    dqm::reco::MonitorElement *ECALmismatchesPerBx_;
    dqm::reco::MonitorElement *HBHEmismatchesPerBx_;
    dqm::reco::MonitorElement *HFmismatchesPerBx_;

    dqm::reco::MonitorElement *bxidErrors_;
    dqm::reco::MonitorElement *l1idErrors_;
    dqm::reco::MonitorElement *orbitErrors_;

    dqm::reco::MonitorElement *ecalLinkErrorByLumi_;
    dqm::reco::MonitorElement *ecalMismatchByLumi_;
    dqm::reco::MonitorElement *hcalLinkErrorByLumi_;
    dqm::reco::MonitorElement *hcalMismatchByLumi_;

    dqm::reco::MonitorElement *maxEvtLinkErrorsByLumiECAL_;
    dqm::reco::MonitorElement *maxEvtLinkErrorsByLumiHCAL_;
    dqm::reco::MonitorElement *maxEvtLinkErrorsByLumi_;

    dqm::reco::MonitorElement *maxEvtMismatchByLumiECAL_;
    dqm::reco::MonitorElement *maxEvtMismatchByLumiHCAL_;
    dqm::reco::MonitorElement *maxEvtMismatchByLumi_;

    dqm::reco::MonitorElement *last20Mismatches_;

    dqm::reco::MonitorElement *ecalOccRecd5Bx_;
    dqm::reco::MonitorElement *ecalOccRecd5BxEtWgt_;
    dqm::reco::MonitorElement *ecalOccRecdBx1_;
    dqm::reco::MonitorElement *ecalOccRecdBx2_;
    dqm::reco::MonitorElement *ecalOccRecdBx3_;
    dqm::reco::MonitorElement *ecalOccRecdBx4_;
    dqm::reco::MonitorElement *ecalOccRecdBx5_;

    dqm::reco::MonitorElement *slot7bit_;

    std::vector<std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>>> runMismatchList;
  };

  struct perStreamMonitoringDataHolder {
    int streamNumMaxEvtLinkErrorsECAL{0};
    int streamNumMaxEvtLinkErrorsHCAL{0};
    int streamNumMaxEvtLinkErrors{0};

    int streamNumMaxEvtMismatchECAL{0};
    int streamNumMaxEvtMismatchHCAL{0};
    int streamNumMaxEvtMismatch{0};
    std::vector<std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>>> streamMismatchList;
  };

  struct perLumiBlockMonitoringInformation {
    int lumiNumMaxEvtLinkErrorsECAL{0};
    int lumiNumMaxEvtLinkErrorsHCAL{0};
    int lumiNumMaxEvtLinkErrors{0};

    int lumiNumMaxEvtMismatchECAL{0};
    int lumiNumMaxEvtMismatchHCAL{0};
    int lumiNumMaxEvtMismatch{0};

    std::vector<std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>>> lumiMismatchList;
  };

  struct perRunSummaryMonitoringInformation {
    std::vector<std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>>> runMismatchList;
  };

}  // namespace CaloL1Information

class L1TStage2CaloLayer1
    : public DQMGlobalRunSummaryEDAnalyzer<
          CaloL1Information::monitoringDataHolder,
          CaloL1Information::perRunSummaryMonitoringInformation,
          edm::StreamCache<CaloL1Information::perStreamMonitoringDataHolder>,
          edm::LuminosityBlockSummaryCache<CaloL1Information::perLumiBlockMonitoringInformation>> {
public:
  L1TStage2CaloLayer1(const edm::ParameterSet &ps);
  ~L1TStage2CaloLayer1() override;

protected:
  //DQM module implemented functionality
  void dqmBeginRun(edm::Run const &, edm::EventSetup const &, CaloL1Information::monitoringDataHolder &) const override;
  void bookHistograms(DQMStore::IBooker &ibooker,
                      const edm::Run &,
                      const edm::EventSetup &,
                      CaloL1Information::monitoringDataHolder &eventMonitors) const override;
  void dqmAnalyze(edm::Event const &,
                  edm::EventSetup const &,
                  CaloL1Information::monitoringDataHolder const &) const override;
  void dqmEndRun(edm::Run const &,
                 edm::EventSetup const &,
                 CaloL1Information::monitoringDataHolder const &,
                 CaloL1Information::perRunSummaryMonitoringInformation const &) const override;
  //stream functionality
  std::unique_ptr<CaloL1Information::perStreamMonitoringDataHolder> beginStream(edm::StreamID) const override {
    return std::make_unique<CaloL1Information::perStreamMonitoringDataHolder>();
  };
  void streamEndLuminosityBlock(edm::StreamID, edm::LuminosityBlock const &, edm::EventSetup const &) const override{};

  //lumi summary functionality
  void streamEndLuminosityBlockSummary(edm::StreamID,
                                       edm::LuminosityBlock const &,
                                       edm::EventSetup const &,
                                       CaloL1Information::perLumiBlockMonitoringInformation *) const override;

  std::shared_ptr<CaloL1Information::perLumiBlockMonitoringInformation> globalBeginLuminosityBlockSummary(
      edm::LuminosityBlock const &, edm::EventSetup const &) const override {
    return std::make_unique<CaloL1Information::perLumiBlockMonitoringInformation>();
  };

  void globalEndLuminosityBlockSummary(edm::LuminosityBlock const &,
                                       edm::EventSetup const &,
                                       CaloL1Information::perLumiBlockMonitoringInformation *) const override;

  //run summary functionality.
  std::shared_ptr<CaloL1Information::perRunSummaryMonitoringInformation> globalBeginRunSummary(
      edm::Run const &, edm::EventSetup const &) const override {
    return std::make_unique<CaloL1Information::perRunSummaryMonitoringInformation>();
  };

  void streamEndRunSummary(edm::StreamID,
                           edm::Run const &,
                           edm::EventSetup const &,
                           CaloL1Information::perRunSummaryMonitoringInformation *) const override;

  void globalEndRunSummary(edm::Run const &,
                           edm::EventSetup const &,
                           CaloL1Information::perRunSummaryMonitoringInformation *) const override;

private:
  void updateMismatch(const edm::Event &e,
                      int mismatchType,
                      std::vector<std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>>>
                          &streamMismatches) const;

  void mergeMismatchVectors(
      std::vector<std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>>> &,
      std::vector<std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>>> &) const;

  bool isLaterMismatch(
      std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>> &candidateMismatch,
      std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>> &comparisonMismatch) const;

  int findIndex(std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>>,
                std::vector<std::tuple<edm::RunID, edm::LuminosityBlockID, edm::EventID, std::vector<int>>>,
                int lowerIndexToSearch,
                int upperIndexToSearch) const;
  // Input and config info
  edm::EDGetTokenT<EcalTrigPrimDigiCollection> ecalTPSourceRecd_;
  std::string ecalTPSourceRecdLabel_;
  edm::EDGetTokenT<EcalTrigPrimDigiCollection> ecalTPSourceRecdBx1_;
  std::string ecalTPSourceRecdBx1Label_;
  edm::EDGetTokenT<EcalTrigPrimDigiCollection> ecalTPSourceRecdBx2_;
  std::string ecalTPSourceRecdBx2Label_;
  edm::EDGetTokenT<EcalTrigPrimDigiCollection> ecalTPSourceRecdBx3_;
  std::string ecalTPSourceRecdBx3Label_;
  edm::EDGetTokenT<EcalTrigPrimDigiCollection> ecalTPSourceRecdBx4_;
  std::string ecalTPSourceRecdBx4Label_;
  edm::EDGetTokenT<EcalTrigPrimDigiCollection> ecalTPSourceRecdBx5_;
  std::string ecalTPSourceRecdBx5Label_;
  edm::EDGetTokenT<HcalTrigPrimDigiCollection> hcalTPSourceRecd_;
  std::string hcalTPSourceRecdLabel_;
  edm::EDGetTokenT<EcalTrigPrimDigiCollection> ecalTPSourceSent_;
  std::string ecalTPSourceSentLabel_;
  edm::EDGetTokenT<HcalTrigPrimDigiCollection> hcalTPSourceSent_;
  std::string hcalTPSourceSentLabel_;
  edm::EDGetTokenT<l1t::CaloTowerBxCollection> CaloTowerCollectionData_;
  std::string CaloTowerCollectionDataLabel_;
  edm::EDGetTokenT<FEDRawDataCollection> fedRawData_;
  std::string histFolder_;
  int tpFillThreshold_;
  int tpFillThreshold5Bx_;
  bool ignoreHFfbs_;
};

#endif
