/* Copyright (C) 2014-2020 Institut fuer Kernphysik, Goethe-Universitaet Frankfurt, Frankfurt
   SPDX-License-Identifier: GPL-3.0-only
   Authors: Philipp Sitzmann [committer], Florian Uhlig, Volker Friese */

// -------------------------------------------------------------------------
// -----                    CbmMvdDigitizer source file                -----
// -------------------------------------------------------------------------

// Includes from MVD
#include "CbmMvdDigitizer.h"

#include "CbmMatch.h"
#include "CbmMvdDetector.h"
#include "CbmMvdDigi.h"
#include "CbmMvdPileupManager.h"
#include "CbmMvdPoint.h"
#include "SensorDataSheets/CbmMvdMimosa26AHR.h"
#include "plugins/tasks/CbmMvdSensorDigitizerTask.h"
#include "tools/CbmMvdGeoHandler.h"

// Includes from FAIR
#include "FairModule.h"
#include "FairRootManager.h"
#include <Logger.h>

// Includes from ROOT
#include "TClonesArray.h"
#include "TStopwatch.h"

// Includes from C++
#include <cassert>
#include <iomanip>
#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;

// -----   Default constructor   ------------------------------------------
CbmMvdDigitizer::CbmMvdDigitizer()
  : CbmDigitize<CbmMvdDigi>("MvdDigitize")
  , fMode(0)
  , fShowDebugHistos(kFALSE)
  , fNoiseSensors(kFALSE)
  , fDetector(nullptr)
  , fInputPoints(nullptr)
  , fMcPileUp(nullptr)
  , fTmpMatch(nullptr)
  , fTmpDigi(nullptr)
  , fDigiVect()
  , fMatchVect()
  , fPerformanceDigi()
  , fDigiPluginNr(0)
  , fFakeRate(-1.)
  , fNPileup(-1)
  , fNDeltaElect(-1)
  , fDeltaBufferSize(-1)
  , fBgBufferSize(-1)
  , epsilon()
  , fInputBranchName("")
  , fBgFileName("")
  , fDeltaFileName("")
  , fTimer()
  , fPileupManager(nullptr)
  , fDeltaManager(nullptr)
{
}
// -------------------------------------------------------------------------

// -----   Standard constructor   ------------------------------------------
CbmMvdDigitizer::CbmMvdDigitizer(const char* name, Int_t iMode, Int_t /*iVerbose*/)
  : CbmDigitize<CbmMvdDigi>(name)
  , fMode(iMode)
  , fShowDebugHistos(kFALSE)
  , fNoiseSensors(kFALSE)
  , fDetector(nullptr)
  , fInputPoints(nullptr)
  , fMcPileUp(nullptr)
  , fTmpMatch(nullptr)
  , fTmpDigi(nullptr)
  , fDigiVect()
  , fMatchVect()
  , fPerformanceDigi()
  , fDigiPluginNr(0)
  , fFakeRate(-1.)
  , fNPileup(0)
  , fNDeltaElect(0)
  , fDeltaBufferSize(-1)
  , fBgBufferSize(-1)
  , epsilon()
  , fInputBranchName("MvdPoint")
  , fBgFileName("")
  , fDeltaFileName("")
  , fTimer()
  , fPileupManager(nullptr)
  , fDeltaManager(nullptr)
{
}
// -------------------------------------------------------------------------

// -----   Destructor   ----------------------------------------------------
CbmMvdDigitizer::~CbmMvdDigitizer()
{

  if (fMcPileUp) {
    fMcPileUp->Delete();
    delete fMcPileUp;
  }
  delete fPileupManager;
  delete fDeltaManager;
}
// -----------------------------------------------------------------------------

// -----   Exec   --------------------------------------------------------------
void CbmMvdDigitizer::Exec(Option_t* /*opt*/)
{
  // --- Start timer
  using namespace std;

  fTimer.Start();
  GetEventInfo();  // event number and time

  BuildEvent();
  Int_t nPoints = fInputPoints->GetEntriesFast();
  Int_t nDigis  = 0;
  if (fInputPoints->GetEntriesFast() > 0) {
    LOG(debug) << "//----------------------------------------//";
    LOG(debug) << fName << ": Send Input";
    fDetector->SendInput(fInputPoints);
    LOG(debug) << fName << ": Execute DigitizerPlugin Nr. " << fDigiPluginNr;
    fDetector->Exec(fDigiPluginNr);
    LOG(debug) << fName << ": End Chain";

    // --- Send produced digis to DAQ
    fTmpDigi  = fDetector->GetOutputDigis();
    fTmpMatch = fDetector->GetOutputDigiMatchs();

    Int_t nEntries = fTmpDigi->GetEntriesFast();
    for (Int_t index = 0; index < nEntries; index++) {

      CbmMvdDigi* digi  = dynamic_cast<CbmMvdDigi*>(fTmpDigi->At(index));
      CbmMvdDigi* digi1 = new CbmMvdDigi(*digi);
      assert(digi1);
      fDigiVect.push_back(digi1);

      //     CbmMatch match{*(dynamic_cast<CbmMatch*>(fTmpMatch->At(index)))};
      //     CbmMatch* match1 = new CbmMatch(match);
      CbmMatch* match  = dynamic_cast<CbmMatch*>(fTmpMatch->At(index));
      CbmMatch* match1 = new CbmMatch(*match);
      fMatchVect.push_back(match1);

      //digi1->SetMatch(match1);
      SendData(digi1, match1);
      nDigis++;
    }


    /*
   for (Int_t index = 0; index < fTmpDigi->GetEntriesFast(); index++) {

     LOG(info) << "Size: " << fTmpDigi->GetEntriesFast() << ", " 
               << fTmpDigi->GetEntries();
 
     CbmMvdDigi* digi = dynamic_cast<CbmMvdDigi*>(fTmpDigi->Remove(fTmpDigi->At(index)));
     digi->Print();
     fDigiVect.push_back(digi);
        
     CbmMatch* match = dynamic_cast<CbmMatch*>(fTmpMatch->Remove(fTmpMatch->At(index)));
     match->Print();
     fMatchVect.push_back(match);

     digi->SetMatch(match);
     SendDigi(digi);
     nDigis++;
   }
*/
    // TODO: (VF) There seem to be no entries in the match array, nor matches
    // attached to the digi object
    LOG(debug) << fName << ": Sent " << nDigis << " digis to DAQ";

    //fDigis->AbsorbObjects(fDetector->GetOutputDigis(),0,fDetector->GetOutputArray(fDigiPluginNr)->GetEntriesFast()-1);
    //LOG(debug) << "Total of " << fDigis->GetEntriesFast() << " digis in this Event";
    //if(fVerbose) cout << "Start writing DigiMatchs" << endl;
    //fDigiMatch->AbsorbObjects(fDetector->GetOutputDigiMatchs(),0,fDetector->GetOutputDigiMatchs()->GetEntriesFast()-1);
    //if(fVerbose) cout << "Total of " << fDigiMatch->GetEntriesFast() << " digisMatch in this Event" << endl;
    LOG(debug) << "//----------------------------------------//";
    //LOG(info) << "+ " << setw(20) << GetName() << ": Created: "
    //          << fDigis->GetEntriesFast() << " digis in "
    //          << fixed << setprecision(6) << fTimer.RealTime() << " s";
  }

  // --- Event log
  fTimer.Stop();
  LOG(info) << "+ " << setw(15) << GetName() << ": Event " << setw(6) << right << fCurrentEvent << " at " << fixed
            << setprecision(3) << fCurrentEventTime << " ns, points: " << nPoints << ", digis: " << nDigis
            << ". Exec time " << setprecision(6) << fTimer.RealTime() << " s.";
}
// -----------------------------------------------------------------------------

// -----   Init   --------------------------------------------------------------
InitStatus CbmMvdDigitizer::Init()
{
  cout << "-I- " << GetName() << ": Initialisation..." << endl;
  cout << endl;
  cout << "---------------------------------------------" << endl;
  cout << "-I- Initialising " << GetName() << " ...." << endl;

  // **********  RootManager
  FairRootManager* ioman = FairRootManager::Instance();
  if (!ioman) {
    cout << "-E- " << GetName() << "::Init: No FairRootManager!" << endl;
    return kFATAL;
  }

  // **********  Get input arrays
  fInputPoints = (TClonesArray*) ioman->GetObject("MvdPoint");

  if (!fInputPoints) {
    LOG(error) << "No MvdPoint branch found. There was no MVD in the "
                  "simulation. Switch this task off";
    return kERROR;
  }


  // **********  Register output arrays
  RegisterOutput();
  fMcPileUp = new TClonesArray("CbmMvdPoint", 10000);
  ioman->Register("MvdPileUpMC", "Mvd MC Points after Pile Up", fMcPileUp, IsOutputBranchPersistent("MvdPileUpMC"));

  fDetector = CbmMvdDetector::Instance();

  // **********  Create pileup manager if necessary
  if (fNPileup >= 1 && !(fPileupManager) && fMode == 0) {
    if (fBgFileName == "") {
      cout << "-E- " << GetName() << "::Init: Pileup events needed, but no "
           << " background file name given! " << endl;
      return kERROR;
    }
    fPileupManager = new CbmMvdPileupManager(fBgFileName, fInputBranchName, fBgBufferSize);
    if (fPileupManager->GetNEvents() < 2 * fNPileup) {
      cout << "-E- " << GetName()
           << ": The size of your BG-File is insufficient to perform the "
              "requested pileup"
           << endl;
      cout << "    You need at least events > 2* fNPileup." << endl;
      cout << "    Detected: fPileUp = " << fNPileup << ", events in file " << fPileupManager->GetNEvents() << endl;
      Fatal(GetName(), "The size of your BG-File is insufficient");
      return kERROR;
    }
  }

  // **********   Create delta electron manager if required
  if (fNDeltaElect >= 1 && !(fDeltaManager) && fMode == 0) {
    if (fDeltaFileName == "") {
      cout << "-E- " << GetName() << "::Init: Pileup events needed, but no "
           << " background file name given! " << endl;
      return kERROR;
    }
    fDeltaManager = new CbmMvdPileupManager(fDeltaFileName, fInputBranchName, fDeltaBufferSize);
    if (fDeltaManager->GetNEvents() < 2 * fNDeltaElect) {
      cout << "-E- " << GetName()
           << ": The size of your Delta-File is insufficient to perform the "
              "requested pileup"
           << endl;
      cout << "    You need at least events > 2* fNDeltaElect." << endl;
      cout << "    Detected: fNDeltaElect = " << fNDeltaElect << ", events in file " << fDeltaManager->GetNEvents()
           << endl;
      Fatal(GetName(), "The size of your Delta-File is insufficient");
      return kERROR;
    }
  }
  CbmMvdSensorDigitizerTask* digiTask = new CbmMvdSensorDigitizerTask();

  fDetector->AddPlugin(digiTask);
  fDigiPluginNr = (UInt_t)(fDetector->GetPluginArraySize());
  if (fShowDebugHistos) fDetector->ShowDebugHistos();
  fDetector->Init();

  if (fNoiseSensors) fDetector->SetProduceNoise();

  // Screen output
  cout << GetName() << " initialised with parameters: " << endl;
  //PrintParameters();
  cout << "---------------------------------------------" << endl;


  return kSUCCESS;
}

// -----   Virtual public method Reinit   ----------------------------------
InitStatus CbmMvdDigitizer::ReInit() { return kSUCCESS; }
// -------------------------------------------------------------------------


// -----   Virtual method Finish   -----------------------------------------
void CbmMvdDigitizer::Finish()
{
  // cout<< endl << "finishing" << endl;
  fDetector->Finish();
  PrintParameters();
}
// -------------------------------------------------------------------------


// -----   Private method Reset   ------------------------------------------
void CbmMvdDigitizer::Reset() {}
// -------------------------------------------------------------------------


// -----   Reset output arrays   -------------------------------------------
void CbmMvdDigitizer::ResetArrays()
{
  if (fMcPileUp) fMcPileUp->Delete();
  if (fTmpMatch) fTmpMatch->Delete();
  if (fTmpDigi) fTmpDigi->Delete();
  fDigiVect.clear();
  fMatchVect.clear();
}
// -------------------------------------------------------------------------


// -----   Private method GetMvdGeometry   ---------------------------------
void CbmMvdDigitizer::GetMvdGeometry() {}
// -------------------------------------------------------------------------


// -----   Private method PrintParameters   --------------------------------
void CbmMvdDigitizer::PrintParameters()
{

  using namespace std;

  cout.setf(ios_base::fixed, ios_base::floatfield);
  cout << "============================================================" << endl;
  cout << "============== Parameters MvdDigitizer =====================" << endl;
  cout << "============================================================" << endl;
  cout << "=============== End Task ===================================" << endl;
}
// -------------------------------------------------------------------------

// ---------------  Build Event  ------------------------------------------------------
void CbmMvdDigitizer::BuildEvent()
{

  // Some frequently used variables
  CbmMvdPoint* point = nullptr;
  Int_t nOrig        = 0;
  Int_t nPile        = 0;
  Int_t nElec        = 0;

  // ----- First treat standard input file
  nOrig = fInputPoints->GetEntriesFast();


  // ----- Then treat event pileup
  if (fNPileup > 0) {

    // --- Vector of available background events from pile-up.
    // --- Each event is used only once.
    Int_t nBuffer = fPileupManager->GetNEvents();
    vector<Int_t> freeEvents(nBuffer);
    for (Int_t i = 0; i < nBuffer; i++)
      freeEvents[i] = i;

    // --- Loop over pile-up events
    for (Int_t iBg = 0; iBg < fNPileup; iBg++) {

      // Select random event from vector and remove it after usage
      Int_t index = gRandom->Integer(freeEvents.size());

      Int_t iEvent         = freeEvents[index];
      TClonesArray* points = fPileupManager->GetEvent(iEvent);
      freeEvents.erase(freeEvents.begin() + index);

      // Add points from this event to the input arrays
      for (Int_t iPoint = 0; iPoint < points->GetEntriesFast(); iPoint++) {
        point = (CbmMvdPoint*) points->At(iPoint);
        point->SetTrackID(-2);
        nPile++;
        new ((*fInputPoints)[fInputPoints->GetEntriesFast()]) CbmMvdPoint(*((CbmMvdPoint*) points->At(iPoint)));
      }


    }  // Pileup event loop

  }  // Usage of pile-up


  // ----- Finally, treat delta electrons
  if (fNDeltaElect > 0) {

    // --- Vector of available delta events.
    // --- Each event is used only once.
    Int_t nDeltaBuffer = fDeltaManager->GetNEvents();
    vector<Int_t> freeDeltaEvents(nDeltaBuffer);
    for (Int_t i = 0; i < nDeltaBuffer; i++)
      freeDeltaEvents[i] = i;

    // --- Loop over delta events
    for (Int_t it = 0; it < fNDeltaElect; it++) {

      // Select random event from vector and remove it after usage
      Int_t indexD          = gRandom->Integer(freeDeltaEvents.size());
      Int_t iEventD         = freeDeltaEvents[indexD];
      TClonesArray* pointsD = fDeltaManager->GetEvent(iEventD);
      freeDeltaEvents.erase(freeDeltaEvents.begin() + indexD);

      // Add points from this event to the input arrays
      for (Int_t iPoint = 0; iPoint < pointsD->GetEntriesFast(); iPoint++) {
        point = (CbmMvdPoint*) pointsD->At(iPoint);
        point->SetTrackID(-3);  // Mark the points as delta electron
        new ((*fInputPoints)[fInputPoints->GetEntriesFast()]) CbmMvdPoint(*((CbmMvdPoint*) pointsD->At(iPoint)));
        nElec++;
      }


    }  // Delta electron event loop

  }  // Usage of delta

  for (Int_t i = 0; i < fInputPoints->GetEntriesFast(); i++)
    new ((*fMcPileUp)[i]) CbmMvdPoint(*((CbmMvdPoint*) fInputPoints->At(i)));


  // ----- At last: Screen output
  LOG(debug) << "+ " << GetName() << "::BuildEvent: original " << nOrig << ", pileup " << nPile << ", delta " << nElec
             << ", total " << nOrig + nPile + nElec << " MvdPoints";
}
// -----------------------------------------------------------------------------


ClassImp(CbmMvdDigitizer);