/* ======================================================================== *\
!
! *
! * 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, 11/2003 <mailto:tbretz@astro.uni-wuerzburg.de>
!
!   Copyright: MAGIC Software Development, 2000-2003
!
!
\* ======================================================================== */

//////////////////////////////////////////////////////////////////////////////
//
//  MReportFileRead
//
// Task to read the central control report file. For more information see
// the base class of all reports MReport.
//
// To add a report which should be read use AddToList.
//
// eg. AddToList("Drive") will assume the existance of a class called
//     MReportDrive. It will create such an object automatically. It will
//     send all lines starting with 'MReportDrive::fIndetifier-REPORT'
//     to this class.
//
//////////////////////////////////////////////////////////////////////////////
#include "MReportFileRead.h"

#include <fstream>

#include <TClass.h>
#include <TRegexp.h>
#include <THashTable.h>

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

#include "MReport.h"
#include "MParList.h"

ClassImp(MReportFileRead);

using namespace std;

const TString MReportFileRead::gsReportHeader ="[CC Report File]";
const TString MReportFileRead::gsVersionPrefix="Arehucas Version Number";

class MReportHelp : public TObject
{
private:
    MReport *fReport;
    ULong_t  fNumReports;

public:
    MReportHelp(const char *name, MLog *fLog) : fReport(NULL), fNumReports(0)
    {
        TClass *cls = gROOT->GetClass(name);
        Int_t rc = 0;
        if (!cls)
            rc =1;
        else
        {
            if (!cls->Property())
                rc = 5;
            if (!cls->Size())
                rc = 4;
            if (!cls->IsLoaded())
                rc = 3;
            if (!cls->HasDefaultConstructor())
                rc = 2;
        }

        if (rc)
        {
            *fLog << err << dbginf << "Cannot create new instance of class '" << name << "': ";
            switch (rc)
            {
            case 1:
                *fLog << "gROOT->GetClass() returned NULL." << endl;
                return;
            case 2:
                *fLog << "no default constructor." << endl;
                return;
            case 3:
                *fLog << "not loaded." << endl;
                return;
            case 4:
                *fLog << "zero size." << endl;
                return;
            case 5:
                *fLog << "no property." << endl;
                return;
            }
        }

        //
        // create the parameter container of the the given class type
        //
        fReport = (MReport*)cls->New();
    }
    ~MReportHelp() { if (fReport) delete fReport; }

    const char *GetName() const { return fReport->GetIdentifier(); }
    ULong_t GetNumReports() const { return fNumReports; }
    ULong_t Hash() const { return fReport->GetIdentifier().Hash(); }
    MReport *GetReport() { return fReport; }
    //void SetTime(MTime *t) { fReport->SetTime(t); }
    Int_t Interprete(TString &str, const MTime &start, const MTime &stop)
    {
        const Int_t rc = fReport->Interprete(str, start, stop);

        if (rc==kFALSE)
            return kFALSE;

        fNumReports++;
        return rc;
    }
    Bool_t SetupReading(MParList &plist) { return fReport->SetupReading(plist); }
    void AddToList(MParList &plist) { plist.AddToList(fReport); }
};

// --------------------------------------------------------------------------
//
// Default constructor. It tries to open the given file and creates a
// THashTable which allows faster access to the MReport* objects.
//
MReportFileRead::MReportFileRead(const char *fname, const char *name, const char *title)
    : fFileName(fname), fIn(NULL)
{
    fName  = name  ? name  : "MReportFileRead";
    fTitle = title ? title : "Read task to read Central Control report files";

    fIn = new ifstream;
    fList = new THashTable(1,1);
    fList->SetOwner();
}

// --------------------------------------------------------------------------
//
// Destructor. Delete input stream and hash table.
//
MReportFileRead::~MReportFileRead()
{
    delete fIn;
    delete fList;
}

MReportHelp *MReportFileRead::GetReportHelp(const TString &str) const
{
    return (MReportHelp*)fList->FindObject(str);
}

MReport *MReportFileRead::GetReport(MReportHelp *help) const
{
    return help ? help->GetReport() : 0;
}

MReport *MReportFileRead::GetReport(const TString &str) const
{
    return GetReport(GetReportHelp(str));
}

// --------------------------------------------------------------------------
//
// Add a new MReport* to the list (eg 'Drive' will add MReportDrive)
// For convinience the object is created as a MReportHelp object.
//
Bool_t MReportFileRead::AddToList(const char *name) const
{
    MReportHelp *help = new MReportHelp(name, fLog);

    MReport *rep = NULL;
    if (!(rep=help->GetReport()))
        return kFALSE;

    if (GetReport(rep->GetIdentifier()))
    {
        *fLog << warn << "WARNING - Report with Identifier '";
        *fLog << rep->GetIdentifier() << "' already added to the list... ";
        *fLog << "ignored." << endl;
        delete help;
        return kFALSE;
    }

    fList->Add(help);
    return kTRUE;
}

// --------------------------------------------------------------------------
//
// Check whether the file header corresponds to a central control file
// header and check for the existance of a correct version number.
// The version number may later be used to be able to read different
// file versions
//
Int_t MReportFileRead::CheckFileHeader() const
{
    Int_t line = 0;

    TString str;
    str.ReadLine(*fIn);   // Read to EOF or newline
    if (str != gsReportHeader)
    {
        *fLog << err << "ERROR - First line doesn't match '" << gsReportHeader <<"' ";
        *fLog << "in file '" << fFileName << "'"<<endl;
        return line;
    }
    line++;

    str.ReadLine(*fIn);   // Read to EOF or newline
    if (!str.BeginsWith(gsVersionPrefix))
    {
        *fLog << err << "ERROR - Version prefix '" << gsVersionPrefix <<"' ";
        *fLog << "not found in second line of file '" << fFileName << "'"<<endl;
        return line;
    }
    line++;

    str.Remove(0, gsVersionPrefix.Length());
    str = str.Strip(TString::kBoth);

    TString ver = str(TRegexp("^[0-9][0-9][0-9][0-9][0-9][0-9]-[0-9]$"));
    if (ver.IsNull())
    {
        *fLog << err << "ERROR - Version string '" << str <<"' doesn't ";
        *fLog << "match regular expression." << endl;
        return line;
    }

    *fLog << dbg << "Report File version: <" << ver << ">" << endl;

    return line;
}

// --------------------------------------------------------------------------
//
// Call SetupReading for all MReportHelp objects scheduled.
// Try to open the file and check the file header.
//
Int_t MReportFileRead::PreProcess(MParList *pList)
{
    //MTime *time = (MTime*)pList->FindCreateObj("MTime");
    //if (!time)
    //    return kFALSE;
    fNumLine = 0;

    TIter Next(fList);
    MReportHelp *help=0;
    while ((help=(MReportHelp*)Next()))
        if (!help->SetupReading(*pList))
            return kFALSE;

    fList->ForEach(MReportHelp, AddToList)(*pList);

    //
    // open the input stream
    // first of all check if opening the file in the constructor was
    // successfull
    //
    fIn->open(fFileName);
    if (!(*fIn))
    {
        *fLog << err << "Error: Cannot open file '" << fFileName << "'" << endl;
        return kFALSE;
    }
    if (TestBit(kHasNoHeader))
        return kTRUE;

    const Int_t n = CheckFileHeader();
    fNumLine += n;
    return n==2;
}

// --------------------------------------------------------------------------
//
// Read the file line by line as long as a matching MReport* class is found.
// In this case call its interpreter (Interprete()) and remove the identifier
// first (XYZ-REPORT)
//
Int_t MReportFileRead::Process()
{
    TString str;

    MReportHelp *rep=NULL;
    while (!GetReport(rep))
    {
        str.ReadLine(*fIn);
        if (!*fIn)
        {
            *fLog << dbg << "EOF detected." << endl;
            return kFALSE;
        }

        fNumLine++;

        const Int_t pos = str.First(' ');
        if (pos<=0)
            continue;

        rep = GetReportHelp(str(0,pos));
        if (GetReport(rep))
            str.Remove(0, pos);
    }

    const Int_t rc = rep->Interprete(str, fStart, fStop);
    if (rc==kFALSE)
    {
        *fLog << err << "ERROR - Interpretation of '" << rep->GetName() << "' failed (Line #" << fNumLine << ")" << endl;
        return kFALSE;
    }

    return rc;
}

// --------------------------------------------------------------------------
//
//  Close the file and print some execution statistics
//
Int_t MReportFileRead::PostProcess()
{
    fIn->close();

    if (!GetNumExecutions())
        return kTRUE;

    *fLog << inf << endl;
    *fLog << GetDescriptor() << " statistics:" << endl;
    *fLog << dec << setfill(' ');

    TIter Next(fList);
    MReportHelp *rep=0;

    while ((rep=(MReportHelp*)Next()))
    {
        *fLog << " " << setw(7) << rep->GetNumReports() << " (";
        *fLog << setw(3) << (int)(100.*rep->GetNumReports()/GetNumExecutions());
        *fLog << "%): " << rep->GetName() << endl;
    }

    return kTRUE;
}
