#include <RAT/MissedMuonFollowerCut.hh>
#include <RAT/DBJsonLoader.hh>
#include <RAT/DB.hh>
#include <RAT/BitManip.hh>

#include <RAT/DBTable.hh>
#include <RAT/DBTextLoader.hh>
#include <RAT/DU/TrigBits.hh>
#include <RAT/Log.hh>


namespace RAT {

  void MissedMuonFollowerCut::BeginOfRun(DS::Run&)
  {
      fLClean = DB::Get()->GetLink("DATACLEANING",fName);
      fNhitMin = static_cast<unsigned int>( fLClean->GetI("primary_nhit_threshold") );
      fSecondaryNhitMin = static_cast<unsigned int>( fLClean->GetI("secondary_nhit_threshold") );
      fTimeWindow = fLClean->GetD("time_window");

      fLastHighNHitGTID = 0;
      fInWindow = false;


      // Initialize various DB/Table/Column names
      fDBName = "MissedMuonFollower.ratdb";
      fDBTableName = "MISSEDMUONFOLLOWER";
      fDBGTIDColumnName = "gtid_missedmuons";
      fDBDaysColumnName = "days_missedmuons";
      fDBSecondsColumnName = "secs_missedmuons";
      fDBNanoSecondsColumnName = "nsecs_missedmuons";

      if (fProcessPass == 2){
          bool foundDBFile = false;
          try {
              DBLinkPtr MissedMuonsDB = DB::Get()->GetLink(fDBTableName);
              fEventStore = MissedMuonsDB->GetIArray(fDBGTIDColumnName);
              foundDBFile = true;
          }
          catch (const DBNotFoundError &e) {
              warn << "MissedMuonFollowerCut::BeginOfRun: Couldn't access DB."
                      "Attempting to use local file.\n";
              foundDBFile = false;
          }
          if(!foundDBFile) {
              try {
                  std::vector<DBTable *> contents = DBJsonLoader::parse(fDBName);
                  fEventStore = contents[0]->GetIArray(fDBGTIDColumnName);

              } catch (const FileError &e) {
                  warn << "MissedMuonFollowerCut::BeginOfRun: "
                          "Couldn't find file of Missed Muon GTIDS. "
                          "Results of this cut won't be useful\n";
              } catch (const DBWrongTypeError &e) {
                  // Will occur if there were no missed muons
                  // found previously. Nothing to do.
              }
          }
      }
  }

  Processor::Result MissedMuonFollowerCut::DSEvent(DS::Run&, DS::Entry& ds)
  {
    bool pass = true; // If any triggered event fails, they all fail
    for( size_t iEV = 0; iEV < ds.GetEVCount(); iEV++ )
    {
      if( Event(ds, ds.GetEV(iEV)) != OKTRUE )
        pass = false;
    }
    return pass ? OKTRUE : OKFALSE;
  }

  // Used for comparing times between two events. Returns the time difference
  // between the arguements in nanoseconds
  double MissedMuonFollowerCut::TimeDiff(DS::UniversalTime time_a, DS::UniversalTime time_b) {
      UInt_t sec_a(time_a.GetSeconds()), sec_b(time_b.GetSeconds());
      UInt_t days_a(time_a.GetDays()), days_b(time_b.GetDays());
      double nsec_a(time_a.GetNanoSeconds()), nsec_b(time_b.GetNanoSeconds());
      // 86400 seconds in a day
      return ((days_a - days_b)*86400 + (sec_a - sec_b)*1e9) + (nsec_a - nsec_b);
  }

  Processor::Result MissedMuonFollowerCut::Event(DS::Entry&, DS::EV& ev)
  {
      fPassFlag = true;
      if (fProcessPass == 1) {
          unsigned int nhit = ev.GetUncalPMTs().GetCount();
          // For pass 1 the idea is to just find all events greater than some
          // threshold nhit that happen closer in time than some threshold time.
          // Then store all the GTIDs that match that criteria in a file

          // check trigger word to make sure there was a valid physics event.
          // throw out pedestal, and ext_a triggers
          if (BitManip::TestBit(ev.GetTrigType(), DU::TrigBits::Pedestal) ||
              BitManip::TestBit(ev.GetTrigType(), DU::TrigBits::EXTASY)) {
              return fPassFlag ? OKFALSE : OKTRUE;
          }
          if (!fInWindow && nhit >= fNhitMin) {
              // If you're not in a window and a potential 'missed muon'
              // occurs then jot down the event gtid and time.
              // And begin a window
             fInWindow = true;
             fLastHighNHitGTID = ev.GetGTID();
             fTimeOfLastHighNhit = ev.GetUniversalTime();
          }
          else if (fInWindow && nhit >= fSecondaryNhitMin &&
                  TimeDiff(ev.GetUniversalTime(), fTimeOfLastHighNhit) <= fTimeWindow) {
              // If you're in a window and a event with high enough nhit
              // to be a follower occurs.
              // Then save to DB/JSON that event and time.
              // Then also say you're no longer in an mmf window b/c this one
              // has been properly marked already.
              // Unless this event is above the primary nhit threshold...
              // then this is the 'start' of the next follower window
              fEventStore.push_back(fLastHighNHitGTID);
              fDaysTimeStore.push_back((int)fTimeOfLastHighNhit.GetDays());
              fSecsTimeStore.push_back((int)fTimeOfLastHighNhit.GetSeconds());
              fNSecsTimeStore.push_back((int)fTimeOfLastHighNhit.GetNanoSeconds());

              // Check if event is above primary nhit threshold, if it isn't
              // reset the window.
              if( nhit >= fNhitMin) {
                  fLastHighNHitGTID = ev.GetGTID();
                  fTimeOfLastHighNhit = ev.GetUniversalTime();
              } else {
                  fInWindow = false;
              }
          }
          else if (fInWindow &&
                  TimeDiff(ev.GetUniversalTime(), fTimeOfLastHighNhit) > fTimeWindow) {
            fInWindow = false;
          }

          return fPassFlag ? OKFALSE : OKTRUE;

      } else {  // fProcessPass == 2
          // For pass two just need to mark all events with a GTID that was
          // flagged in pass 1
          int gtid = ev.GetGTID();
          DS::UniversalTime time = ev.GetUniversalTime();
          DS::UniversalTime mostRecentMissedMuon;
          for (size_t i=0;i<fEventStore.size();i++){
              if (gtid == fEventStore[i]){
                  fTimeOfLastHighNhit = time;
                  fLastHighNHitGTID = gtid;
                  fPassFlag = false;
                  UpdateMask(ev);
                  return fPassFlag ? OKFALSE : OKTRUE;
              }
          }
          if(TimeDiff(time, fTimeOfLastHighNhit) < fTimeWindow && fLastHighNHitGTID) {
              fPassFlag = false;
              UpdateMask(ev);
              return fPassFlag ? OKFALSE : OKTRUE;
          }
      }

      UpdateMask(ev);
      return fPassFlag ? OKFALSE : OKTRUE;
  }

  void MissedMuonFollowerCut::EndOfRun(DS::Run& run)
  {
      if (fProcessPass == 1){
          // Now store the results in a ratdb file
          DBTable table(fDBTableName);
          table.SetI("version", 1);
          table.SetPassNumber(-1);
          table.SetRunRange(run.GetRunID(), run.GetRunID());
          table.SetIArray(fDBGTIDColumnName,fEventStore);
          table.SetIArray(fDBDaysColumnName,fDaysTimeStore);
          table.SetIArray(fDBSecondsColumnName,fSecsTimeStore);
          table.SetIArray(fDBNanoSecondsColumnName,fNSecsTimeStore);
          table.SaveAs(fDBName);
      }
  }
}