/* ======================================================================== *\
!
! *
! * This file is part of CheObs, the Modular 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 appears 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/2009 <mailto:tbretz@astro.uni-wuerzburg.de>
!
!   Copyright: CheObs Software Development, 2000-2009
!
!
\* ======================================================================== */

//////////////////////////////////////////////////////////////////////////////
//
//  MSimCamera
//
//  This task initializes the analog channels with analog noise and simulated
//  the analog pulses from the photon signal.
//
//  Input Containers:
//   MPhotonEvent
//   MPhotonStatistics
//   MRawRunHeader
//
//  Output Containers:
//   MAnalogChannels
//
//////////////////////////////////////////////////////////////////////////////
#include "MSimCamera.h"

#include <TF1.h>
#include <TRandom.h>

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

#include "MSpline3.h"
#include "MPulseShape.h"

#include "MParList.h"
//#include "MParameters.h"

#include "MPhotonEvent.h"
#include "MPhotonData.h"

#include "MPedestalCam.h"
#include "MPedestalPix.h"

#include "MAnalogSignal.h"
#include "MAnalogChannels.h"

#include "MMcEvt.hxx"            // To be replaced by a CheObs class
#include "MRawRunHeader.h"

ClassImp(MSimCamera);

using namespace std;

// --------------------------------------------------------------------------
//
//  Default Constructor.
//
MSimCamera::MSimCamera(const char* name, const char *title)
    : fEvt(0), fStat(0), fRunHeader(0), fElectronicNoise(0), fGain(0),
    fCamera(0), fMcEvt(0), fSpline(0), fBaselineGain(kFALSE)
{
    fName  = name  ? name  : "MSimCamera";
    fTitle = title ? title : "Task to simulate the electronic noise and to convert photons into pulses";
}

// --------------------------------------------------------------------------
//
// Search for the necessayr parameter containers.
// Setup spline for pulse shape.
//
Int_t MSimCamera::PreProcess(MParList *pList)
{
    fMcEvt = (MMcEvt*)pList->FindCreateObj("MMcEvt");
    if (!fMcEvt)
        return kFALSE;

    fCamera = (MAnalogChannels*)pList->FindCreateObj("MAnalogChannels");
    if (!fCamera)
        return kFALSE;

    fEvt = (MPhotonEvent*)pList->FindObject("MPhotonEvent");
    if (!fEvt)
    {
        *fLog << err << "MPhotonEvent not found... aborting." << endl;
        return kFALSE;
    }

    fStat = (MPhotonStatistics*)pList->FindObject("MPhotonStatistics");
    if (!fStat)
    {
        *fLog << err << "MPhotonStatistics not found... aborting." << endl;
        return kFALSE;
    }

    fRunHeader = (MRawRunHeader *)pList->FindObject("MRawRunHeader");
    if (!fRunHeader)
    {
        *fLog << err << "MRawRunHeader not found... aborting." << endl;
        return kFALSE;
    }
/*
    fPulsePos = (MParameterD*)pList->FindObject("IntendedPulsePos", "MParameterD");
    if (!fPulsePos)
    {
        *fLog << err << "IntendedPulsePos [MParameterD] not found... aborting." << endl;
        return kFALSE;
    }
 */

    // Create it here to make sure that MGeomApply will set the correct size
    fElectronicNoise = (MPedestalCam*)pList->FindCreateObj("MPedestalCam", "ElectronicNoise");
    if (!fElectronicNoise)
        return kFALSE;

    fGain = (MPedestalCam*)pList->FindCreateObj("MPedestalCam", "Gain");
    if (!fGain)
        return kFALSE;

    MPulseShape *pulse = (MPulseShape*)pList->FindObject("MPulseShape");
    if (!pulse)
    {
        *fLog << err << "MPulseShape not found... aborting." << endl;
        return kFALSE;
    }

    if (fRunHeader->GetFreqSampling()!=1000)
    {
        *fLog << err  << "ERROR - Sampling frequencies others than 1GHz are not yet supported." << endl;
        *fLog << warn << "FIXME - SCALE MPulsShape WITH THE SAMPLING FREQUENCY." << endl;
        return kFALSE;
    }

    fSpline = pulse->GetSpline();
    if (!fSpline)
    {
        *fLog << err << "No spline initialized." << endl;
        return kFALSE;
    }

    // ---------------- Information output ----------------------

    if (fBaselineGain)
        *fLog << inf << "Gain is also applied to the electronic noise." << endl;

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// FIXME: For now this is a workaround to set a baseline and the
// electronic (guassian noise)
//
Bool_t MSimCamera::ReInit(MParList *plist)
{
    for (int i=0; i<fElectronicNoise->GetSize(); i++)
    {
        // 64 -> Dynamic range 12 bit left
        MPedestalPix &ped = (*fElectronicNoise)[i];
        ped.SetPedestal(15*64);     // Baseline at 15 like in MAGIC
        ped.SetPedestalRms(1.5*64); //2.0); // 1.5 bit noise for a gain of 64
        ped.SetPedestalABoffset(0);
        ped.SetNumEvents(0);

        // 256 scale from 8bit to 16bit
        // 8 signal height of one phe
        MPedestalPix &gain = (*fGain)[i];
        gain.SetPedestal(4*64);     // This allows a maximum of 200phe/pix
        gain.SetPedestalRms(0);
        gain.SetPedestalABoffset(0);
        gain.SetNumEvents(0);
    }
    return kTRUE;
}

// --------------------------------------------------------------------------
//
// fStat->GetMaxIndex must return the maximum index possible
// (equiv. number of pixels) not just the maximum index stored!
//
Int_t MSimCamera::Process()
{
    // Calculate start time, end time and corresponding number of samples
    const Double_t freq = fRunHeader->GetFreqSampling()/1000.;

    // FIXME: Should we use a higher sampling here?

    const Double_t start = fStat->GetTimeFirst()*freq;
    const Double_t end   = fStat->GetTimeLast() *freq;

    const UInt_t   nlen  = TMath::CeilNint(end-start);

    // Get number of pixels/channels
    const UInt_t npix = fStat->GetMaxIndex()+1;

    const Double_t pl = fSpline->GetXmin()*freq;
    const Double_t pr = fSpline->GetXmax()*freq;

    // Init the arrays and set the range which will contain valid data
    fCamera->Init(npix, nlen);
    fCamera->SetValidRange(TMath::FloorNint(pr), TMath::CeilNint(nlen+pl));

    // Add electronic noise to empty channels
    for (UInt_t i=0; i<npix; i++)
    {
        const MPedestalPix &pix = (*fElectronicNoise)[i];

        const Double_t val = pix.GetPedestal();
        const Double_t rms = pix.GetPedestalRms();

        if (!fBaselineGain)
        {
            (*fCamera)[i].AddGaussianNoise(rms, val);
            continue;
        }
        // Sorry, the name "pedestal" is misleading here
        // FIXME: Simulate gain fluctuations
        const Double_t gain = (*fGain)[i].GetPedestal();

        // FIXME: We might add the base line here already.
        // FIXME: How stable is the offset?
        // FIXME: Should we write a container AppliedGain for MSImTrigger?
        (*fCamera)[i].AddGaussianNoise(rms*gain, val*gain);
    }

    // FIXME: Simulate correlations with neighboring pixels

    const Int_t num = fEvt->GetNumPhotons();

    // A random shift, uniformely distributed within one slice, to make sure that
    // the first photon is not always aligned identically with a sample edge.
    // FIXME: Make it switchable
    const Float_t rndm = gRandom->Uniform();

    // FIXME: Shell we add a random shift of [0,1] samples per channel?
    //        Or maybe per channel and run?

    Double_t tot = 0;

    // Simulate pulses
    for (Int_t i=0; i<num; i++)
    {
        const MPhotonData &ph = (*fEvt)[i];

        const UInt_t   idx = ph.GetTag();
        const Double_t t   = (ph.GetTime()-fStat->GetTimeFirst())*freq+rndm;// - fSpline->GetXmin();

        // FIXME: Time jitter?
        // FIXME: Add additional routing here?
        // FIMXE: How stable is the gain?

        if (ph.GetPrimary()!=MMcEvt::kNightSky)
            tot += ph.GetWeight();

        // Sorry, the name "pedestal" is misleading here
        // FIXME: Simulate gain fluctuations
        const Double_t gain = (*fGain)[idx].GetPedestal();

        // === FIXME === FIXME === FIXME === Frequency!!!!
        (*fCamera)[idx].AddPulse(*fSpline, t, ph.GetWeight()*gain);
    }

    fMcEvt->SetPhotElfromShower(TMath::Nint(tot));

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// BaselineGain: Off
//
Int_t MSimCamera::ReadEnv(const TEnv &env, TString prefix, Bool_t print)
{
    Bool_t rc = kFALSE;
    if (IsEnvDefined(env, prefix, "BaselineGain", print))
    {
        rc = kTRUE;
        fBaselineGain = GetEnvValue(env, prefix, "BaselineGain", fBaselineGain);
    }

    return rc;
}
