/* ======================================================================== *\
!
! *
! * This file is part of MARS, the MAGIC Analysis and Reconstruction
! * Software. It is distributed to you in the hope that it can be a useful
! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
! * It is distributed WITHOUT ANY WARRANTY.
! *
! * Permission to use, copy, modify and distribute this software and its
! * documentation for any purpose is hereby granted without fee,
! * provided that the above copyright notice appear in all copies and
! * that both that copyright notice and this permission notice appear
! * in supporting documentation. It is provided "as is" without express
! * or implied warranty.
! *
!
!
!   Author(s): Markus Gaug, 12/2004 <mailto:markus@ifae.es>
!         
!   Copyright: MAGIC Software Development, 2000-2004
!
!
\* ======================================================================== */
//////////////////////////////////////////////////////////////////////////////
//
//  MCalibCalcFromPast
//
//  Steers the occurrance of interlaced calibration events in one data run
// 
//  Input Containers:
//   MParList
//   MCalibrationIntensityChargeCam
//   MCalibrationIntensityRelTimeCam
//   MBadPixelsIntensityCam 
//
//  Output Containers:
//   MCalibrationIntensityChargeCam
//   MCalibrationIntensityRelTimeCam
//   MBadPixelsIntensityCam
//
// Class version 2:
//  +  UInt_t  fNumPhesDump;         // Number of cams after which the number of phes gets averaged
//  +  Float_t fMeanPhes;
//  +  Float_t fMeanPhesRelVar;
//  +  Bool_t  fUpdateNumPhes;       // Update the number of photo-electrons only after fNumPhesDump number of Cams
//  +  TArrayF fPhes;
//  +  TArrayF fPhesVar;
//
//////////////////////////////////////////////////////////////////////////////
#include "MCalibCalcFromPast.h"

#include "MLog.h"
#include "MLogManip.h"

#include "MParList.h"

#include "MRawRunHeader.h"

#include "MHCalibrationCam.h"

#include "MCalibrationIntensityChargeCam.h"
#include "MCalibrationIntensityBlindCam.h"
#include "MCalibrationIntensityQECam.h"
//#include "MCalibrationIntensityRelTimeCam.h"

#include "MBadPixelsIntensityCam.h"

#include "MCalibrationChargePix.h"
#include "MCalibrationChargeCalc.h"
#include "MCalibrationRelTimeCalc.h"
#include "MCalibrateData.h"

ClassImp(MCalibCalcFromPast);

using namespace std;

const UInt_t MCalibCalcFromPast::fgNumEventsDump = 500;
const UInt_t MCalibCalcFromPast::fgNumPhesDump   = 10;

// --------------------------------------------------------------------------
//
//  Default constructor. 
//
// Sets:
// - fNumEventsDump to fgNumEventsDump
// - fNumPhesDump to fgNumPhesDump
//
MCalibCalcFromPast::MCalibCalcFromPast(const char *name, const char *title)
    : fGeom(NULL), fParList(NULL), fRunHeader(NULL),
      fIntensCharge(NULL), fIntensBlind(NULL), fIntensRelTime(NULL),
      fIntensBad(NULL),
      fChargeCalc(NULL), fRelTimeCalc(NULL), fCalibrate(NULL),
    fNumCam(0), fNumEvents(0), fUpdateWithFFactorMethod(kTRUE), fUpdateNumPhes(kTRUE),
    fNumFails(0)
{

  fName  = name  ? name  : "MCalibCalcFromPast";
  fTitle = title ? title : "Task to steer the processing of interlaced calibration events";

  SetNumEventsDump();
  SetNumPhesDump  ();
}

// -----------------------------------------------------------------------------------
//
Int_t MCalibCalcFromPast::PreProcess(MParList *pList)
{

  fGeom = (MGeomCam*)pList->FindObject("MGeomCam");
  if (!fGeom)
    {
      *fLog << err << "MGeomCam not found... abort." << endl;
      return kFALSE;
    }

  fParList = pList;
  if (!fParList)
    {
      *fLog << err << "MParList not found... abort." << endl;
      return kFALSE;
    }

  fRunHeader = (MRawRunHeader*)pList->FindObject("MRawRunHeader");
  if (!fRunHeader)
  {
      *fLog << err << "MRawRunHeader not found... abort." << endl;
      return kFALSE;
  }

  // 
  // Look for the MBadPixels Intensity Cam
  //
  fIntensBad = (MBadPixelsIntensityCam*)pList->FindCreateObj("MBadPixelsIntensityCam");
  if (fIntensBad)
    *fLog << inf << "Found MBadPixelsIntensityCam ... " << flush;
  else
    return kFALSE;
  
  // 
  // Look for the MFillH-class "MHCalibrationBlindCam". In case yes, initialize the 
  // corresponding IntensityCam
  //
  if (pList->FindObject(AddSerialNumber("MHCalibrationBlindCam")))
  {

    *fLog << inf << "Found MHCalibrationBlindCam ... " << flush;

    fIntensBlind = (MCalibrationIntensityBlindCam*)pList->FindCreateObj("MCalibrationIntensityBlindCam");
    if (!fIntensBlind)
      {
        *fLog << err << "Could not find nor create MCalibrationIntensitBlindCam abort... " << endl;
        return kFALSE;
      }
  }
  
  // 
  // Look for the MFillH-class "MHCalibrationChargeCam". In case yes, initialize the 
  // corresponding IntensityCam
  //
  if (pList->FindObject(AddSerialNumber("MHCalibrationChargeCam")))
  {

    fIntensCharge = (MCalibrationIntensityChargeCam*)pList->FindCreateObj("MCalibrationIntensityChargeCam");
    fIntensQE     = (MCalibrationIntensityQECam*)    pList->FindCreateObj("MCalibrationIntensityQECam");

    if (!fIntensCharge)
        return kFALSE;
    if (!fIntensQE)
        return kFALSE;

    MCalibrationChargeCam *chargeinit = (MCalibrationChargeCam*)pList->FindObject("MCalibrationChargeCam");
    MCalibrationQECam     *qeinit     = (MCalibrationQECam*)    pList->FindObject("MCalibrationQECam");

    if (chargeinit)
      fIntensCharge->SetCam(chargeinit,0);
    else
      *fLog << "Could not find initial MCalibrationChargeCam, cannot initialize intensity cam" << endl;

    if (qeinit)
      fIntensQE->SetCam(qeinit,0);
    else
      *fLog << "Could not find initial MCalibrationQECam, cannot initialize intensity cam" << endl;

    if (!fChargeCalc)
      fChargeCalc   = (MCalibrationChargeCalc*)pList->FindObject("MCalibrationChargeCalc");
    if (!fChargeCalc)
      {
        *fLog << err << "Could not find MCalibrationChargeCalc abort... " << endl;
        return kFALSE;
      }

    if (!fCalibrate)
      fCalibrate   = (MCalibrateData*)pList->FindObject("MCalibrateData");
    if (!fCalibrate)
      {
        *fLog << err << "Could not find MCalibrateData abort... " << endl;
        return kFALSE;
      }
    
    *fLog << inf << "Found MHCalibrationChargeCam ... " << flush;

  }

  // 
  // Look for the MFillH name "FillRelTimeCam". In case yes, initialize the 
  // corresponding IntensityCam
  //
  if (pList->FindObject(AddSerialNumber("MHCalibrationRelTimeCam")))
  {

    fIntensRelTime = (MCalibrationIntensityRelTimeCam*)pList->FindCreateObj("MCalibrationIntensityRelTimeCam");
    if (!fIntensRelTime)
      {
        *fLog << err << "Could not find nor create MCalibrationIntensityRelTimeCam abort... " << endl;
        return kFALSE;
      }

    if (!fRelTimeCalc)
      fRelTimeCalc   = (MCalibrationRelTimeCalc*)pList->FindObject(AddSerialNumber("MCalibrationRelTimeCalc"));
    if (!fRelTimeCalc)
      {
        *fLog << err << "Could not find MCalibrationRelTimeCalc abort... " << endl;
        return kFALSE;
      }

    *fLog << inf << "Found MHCalibrationRelTimeCam ... " << flush;
  }

  fNumCam    = 0;
  fNumEvents = 0;
  fNumPhes   = 0;

  fChargeCalc->SetUseExternalNumPhes(kFALSE);

  if (fUpdateNumPhes)
    {
      fPhes.Set(fNumPhesDump);
      fPhesVar.Set(fNumPhesDump);
    }

  fNumFails = 0;

  return kTRUE;
}

// --------------------------------------------------------------------------
//
// Set fNumEvents=0
//
// This is necessary because the calibration histograms do reset themself
// if ReInit is called, so they are empty. MCalibCalcFromPast errornously
// ignores how many events are in the histograms but count the number
// itself.
//
Bool_t MCalibCalcFromPast::ReInit(MParList *pList)
{
    if (fNumEvents>0)
        *fLog << inf << fNumEvents << " calibration events at the end of the last file have been skipped." << endl;

    fNumEvents = 0;

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// - Initializes new containers in the 
// - Intensity Cams, if the number of calibration events has reach fNumEventsDump. 
// - Executes Finalize() of the MCalibration*Calc classes in that case.
// - Sets the latest MCalibrationChargeCam as update class into MCalibrateData
// - Initialize new MCalibration*Cams into the intensity cams.
//
Int_t MCalibCalcFromPast::Process()
{
  if (fNumEvents++ < fNumEventsDump)
    return kTRUE;

  fNumEvents = 0;
  ReInitialize();

  *fLog << all << "MCalibCalcFromPast: Calibration Update..." << flush;

  //
  // Finalize Possible calibration histogram classes...
  //
  *fLog << inf << "Finalize calibration histograms:" << endl;

  // This fills the IntensityCam which are newly created by
  // ReInitialize with the result of the last calib cycle
  Finalize("MHCalibrationChargeCam");
  Finalize("MHCalibrationChargeBlindCam");
  Finalize("MHCalibrationRelTimeCam");

  //
  // Finalize possible calibration calculation tasks
  //
  *fLog << endl;
  *fLog << inf << "Finalize calibration calculations..." << endl;
  if (fChargeCalc)
    {
        // Finalized Pedestals, Charges, Bad Pixels and all QE cams
        if (!fChargeCalc->Finalize())
        {
            fNumFails++;
            *fLog << warn << "WARNING - Finalization of charges failed the " << fNumFails << ". time..." << endl;
            return kTRUE;
        }

        if (fUpdateNumPhes)
        {
          MCalibrationChargePix &avpix =(MCalibrationChargePix&)fIntensCharge->GetCam()->GetAverageArea(0);
          fPhes   [fNumPhes] = avpix.GetPheFFactorMethod();
          fPhesVar[fNumPhes] = avpix.GetPheFFactorMethodVar();

          fNumPhes++;

          if (fNumPhes == fNumPhesDump)
            {
              fNumPhes = 0;
              if (!UpdateMeanPhes())
                {
                  *fLog << warn << "Could not update mean number of photo-electrons. "
                        << "Skip it until next update" << endl;
                  fChargeCalc->SetUseExternalNumPhes(kFALSE);
                }
              else
                {
                  *fLog << inf << "New averaged number photo-electrons: " << fMeanPhes << endl;
                  fChargeCalc->SetExternalNumPhes      ( fMeanPhes );
                  fChargeCalc->SetExternalNumPhesRelVar( fMeanPhesRelVar );
                  fChargeCalc->SetUseExternalNumPhes();
                }
            }
        }
    }

  if (fRelTimeCalc)
    fRelTimeCalc->Finalize();

  if (fCalibrate)
    return fCalibrate->UpdateConversionFactors(fUpdateWithFFactorMethod ? NULL
                                               : (MCalibrationChargeCam*)fIntensCharge->GetCam() );

  return kTRUE;
}


// --------------------------------------------------------------------------
//
// Searches for name in the MParList and calls, if existing: 
// - MHCalibrationCam::Finalize()
// - MHCalibrationCam::ResetHists()
//
void MCalibCalcFromPast::Finalize(const char* name, Bool_t finalize)
{
    MHCalibrationCam *hist = (MHCalibrationCam*)fParList->FindObject(name, "MHCalibrationCam");
    if (!hist)
        return;

    *fLog << inf << "Finalize " << name << ":" << endl;

    if (finalize)
        hist->Finalize();

    hist->ResetHists();
}

// --------------------------------------------------------------------------
//
// Re-Intitializes new containers inside the Intensity Cams. 
// From now on, a call to the IntensityCam functions returns pointers 
// to the newly created Containers.
//
Bool_t MCalibCalcFromPast::ReInitialize()
{
  fNumCam++;

  *fLog << inf << "MCalibCalcFromPast::ReInitialize #" << fNumCam << ": ";

  const Int_t runnumber = fRunHeader->GetRunNumber();

  // The "DeleteOldCam" function must not delete the first entry in
  // the array because it is a special cam from the MParList. (see above)

  if (fIntensBad)
  {
      *fLog << "MBadPixelsCam...";
      MBadPixelsCam *cold = fIntensBad->GetCam();
      fIntensBad->AddToList(Form("MBadPixelsCam%04d",fNumCam),*fGeom);
      if (cold)
          fIntensBad->DeleteOldCam(cold);
  }

  if (fIntensCharge)
  {
        *fLog << "MCalibrationChargeCam...";
        MCalibrationChargeCam *cold = (MCalibrationChargeCam*)fIntensCharge->GetCam();

        fIntensCharge->AddToList(Form("MCalibrationChargeCam%04d",fNumCam),*fGeom);

        MCalibrationChargeCam *cnew = (MCalibrationChargeCam*)fIntensCharge->GetCam();
        cnew->SetRunNumber(runnumber);

        if (cold)
        {
            cnew->MergeHiLoConversionFactors(*cold);
            fIntensCharge->DeleteOldCam(cold);
        }
  }

  if (fIntensQE)
  {
      *fLog << "MCalibrationQECam...";
      MCalibrationCam *cold = fIntensQE->GetCam();
      fIntensQE->AddToList(Form("MCalibrationQECam%04d",fNumCam),*fGeom);
      fIntensQE->GetCam()->SetRunNumber(runnumber);
      if (cold)
          fIntensQE->DeleteOldCam(cold);
  }
  if (fIntensBlind)
  {
      *fLog << "MCalibrationBlindCam...";
      MCalibrationCam *cold = fIntensBlind->GetCam();
      fIntensBlind->AddToList(Form("MCalibrationBlindCam%04d",fNumCam),*fGeom);
      fIntensBlind->GetCam()->SetRunNumber(runnumber);
      if (cold)
          fIntensBlind->DeleteOldCam(cold);
  }

  *fLog << endl;

  return kTRUE;

}

Int_t MCalibCalcFromPast::PostProcess()
{
    if (GetNumExecutions()==0)
        return kTRUE;

    // Now we reset all histograms to make sure that the PostProcess
    // of the following tasks doesn't try to finalize a partly empty
    // histogram!
    Finalize("MHCalibrationChargeCam",      kFALSE);
    Finalize("MHCalibrationChargeBlindCam", kFALSE);
    Finalize("MHCalibrationRelTimeCam",     kFALSE);

    if (fChargeCalc)
        fChargeCalc->ResetNumProcessed();

    if (fNumCam==0)
        return kTRUE;

    *fLog << inf << endl;
    *fLog << GetDescriptor() << " execution statistics:" << endl;
    *fLog << " " << setfill(' ') << setw(7) << fNumFails << " (" << Form("%5.1f", 100.*fNumFails/fNumCam) << "%) updates failed." << endl;
    *fLog << endl;

    return kTRUE;
}


Bool_t MCalibCalcFromPast::UpdateMeanPhes()
{
    Float_t sumw = 0.;
    Float_t sum  = 0.;

    for (Int_t i=0; i<fPhes.GetSize(); i++)
    {
        const Float_t weight = 1./fPhesVar[i];
        sum  += fPhes[i]*weight;
        sumw += weight;
    }

    if (sumw < 0.000001)
        return kFALSE;

    if (sum < 0.000001)
        return kFALSE;

    fMeanPhes       = sum/sumw;
    fMeanPhesRelVar = sumw/sum/sum;

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// Read the setup from a TEnv, eg:
//   MCalibCalcFromPast.UpdateWithFFactorMethod: Off, On
//   MCalibCalcFromPast.NumEventsDump: 500
//   MCalibCalcFromPast.UpdateNumPhes: yes/no
//   MCalibCalcFromPast.NumPhesDump: 10
//
Int_t MCalibCalcFromPast::ReadEnv(const TEnv &env, TString prefix, Bool_t print)
{
    Bool_t rc = kFALSE;
    if (IsEnvDefined(env, prefix, "UpdateWithFFactorMethod", print))
    {
        rc = kTRUE;
        SetUpdateWithFFactorMethod(GetEnvValue(env, prefix, "UpdateWithFFactorMethod", fUpdateWithFFactorMethod));
    }

    if (IsEnvDefined(env, prefix, "NumEventsDump", print))
    {
        SetNumEventsDump(GetEnvValue(env, prefix, "NumEventsDump", (Int_t)fNumEventsDump));
        rc = kTRUE;
    }

    if (IsEnvDefined(env, prefix, "UpdateNumPhes", print))
    {
        TString s = GetEnvValue(env, prefix, "UpdateNumPhes", "");
        s.ToLower();
        if (s.BeginsWith("no"))
            SetUpdateNumPhes(kFALSE);
        if (s.BeginsWith("yes"))
            SetUpdateNumPhes(kTRUE);
        rc = kTRUE;
    }

    if (IsEnvDefined(env, prefix, "NumPhesDump", print))
    {
        SetNumPhesDump(GetEnvValue(env, prefix, "NumPhesDump", (Int_t)fNumPhesDump));
        rc = kTRUE;
    }

    return rc;
}
