#include <TSystem.h>

#include <TFile.h>
#include <TTree.h>

#include "MParList.h"
#include "MTaskList.h"
#include "MEvtLoop.h"

#include "MRawFileRead.h"
#include "MSqlInsertRun.h"
#include "MRawFileWrite.h"
#include "MReportFileReadCC.h"
#include "MWriteRootFile.h"

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

#include "MArgs.h"
#include "MTime.h"
#include "MArray.h"
#include "MRawEvtData.h"
#include "MRawRunHeader.h"
#include "MRawEvtHeader.h"
#include "MRawCrateArray.h"

#include "MFDataPhrase.h"

using namespace std;

//////////////////////////////////////////////////////////////////////////////
//                                                                          //
// This is an easy implementation of the Merging process                    //
// (as compilable prog)                                                     //
//                                                                          //
// at the moment it reads a binary file ("rawtest.bin") which was written   //
// in the DAQ raw format.                                                   //
//                                                                          //
// The data are stored in root container objects (classes derived from      //
// TObject like MRawRunHeader)                                              //
//                                                                          //
// This containers are written to a root file ("rawtest.root")              //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

static void StartUpMessage()
{
    gLog << all << endl;

    //                1         2         3         4         5
    //       12345678901234567890123456789012345678901234567890
    gLog << "==================================================" << endl;
    gLog << "                MERPP - MARS V" << MARSVER          << endl;
    gLog << "     MARS - Merging and Preprocessing Program"      << endl;
    gLog << "   Compiled with ROOT v" << ROOT_RELEASE << " on <" << __DATE__ << ">" << endl;
    gLog << "==================================================" << endl;
    gLog << endl;
}

static void Usage()
{
    //                1         2         3         4         5         6         7         8
    //       12345678901234567890123456789012345678901234567890123456789012345678901234567890
    gLog << all << endl;
    gLog << "Sorry the usage is:" << endl;
    gLog << " merpp [options] inputfile[.rep,[.raw.gz],[.txt]] [outputfile[.root]]" << endl << endl;
    gLog << " Arguments:" << endl;
    gLog << "   inputfile.raw[.gz]        Magic DAQ binary file." << endl;
    gLog << "   inputfile.rep             Magic Central Control report file." << endl;
    gLog << "   inputfile.txt             Magic DC currents file." << endl;
    gLog << "   ouputfile.root            Merpped root file." << endl << endl;
    gLog << " Options:" << endl;
    gLog.Usage();
    gLog << "   --version, -V             Show startup message with version number" << endl;
    gLog << "   -?, -h, --help            This help" << endl << endl;
    gLog << " File Options:" << endl;
    gLog << "   -c#                       Compression level #=1..9 [default=2]" << endl;
    gLog << "   -f                        Force overwrite of an existing file" << endl;
    gLog << "   -u, --update              Update an existing file." << endl;
    gLog << "   --only=Name               Read only reports described by MReportName. See the" << endl;
    gLog << "                                mreport-directory for available classes." << endl << endl;
    gLog << " Raw Data Options:" << endl;
    gLog << "   -ff                       Force merpp to ignore broken events and don't stop" << endl;
    gLog << "   --interleave=#            Process only each i-th event [default=1]" << endl << endl;
//    gLog << "   --sql=mysql://user:password@url  Insert run into database" << endl << endl;
    gLog << " Report File Options:" << endl;
    gLog << "   --auto-time-start         Take time automatically from MRawRunHeader" << endl;
    gLog << "                                (overwrites --start=)" << endl;
    gLog << "   --auto-time-stop          Take time automatically from MRawRunHeader" << endl;
    gLog << "                                (overwrites --stop=)" << endl;
    gLog << "   --auto-time               Abbrev. for --auto-time-start and auto-time-stop" << endl;
    gLog << "   --start=date/time         Start event time" << endl;
    gLog << "   --stop=date/time          Stop  event time" << endl;
    gLog << "   --rep-run=#               Only data corresponding to this run number as" << endl;
    gLog << "                                taken from the RUN-REPORT is extracted" << endl;
    gLog << "   --rep-file=#              Only data corresponding to this file number as" << endl;
    gLog << "                                taken from the RUN-REPORT is extracted" << endl;
    gLog << "   --header-run=#            Allow only run-control .rep-files with this" << endl;
    gLog << "                                run number in there header" << endl;
    gLog << "   --header-file=#           Allow only run-control .rep-files with this" << endl;
    gLog << "                                file number in there header" << endl;
    gLog << "   --telescope=#             Allow only run-control .rep-files with this" << endl;
    gLog << "                                telescope number in there header" << endl;
    gLog << "   --sumfile                 Check for an all night summary file" << endl;
    gLog << "                                (from .rep header)" << endl;
    gLog << "   --allfiles                Don't check file type <default>" << endl << endl;
    gLog << " Compatibility (deprecated):" << endl;
    gLog << "   --run=#                   See --rep-run (overwritten by --rep-run)" << endl;
    gLog << "   --runfile=#               See --header-run (overwritten by --header-run)" << endl << endl;
    gLog << " REMARK: - At the moment you can process a .raw _or_ a .rep file, only!" << endl;
    gLog << "         - 'date/time' has the format 'yyyy-mm-dd/hh:mm:ss.mmm'" << endl << endl;
}

// FIXME: Move to MTime (maybe 'InterpreteCmdline')
MTime AnalyseTime(TString str)
{
    Int_t y=0, ms=0, mon=0, d=0, h=0, m=0, s=0;

    const Int_t n = sscanf(str.Data(), "%d-%d-%d/%d:%d:%d.%d", &y, &mon, &d, &h, &m, &s, &ms);

    if (n<6 || n>7)
    {
        gLog << warn << "'" << str << "' no valid Time... ignored." << endl;
        return MTime();
    }

    MTime t;
    t.Set(y, mon, d, h, m, s, ms);
    return t;
}

void GetTimeFromFile(const char *fname, MTime *start, MTime *stop)
{
    TFile f(fname, "READ");

    TTree *t = (TTree*)f.Get("RunHeaders");
    if (t->GetEntries()!=1)
    {
        gLog << warn << "WARNING - File " << fname << " contains no or more than one entry in RunHeaders... Times unchanged." << endl;
        return;
    }

    MRawRunHeader *h = 0;
    t->SetBranchAddress("MRawRunHeader.", &h);
    t->GetEntry(0);
    if (!h)
    {
        gLog << warn << "WARNING - File " << fname << " did not contain RunHeaders.MRawRunHeader... Times unchanged." << endl;
        return;
    }

    if (start && !*start)
        *start = h->GetRunStart();
    if (stop && !*stop)
        *stop = h->GetRunEnd();
}

void Add(MReportFileReadCC *r, const TString &rep, const TString &only)
{
    if (!only.IsNull() && rep!=only)
        return;

    r->AddToList(Form("MReport%s", rep.Data()));
}

int main(const int argc, char **argv)
{
    if (!MARS::CheckRootVer())
        return 0xff;

    MLog::RedirectErrorHandler(MLog::kColor);

    //
    // Evaluate arguments
    //
    MArgs arg(argc, argv);
    gLog.Setup(arg);

    StartUpMessage();

    if (arg.HasOnly("-V") || arg.HasOnly("--version"))
        return 0;

    if (arg.HasOnly("-?") || arg.HasOnly("-h") || arg.HasOnly("--help"))
    {
        Usage();
        return 2;
    }

    const Int_t   kComprlvl      = arg.GetIntAndRemove("-c", 2);
    const Bool_t  kInterleave    = arg.GetIntAndRemove("--interleave=", 1);
    const Bool_t  kForce         = arg.HasOnlyAndRemove("-f");
    const Bool_t  kForceProc     = arg.HasOnlyAndRemove("-ff");
    const Int_t   run            = arg.GetIntAndRemove("--run=", -1);
    const Int_t   kRunNumber     = arg.GetIntAndRemove("--rep-run=", run);
    const Int_t   kFileNumber    = arg.GetIntAndRemove("--rep-file=", -1);
    const Bool_t  kAutoTime      = arg.HasOnlyAndRemove("--auto-time");
    const Bool_t  kAutoTimeStart = arg.HasOnlyAndRemove("--auto-time-start") || kAutoTime;
    const Bool_t  kAutoTimeStop  = arg.HasOnlyAndRemove("--auto-time-stop")  || kAutoTime;
    const Int_t   runfile        = arg.GetIntAndRemove("--runfile=", -1);
          Int_t   kHeaderRun     = arg.GetIntAndRemove("--header-run=", runfile);
    const Int_t   kHeaderFile    = arg.GetIntAndRemove("--header-file=", -1);
    const Int_t   kTelescope     = arg.GetIntAndRemove("--telescope=", -1);
          Bool_t  kUpdate        = arg.HasOnlyAndRemove("--update") || arg.HasOnlyAndRemove("-u");
    const TString kOnly          = arg.GetStringAndRemove("--only=", "");

    MTime kTimeStart;
    MTime kTimeStop;
    if (arg.HasOption("--star="))
        kTimeStart = AnalyseTime(arg.GetStringAndRemove("--start="));
    if (arg.HasOption("--stop="))
        kTimeStop = AnalyseTime(arg.GetStringAndRemove("--stop="));

//    const TString kSqlDataBase(arg.GetStringAndRemove("--sql="));

    if (arg.HasOnlyAndRemove("--sumfile"))
        kHeaderRun  = 0;

    if (arg.GetNumOptions()>0)
    {
        gLog << warn << "WARNING - Unknown commandline options..." << endl;
        arg.Print("options");
        gLog << endl;
        return 2;
    }

    //
    // check for the right usage of the program
    //
    if (arg.GetNumArguments()<1 || arg.GetNumArguments()>2)
    {
        Usage();
        return 2;
    }

    //
    // This is to make argv[i] more readable insidethe code
    //
    TString kNamein  = arg.GetArgumentStr(0);
    TString kNameout = arg.GetArgumentStr(1);

    const Bool_t isreport = kNamein.EndsWith(".rep");
    const Bool_t isdc     = kNamein.EndsWith(".txt");
    const Bool_t israw    = !isreport && !isdc;

    if (!kNamein.EndsWith(".raw") && !kNamein.EndsWith(".raw.gz") && israw)
        kNamein += ".raw.gz";

    if (kNameout.IsNull())
        kNameout = kNamein(0, kNamein.Last('.'));

    if (!kNameout.EndsWith(".root"))
        kNameout += ".root";

//    if (!kSqlDataBase.IsNull() && !israw)
//        gLog << warn << "WARNING - Option '--sql=' only valid for raw-files... ignored." << endl;

    //
    // Initialize Non-GUI (batch) mode
    //
    gROOT->SetBatch();

    //
    // check whether the given files are OK.
    //
    if (gSystem->AccessPathName(kNamein, kFileExists))
    {
        gLog << err << "Sorry, the input file '" << kNamein << "' doesn't exist." << endl;
        return 2;
    }

    const Bool_t fileexist = !gSystem->AccessPathName(kNameout, kFileExists);
    const Bool_t writeperm = !gSystem->AccessPathName(kNameout, kWritePermission);

    if (fileexist && !writeperm)
    {
        gLog << err << "Sorry, you don't have write permission for '" << kNameout << "'." << endl;
        return 2;
    }

    if (fileexist && !kUpdate && !kForce)
    {
        gLog << err << "Sorry, file '" << kNameout << "' already existing." << endl;
        return 2;
    }

    if (!fileexist && kUpdate)
    {
        gLog << warn << "File '" << kNameout << "' doesn't yet exist." << endl;
        kUpdate=kFALSE;
    }

    //
    // Evaluate possible start-/stop-time
    //
    if ((kAutoTimeStart || kAutoTimeStop) && kUpdate && (isreport || isdc))
        GetTimeFromFile(kNameout, kAutoTimeStart?&kTimeStart:0, kAutoTimeStop?&kTimeStop:0);

    if (kTimeStart)
        gLog << inf << "Start Time: " << kTimeStart << endl;
    if (kTimeStop)
        gLog << inf << "Stop  Time: " << kTimeStop << endl;

    //
    // Ignore TObject Streamer (bits, uniqueid) for MArray and MParContainer
    //
    MArray::Class()->IgnoreTObjectStreamer();
    MParContainer::Class()->IgnoreTObjectStreamer();

    //
    // create a (empty) list of parameters which can be used by the tasks
    // and an (empty) list of tasks which should be executed
    //
    MParList plist;

    MTaskList tasks;
    tasks.SetOwner();
    plist.AddToList(&tasks);

    //
    // ---- The following is only necessary to supress some output ----
    /*
    MRawRunHeader runheader;
    plist.AddToList(&runheader);

    MRawEvtHeader evtheader;
    plist.AddToList(&evtheader);

    MRawEvtData evtdata;
    plist.AddToList(&evtdata);

    MRawCrateArray cratearray;
    plist.AddToList(&cratearray);

    MTime evttime;
    plist.AddToList(&evttime);
    */

    //
    // create the tasks which should be executed and add them to the list
    // in the case you don't need parameter containers, all of them can
    // be created by MRawFileRead::PreProcess
    //
    MTask   *read   = 0;
    MFilter *filter = 0;
    MTask   *write  = 0;

    const TString option(kUpdate ? "UPDATE" : "RECREATE");
    if (isreport || isdc)
    {
        MWriteRootFile *w = new MWriteRootFile(kNameout, option, "Magic root-file", kComprlvl);
        if (isdc)
        {
            w->AddContainer("MTimeCurrents",      "Currents");
            w->AddContainer("MCameraDC",          "Currents");
            w->AddContainer("MReportCurrents",    "Currents");
        }
        else
        {
            const Bool_t required = kOnly.IsNull();
            w->AddContainer("MReportCamera",      "Camera",     required);
            w->AddContainer("MTimeCamera",        "Camera",     required);
            w->AddContainer("MCameraAUX",         "Camera",     required);
            w->AddContainer("MCameraCalibration", "Camera",     required);
            w->AddContainer("MCameraCooling",     "Camera",     required);
            w->AddContainer("MCameraActiveLoad",  "Camera",     required);
            w->AddContainer("MCameraHV",          "Camera",     required);
            w->AddContainer("MCameraLV",          "Camera",     required);
            w->AddContainer("MCameraLids",        "Camera",     required);
            w->AddContainer("MReportTrigger",     "Trigger",    required);
            w->AddContainer("MTimeTrigger",       "Trigger",    required);
            w->AddContainer("MTriggerBit",        "Trigger",    required);
            w->AddContainer("MTriggerIPR",        "Trigger",    required);
            w->AddContainer("MTriggerCell",       "Trigger",    required);
            w->AddContainer("MTriggerPrescFact",  "Trigger",    required);
            w->AddContainer("MTriggerLiveTime",   "Trigger",    required);
            w->AddContainer("MReportDrive",       "Drive",      required);
            w->AddContainer("MTimeDrive",         "Drive",      required);
            w->AddContainer("MCameraTH",          "Rec",        required);
            w->AddContainer("MCameraTD",          "Rec",        required);
            w->AddContainer("MCameraRecTemp",     "Rec",        required);
            w->AddContainer("MReportRec",         "Rec",        required);
            w->AddContainer("MTimeRec",           "Rec",        required);
            w->AddContainer("MReportCC",          "CC",         required);
            w->AddContainer("MTimeCC",            "CC",         required);
            w->AddContainer("MReportStarguider",  "Starguider", required);
            w->AddContainer("MTimeStarguider",    "Starguider", required);
            w->AddContainer("MReportPyrometer",   "Pyrometer",  required);
            w->AddContainer("MTimePyrometer",     "Pyrometer",  required);
            // w->AddContainer("MReportDAQ",         "DAQ");
            // w->AddContainer("MTimeDAQ",           "DAQ");
        }
        write = w;

        MReportFileReadCC *r = new MReportFileReadCC(kNamein);
        r->SetTimeStart(kTimeStart);
        r->SetTimeStop(kTimeStop);
        if (isdc)
        {
            r->SetHasNoHeader();
            r->AddToList("MReportCurrents");
        }
        else
        {
            r->SetTelescope(kTelescope);
            r->SetRunNumber(kHeaderRun);
            r->SetFileNumber(kHeaderFile);
            Add(r, "CC",         kOnly);
            Add(r, "Rec",        kOnly);
            Add(r, "Drive",      kOnly);
            Add(r, "Camera",     kOnly);
            Add(r, "Trigger",    kOnly);
            Add(r, "Starguider", kOnly);
            Add(r, "Pyrometer",  kOnly);
            // Add(r, "DAQ",  kUpdateOnly);
            if (kRunNumber>0)
            {
                const TString f1 = kRunNumber>0  ? Form("MReportRun.fRunNumber==%d",  kRunNumber)  : "";
                const TString f2 = kFileNumber>0 ? Form("MReportRun.fFileNumber==%d", kFileNumber) : "";

                const TString f = Form(kRunNumber>0 && kFileNumber>0 ? "%s && %s" : "%s%s",
                                       f1.Data(), f2.Data());

                r->AddToList("MReportRun");
                filter = new MFDataPhrase(f.Data());
                w->SetFilter(filter);
            }
        }
        read = r;
    }
    else
    {
        read  = new MRawFileRead(kNamein);
        static_cast<MRawFileRead*>(read)->SetInterleave(kInterleave);
        static_cast<MRawFileRead*>(read)->SetForceMode(kForceProc);
        write = new MRawFileWrite(kNameout, option, "Magic root-file", kComprlvl);
    }

    tasks.AddToList(read);
    if (filter)
        tasks.AddToList(filter);
    /*
    if (israw && !kSqlDataBase.IsNull())
    {
        MSqlInsertRun *ins = new MSqlInsertRun(kSqlDataBase);
        ins->SetUpdate();
        tasks.AddToList(ins);
    }*/
    tasks.AddToList(write);

    //
    // create the looping object and tell it about the parameters to use
    // and the tasks to execute
    //
    MEvtLoop magic;
    magic.SetParList(&plist);

    //
    // Start the eventloop which reads the raw file (MRawFileRead) and
    // write all the information into a root file (MRawFileWrite)
    //
    // between reading and writing we can do, transformations, checks, etc.
    // (I'm think of a task like MRawDataCheck)
    //
    if (!magic.Eventloop())
    {
        gLog << err << "ERROR: Merging and preprocessing failed!" << endl;
        return 2;
    }

    tasks.PrintStatistics();

    gLog << all << "Merpp finished successfull!" << endl;
    return 0;
}
