/* ======================================================================== *\
!
! *
! * 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): Thomas Bretz, 1/2004 <mailto:tbretz@astro.uni-wuerzburg.de>
!              Markus Gaug ,04/2004 <mailto:markus@ifae.es>
!
!   Copyright: MAGIC Software Development, 2000-2004
!
!
\* ======================================================================== */

/////////////////////////////////////////////////////////////////////////////
//
//  MJPedestal
//
/////////////////////////////////////////////////////////////////////////////
#include "MJPedestal.h"

#include <TF1.h>
#include <TEnv.h>
#include <TFile.h>
#include <TLine.h>
#include <TLatex.h>
#include <TString.h>
#include <TCanvas.h>
#include <TSystem.h>
#include <TLegend.h>

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

#include "MTaskEnv.h"
#include "MSequence.h"
#include "MRunIter.h"
#include "MParList.h"
#include "MTaskList.h"
#include "MEvtLoop.h"
#include "MExtractor.h"

#include "MStatusDisplay.h"

#include "MGeomCam.h"
#include "MHCamera.h"
#include "MPedestalCam.h"
#include "MBadPixelsCam.h"

#include "MReadMarsFile.h"
#include "MRawFileRead.h"
#include "MGeomApply.h"
#include "MBadPixelsMerge.h"
#include "MPedCalcPedRun.h"
#include "MPedCalcFromLoGain.h"

ClassImp(MJPedestal);

using namespace std;

const Double_t MJPedestal::fgPedestalMin = 4.;
const Double_t MJPedestal::fgPedestalMax = 16.;
const Double_t MJPedestal::fgPedRmsMin   = 0.;
const Double_t MJPedestal::fgPedRmsMax   = 20.;

const Float_t  MJPedestal::fgRefPedClosedLids       = 9.635;
const Float_t  MJPedestal::fgRefPedExtraGalactic    = 9.93;
const Float_t  MJPedestal::fgRefPedGalactic         = 10.03;
const Float_t  MJPedestal::fgRefPedRmsClosedLidsInner    = 1.7;
const Float_t  MJPedestal::fgRefPedRmsExtraGalacticInner = 5.6;
const Float_t  MJPedestal::fgRefPedRmsGalacticInner      = 6.92;
const Float_t  MJPedestal::fgRefPedRmsClosedLidsOuter    = 1.7;
const Float_t  MJPedestal::fgRefPedRmsExtraGalacticOuter = 3.35;
const Float_t  MJPedestal::fgRefPedRmsGalacticOuter      = 4.2;
// --------------------------------------------------------------------------
//
// Default constructor. 
//
// Sets:
// - fRuns to 0, 
// - fExtractor to NULL, 
// - fDataCheck to kFALSE, 
// - fUseData to kFALSE
// - fStorage to Normal Storage
//
MJPedestal::MJPedestal(const char *name, const char *title) 
    : fRuns(0), fExtractor(NULL), fDisplayType(kNormalDisplay),
      fDataCheck(kFALSE), fUseData(kFALSE)
{

  fName  = name  ? name  : "MJPedestal";
  fTitle = title ? title : "Tool to create a pedestal file (MPedestalCam)";
  
  SetNormalStorage();

}

const char* MJPedestal::GetOutputFile() const
{
    if (fSequence.IsValid())
        return Form("%s/pedest%06d.root", (const char*)fPathOut, fSequence.GetSequence());

    if (!fRuns)
        return "";

    return Form("%s/%s-F0.root", (const char*)fPathOut, (const char*)fRuns->GetRunsAsFileName());
}

//---------------------------------------------------------------------------------
//
// Try to read an existing MPedestalCam from a previously created output file. 
// If found, also an MBadPixelsCam and the corresponding display is read.
//
// In case of Storage type "kNoStorage" or if the file is not found or the 
// MPedestalCam cannot be read, return kFALSE, otherwise kTRUE.
//
Bool_t MJPedestal::ReadPedestalCam()
{

  if (IsNoStorage())
    return kFALSE;
  
  const TString fname = GetOutputFile();
  
  if (gSystem->AccessPathName(fname, kFileExists))
    {
      *fLog << warn << "Input file " << fname << " doesn't exist, will create it." << endl;
      return kFALSE;
    }
  
  *fLog << inf << "Reading from file: " << fname << endl;
  
  TFile file(fname, "READ");
  if (fPedestalCam.Read()<=0)
    {
      *fLog << err << "Unable to read MPedestalCam from " << fname << endl;
      return kFALSE;
    }
  
  if (file.FindKey("MBadPixelsCam"))
    {
      MBadPixelsCam bad;
        if (bad.Read()<=0)
          {
            *fLog << err << "Unable to read MBadPixelsCam from " << fname << endl;
            return kFALSE;
          }
        fBadPixels.Merge(bad);
    }

  if (fDisplay && !fDisplay->GetCanvas("Pedestals"))
    fDisplay->Read();
  
  return kTRUE;
}

//---------------------------------------------------------------------------------
//
// Display the results. 
// If Display type "kDataCheck" was chosen, also the reference lines are displayed.
//
void MJPedestal::DisplayResult(MParList &plist)
{
    if (!fDisplay)
        return;

    //
    // Update display
    //
    TString title = fDisplay->GetTitle();
    title += "--  Pedestal ";
    if (fSequence.IsValid())
        title += fSequence.GetName();
    else
        if (fRuns)  // FIXME: What to do if an environmentfile was used?
            title += fRuns->GetRunsAsString();
    title += "  --";
    fDisplay->SetTitle(title);

    //
    // Get container from list
    //
    MGeomCam &geomcam = *(MGeomCam*)plist.FindObject("MGeomCam");

    //
    // Create container to display
    //
    MHCamera disp0(geomcam, "MPedestalCam;ped", "Mean Pedestal");
    MHCamera disp1(geomcam, "MPedestalCam;RMS", "Pedestal RMS");

    disp0.SetCamContent(fPedestalCam, 0);
    disp0.SetCamError  (fPedestalCam, 1);

    disp1.SetCamContent(fPedestalCam, 2);
    disp1.SetCamError  (fPedestalCam, 3);

    disp0.SetYTitle("Pedestal [counts/slice]");
    disp1.SetYTitle("RMS [counts/slice]");

    //
    // Display data
    //
    TCanvas &c3 = fDisplay->AddTab("Pedestals");
    c3.Divide(2,3);

    if (fDisplayType != kDataCheckDisplay)
    {
        disp0.CamDraw(c3, 1, 2, 1);
        disp1.CamDraw(c3, 2, 2, 6);
        return;
    }

    c3.cd(1);
    gPad->SetBorderMode(0);
    gPad->SetTicks();
    MHCamera *obj1=(MHCamera*)disp0.DrawCopy("hist");
    //
    // for the datacheck, fix the ranges!!
    //
    obj1->SetMinimum(fgPedestalMin);
    obj1->SetMaximum(fgPedestalMax);

    //
    // Set the datacheck sizes:
    //
    FixDataCheckHist((TH1D*)obj1);
    //
    // set reference lines
    //
    DisplayReferenceLines(obj1,0);

    //
    // end reference lines
    //
    c3.cd(3);
    gPad->SetBorderMode(0);
    obj1->SetPrettyPalette();
    obj1->Draw();

    c3.cd(5);
    gPad->SetBorderMode(0);
    gPad->SetTicks();
    TH1D *obj2 = (TH1D*)obj1->Projection(obj1->GetName());
    obj2->Draw();
    obj2->SetBit(kCanDelete);
    obj2->Fit("gaus","Q");
    obj2->GetFunction("gaus")->SetLineColor(kYellow);
    //
    // Set the datacheck sizes:
    //
    FixDataCheckHist(obj2);
    obj2->SetStats(1);

    c3.cd(2);
    gPad->SetBorderMode(0);
    gPad->SetTicks();
    MHCamera *obj3=(MHCamera*)disp1.DrawCopy("hist");
    //
    // for the datacheck, fix the ranges!!
    //
    obj3->SetMinimum(fgPedRmsMin);
    obj3->SetMaximum(fgPedRmsMax);
    //
    // Set the datacheck sizes:
    //
    FixDataCheckHist((TH1D*)obj3);
    //
    // set reference lines
    //
    DisplayReferenceLines(obj1,1);

    c3.cd(4);
    gPad->SetBorderMode(0);
    obj3->SetPrettyPalette();
    obj3->Draw();

    c3.cd(6);
    gPad->SetBorderMode(0);

    if (geomcam.InheritsFrom("MGeomCamMagic"))
    {
        TArrayI inner(1);
        inner[0] = 0;

        TArrayI outer(1);
        outer[0] = 1;

        TArrayI s0(6);
        s0[0] = 6;
        s0[1] = 1;
        s0[2] = 2;
        s0[3] = 3;
        s0[4] = 4;
        s0[5] = 5;

        TArrayI s1(3);
        s1[0] = 6;
        s1[1] = 1;
        s1[2] = 2;

        TArrayI s2(3);
        s2[0] = 3;
        s2[1] = 4;
        s2[2] = 5;

        TVirtualPad *pad = gPad;
        pad->Divide(2,1);

        TH1D *inout[2];
        inout[0] = disp1.ProjectionS(s0, inner, "Inner");
        inout[1] = disp1.ProjectionS(s0, outer, "Outer");
        FixDataCheckHist(inout[0]);
        FixDataCheckHist(inout[1]);

        inout[0]->SetTitle(Form("%s %s",disp1.GetTitle(),"Inner"));
        inout[1]->SetTitle(Form("%s %s",disp1.GetTitle(),"Outer"));
        

        for (int i=0; i<2; i++)
        {
            pad->cd(i+1);
            gPad->SetBorderMode(0);
            gPad->SetTicks();

            inout[i]->SetDirectory(NULL);
            inout[i]->SetLineColor(kRed+i);
            inout[i]->SetBit(kCanDelete);
            inout[i]->Draw();
            inout[i]->Fit("gaus", "Q");

            TLegend *leg2 = new TLegend(0.6,0.2,0.9,0.55);
            leg2->SetHeader(inout[i]->GetName());
            leg2->AddEntry(inout[i], inout[i]->GetName(), "l");

            //
            // Display the outliers as dead and noisy pixels
            //
            DisplayOutliers(inout[i]);

            //
            // Display the two half of the camera separately
            //
            TH1D *half[2];
            half[0] = disp1.ProjectionS(s1, i==0 ? inner : outer , "Sector 6-1-2");
            half[1] = disp1.ProjectionS(s2, i==0 ? inner : outer , "Sector 3-4-5");

            for (int j=0; j<2; j++)
            {
                half[j]->SetLineColor(kRed+i+2*j+1);
                half[j]->SetDirectory(NULL);
                half[j]->SetBit(kCanDelete);
                half[j]->Draw("same");
                leg2->AddEntry(half[j], half[j]->GetName(), "l");
            }
            leg2->Draw();
        }
    }
}

void  MJPedestal::DisplayReferenceLines(MHCamera *cam, const Int_t what) const
{

  Double_t x = cam->GetNbinsX();
  
  const MGeomCam *geom = cam->GetGeometry();

  if (geom->InheritsFrom("MGeomCamMagic"))
    x = what ? 397 : cam->GetNbinsX();

  TLine line;
  line.SetLineStyle(kDashed);
  line.SetLineWidth(3);
  
  line.SetLineColor(kBlue);
  TLine *l1 = line.DrawLine(0, what ? fgRefPedRmsGalacticInner : fgRefPedGalactic,
                            x, what ? fgRefPedRmsGalacticInner : fgRefPedGalactic);
  
  line.SetLineColor(kYellow);
  TLine *l2 = line.DrawLine(0, what ? fgRefPedRmsExtraGalacticInner : fgRefPedExtraGalactic,
                            x, what ? fgRefPedRmsExtraGalacticInner : fgRefPedExtraGalactic);
  
  line.SetLineColor(kMagenta);
  TLine *l3 = line.DrawLine(0, what ? fgRefPedRmsClosedLidsInner : fgRefPedClosedLids,
                            x, what ? fgRefPedRmsClosedLidsInner : fgRefPedClosedLids);
  
  if (geom->InheritsFrom("MGeomCamMagic"))
    if (what)
      {
        const Double_t x2 = cam->GetNbinsX();

        line.SetLineColor(kBlue);
        line.DrawLine(398, fgRefPedRmsGalacticOuter,
                      x2,  fgRefPedRmsGalacticOuter);
        
        line.SetLineColor(kYellow);
        line.DrawLine(398, fgRefPedRmsExtraGalacticOuter,
                      x2,  fgRefPedRmsExtraGalacticOuter);
        
        line.SetLineColor(kMagenta);
        line.DrawLine(398, fgRefPedRmsClosedLidsOuter,
                      x2,  fgRefPedRmsClosedLidsOuter);
      }
  
    
  TLegend *leg = new TLegend(0.4,0.75,0.7,0.99);
  leg->SetBit(kCanDelete);
  leg->AddEntry(l1, "Galactic Source","l");
  leg->AddEntry(l2, "Extra-Galactic Source","l");
  leg->AddEntry(l3, "Closed Lids","l");
  leg->Draw();
}

void  MJPedestal::DisplayOutliers(TH1D *hist) const
{
    const Float_t mean  = hist->GetFunction("gaus")->GetParameter(1);
    const Float_t lolim = mean - 3.5*hist->GetFunction("gaus")->GetParameter(2);
    const Float_t uplim = mean + 3.5*hist->GetFunction("gaus")->GetParameter(2);
    const Stat_t  dead  = hist->Integral(0,hist->FindBin(lolim)-1);
    const Stat_t  noisy = hist->Integral(hist->FindBin(uplim)+1,hist->GetNbinsX()+1);

    TLatex deadtex;
    deadtex.SetTextSize(0.06);
    deadtex.DrawLatex(0.1,hist->GetBinContent(hist->GetMaximumBin())/1.1,Form("%3i dead pixels",(Int_t)dead));

    TLatex noisytex;
    noisytex.SetTextSize(0.06);
    noisytex.DrawLatex(0.1,hist->GetBinContent(hist->GetMaximumBin())/1.2,Form("%3i noisy pixels",(Int_t)noisy));
}

void MJPedestal::FixDataCheckHist(TH1D *hist) const 
{

    hist->SetDirectory(NULL);
    hist->SetStats(0);

    //
    // set the labels bigger
    //
    TAxis *xaxe = hist->GetXaxis();
    TAxis *yaxe = hist->GetYaxis();
    
    xaxe->CenterTitle();
    yaxe->CenterTitle();    
    xaxe->SetTitleSize(0.06);
    yaxe->SetTitleSize(0.06);    
    xaxe->SetTitleOffset(0.8);
    yaxe->SetTitleOffset(0.5);    
    xaxe->SetLabelSize(0.05);
    yaxe->SetLabelSize(0.05);    

}
/*
Bool_t MJPedestal::WriteEventloop(MEvtLoop &evtloop) const
{
    if (fOutputPath.IsNull())
        return kTRUE;

    const TString oname(GetOutputFile());

    *fLog << inf << "Writing to file: " << oname << endl;

    TFile file(oname, fOverwrite?"RECREATE":"NEW", "File created by MJPedestal", 9);
    if (!file.IsOpen())
    {
        *fLog << err << "ERROR - Couldn't open file " << oname << " for writing..." << endl;
        return kFALSE;
    }

    if (evtloop.Write(fName)<=0)
    {
        *fLog << err << "Unable to write MEvtloop to " << oname << endl;
        return kFALSE;
    }

    return kTRUE;
}
*/

// --------------------------------------------------------------------------
//
// The following resource options are available:
//   Prefix.DataCheckDisplay: Yes, No
//   Prefix.DataCheck:        Yes, No
//   Prefix.UseData:          Yes, No
//
Bool_t MJPedestal::CheckEnvLocal()
{
    if (HasEnv("DataCheckDisplay"))
        fDisplayType = GetEnv("DataCheckDisplay", kFALSE) ? kDataCheckDisplay : kNormalDisplay;

    SetDataCheck(GetEnv("DataCheck", fDataCheck));
    SetUseData(GetEnv("UseData", fUseData));

    return kTRUE;
}

//---------------------------------------------------------------------------------
//
// Try to write the created MPedestalCam in the output file. 
// If possible, also an MBadPixelsCam and the corresponding display is written.
//
// In case of Storage type "kNoStorage" or if any of the containers
// cannot be written, return kFALSE, otherwise kTRUE.
//
Bool_t MJPedestal::WriteResult()
{
  
  if (IsNoStorage())
    return kTRUE;

  if (fPathOut.IsNull())
    return kTRUE;
  
  const TString oname(GetOutputFile());
  
  *fLog << inf << "Writing to file: " << oname << endl;

  TFile file(oname, "UPDATE", "File created by MJPedestal", 9);
  if (!file.IsOpen())
    {
      *fLog << err << "ERROR - Couldn't open file " << oname << " for writing..." << endl;
      return kFALSE;
    }
  
  if (fDisplay && fDisplay->Write()<=0)
    {
      *fLog << err << "Unable to write MStatusDisplay to " << oname << endl;
      return kFALSE;
    }

  if (fPedestalCam.Write()<=0)
    {
      *fLog << err << "Unable to write MPedestalCam to " << oname << endl;
      return kFALSE;
    }

  if (fBadPixels.Write()<=0)
    {
      *fLog << err << "Unable to write MBadPixelsCam to " << oname << endl;
      return kFALSE;
    }

  return kTRUE;
}

Bool_t MJPedestal::Process()
{
    if (!ReadPedestalCam())
        return ProcessFile();

    return kTRUE;
}

Bool_t MJPedestal::ProcessFile()
{
    if (!fSequence.IsValid())
    {
        if (!fRuns)
        {
            *fLog << err << "Neither AddRuns nor SetSequence nor SetEnv was called... abort." << endl;
            return kFALSE;
        }
        if (fRuns && fRuns->GetNumRuns() != fRuns->GetNumEntries())
        {
            *fLog << err << "Number of files found doesn't match number of runs... abort." << endl;
            return kFALSE;
        }
    }

    //if (!CheckEnv())
    //    return kFALSE;

    CheckEnv();

    // --------------------------------------------------------------------------------

    const TString type = fUseData ? "data" : "pedestal";

    *fLog << inf;
    fLog->Separator(GetDescriptor());
    *fLog << "Calculate MPedestalCam from " << type << "-runs ";
    if (fSequence.IsValid())
        *fLog << fSequence.GetName() << endl;
    else
        if (fRuns)
            *fLog << fRuns->GetRunsAsString() << endl;
        else
            *fLog << "in Resource File." << endl;
    *fLog << endl;

    // --------------------------------------------------------------------------------

    MParList  plist;
    MTaskList tlist;
    plist.AddToList(&tlist);
    plist.AddToList(this); // take care of fDisplay!

    MReadMarsFile read("Events");
    MRawFileRead rawread(NULL);

    MDirIter iter;
    if (fSequence.IsValid())
    {
        const Int_t n0 = fUseData ? fSequence.SetupDatRuns(iter, fPathData, fDataCheck) : fSequence.SetupPedRuns(iter, fPathData, fDataCheck);
        const Int_t n1 = fUseData ? fSequence.GetNumDatRuns() : fSequence.GetNumPedRuns();
        if (n0==0)
        {
            *fLog << err << "ERROR - No " << type << " input files of sequence found in " << fPathData << endl;
            return kFALSE;
        }
        if (n0!=n1)
        {
            *fLog << err << "ERROR - Number of " << type << " files found (" << n0 << ") in " << fPathData << " doesn't match number of files in sequence (" << n1 << ")" << endl;
            return kFALSE;
        }
    }

    if (fDataCheck)
    {
        if (fRuns || fSequence.IsValid())
            rawread.AddFiles(fSequence.IsValid() ? iter : *fRuns);
        tlist.AddToList(&rawread);
    }
    else
    {
        read.DisableAutoScheme();
        if (fRuns || fSequence.IsValid())
            read.AddFiles(fSequence.IsValid() ? iter : *fRuns);
	tlist.AddToList(&read);
    }

    // Setup Tasklist
    plist.AddToList(&fPedestalCam);
    plist.AddToList(&fBadPixels);

    MGeomApply      geomapl;
    MBadPixelsMerge merge(&fBadPixels);

    MPedCalcPedRun     pedcalc;
    MPedCalcFromLoGain pedlogain;
    pedlogain.SetPedestalUpdate(kFALSE);

    MTaskEnv taskenv("ExtractPedestal");
    if (IsUseData())
      taskenv.SetDefault(&pedlogain);
    else
      taskenv.SetDefault(&pedcalc);

    if (fExtractor)
    {
      if (IsUseData())
        pedlogain.SetWindowSize(12,(Int_t)fExtractor->GetNumHiGainSamples());          
      else
        {
          pedcalc.SetWindowSize((Int_t)fExtractor->GetNumHiGainSamples());
          pedcalc.SetRange(fExtractor->GetHiGainFirst(), fExtractor->GetHiGainLast());
        }
    }
  /*
    else
    {
        *fLog << warn << GetDescriptor();
        *fLog << ": No extractor has been chosen, take default number of FADC samples " << endl;
    }
    */

    tlist.AddToList(&geomapl);
    tlist.AddToList(&merge);
    tlist.AddToList(&taskenv);

    //
    // Execute the eventloop
    //
    MEvtLoop evtloop(fName);
    evtloop.SetParList(&plist);
    evtloop.SetDisplay(fDisplay);
    evtloop.SetLogStream(fLog);
    if (!SetupEnv(evtloop))
        return kFALSE;

    //    if (!WriteEventloop(evtloop))
    //        return kFALSE;

    // Execute first analysis
    if (!evtloop.Eventloop(fMaxEvents))
    {
        *fLog << err << GetDescriptor() << ": Failed." << endl;
        return kFALSE;
    }

    tlist.PrintStatistics();

    DisplayResult(plist);

    if (!WriteResult())
        return kFALSE;

    *fLog << all << GetDescriptor() << ": Done." << endl;
    *fLog << endl << endl;

    return kTRUE;
}
