#include <TSystem.h>

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

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

#include "MReportFileRead.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 "MFDataMember.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 on <" << __DATE__ << ">"       << endl;
    gLog << "               Using ROOT v" << ROOTVER             << endl;
    gLog << "==================================================" << endl;
    gLog << endl;
}

static void Usage()
{
    gLog << all << endl;
    gLog << "Sorry the usage is:" << endl;
    gLog << "   merpp [-h] [-?] [-a] [-vn] [-cn] [-u, -f]" << endl;
    gLog << "         inputfile[.rep,[.raw],[.txt]] [outputfile[.root]]" << endl << endl;
    gLog << "     inputfile.raw:  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;
    gLog << "     -c#                              Compression level #=1..9 [default=2]" << endl;
    gLog << "     -v#                              Verbosity level # [default=2]" << endl;
    gLog << "     -u, --update                     Update file" << endl;
    gLog << "     -a, --no-colors                  Do not use Ansii color codes" << endl;
    gLog << "     -f                               Force overwrite of an existing file" << endl;
    gLog << "     -ff                              Force merpp to ignore broken events <raw data only>" << endl;
    gLog << "     --interleave=#                   Process only each i-th event [default=1]  <raw data only>" << endl;
    gLog << "     --sql=mysql://user:password@url  Insert run into database <raw data only>" << endl;
    gLog << "     --start=yyyy-mm-dd/hh:mm:ss.mmm  Start event time for merpping report files" << endl;
    gLog << "     --stop=yyyy-mm-dd/hh:mm:ss.mmm   Stop  event time for merpping report files" << endl;
    gLog << "     --run=#                          Only data corresponding to this run number" << endl;
    gLog << "     --debug[=n]                      Enable root debugging (Default: gDebug=1)" << endl;
    gLog << "     -?, -h, --help                   This help" << endl << endl;
    gLog << "   REMARK: At the moment you can process a .raw _or_ a .rep file, only!" << 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;
}

int main(const int argc, char **argv)
{
    StartUpMessage();

    //
    // Evaluate arguments
    //
    MArgs arg(argc, argv);

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

    if (arg.HasOnlyAndRemove("--no-colors") || arg.HasOnlyAndRemove("-a"))
        gLog.SetNoColors();

    const Int_t kComprlvl = arg.HasOption("-c") ? arg.GetIntAndRemove("-c") : 2;
    Bool_t kUpdate = arg.HasOnlyAndRemove("--update") || arg.HasOnlyAndRemove("-u");
    const Bool_t kInterleave = arg.HasOption("--interleave=") ? arg.GetIntAndRemove("--interleave=") : 1;
    const Bool_t kForce = arg.HasOnlyAndRemove("-f");
    const Bool_t kForceProc = arg.HasOnlyAndRemove("-ff");

    gLog.SetDebugLevel(arg.HasOption("-v") ? arg.GetIntAndRemove("-v") : 2);

    gDebug = arg.HasOption("--debug=") ? arg.GetIntAndRemove("--debug=") : 0;
    if (gDebug==0 && arg.HasOnlyAndRemove("--debug"))
        gDebug=1;

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

    const Int_t   kRunNumber   = arg.HasOption("--run=") ? arg.GetIntAndRemove("--run=") : -1;
    const TString kSqlDataBase(arg.GetStringAndRemove("--sql="));

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

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

    //
    // 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") && israw)
        kNamein += ".raw";

    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 -1;
    }

    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 -1;
    }

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

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

    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
        {
            w->AddContainer("MReportCamera",      "Camera");
            w->AddContainer("MTimeCamera",        "Camera");
            w->AddContainer("MCameraAUX",         "Camera");
            w->AddContainer("MCameraCalibration", "Camera");
            w->AddContainer("MCameraCooling",     "Camera");
            w->AddContainer("MCameraHV",          "Camera");
            w->AddContainer("MCameraLV",          "Camera");
            w->AddContainer("MCameraLids",        "Camera");
            w->AddContainer("MReportTrigger",     "Trigger");
            w->AddContainer("MTimeTrigger",       "Trigger");
            w->AddContainer("MReportDrive",       "Drive");
            w->AddContainer("MTimeDrive",         "Drive");
            w->AddContainer("MReportCC",          "CC");
            w->AddContainer("MTimeCC",            "CC");
            // w->AddContainer("MReportDAQ",         "DAQ");
            // w->AddContainer("MTimeDAQ",           "DAQ");
        }
        write = w;

        MReportFileRead *r = new MReportFileRead(kNamein);
        r->SetTimeStart(kTimeStart);
        r->SetTimeStop(kTimeStop);
        if (isdc)
        {
            r->SetHasNoHeader();
            r->AddToList("MReportCurrents");
        }
        else
        {
            r->AddToList("MReportCC");
            //r->AddToList("MReportDAQ");
            r->AddToList("MReportDrive");
            r->AddToList("MReportCamera");
            r->AddToList("MReportTrigger");
            if (kRunNumber>0)
            {
                r->AddToList("MReportRun");
                filter = new MFDataMember("MReportRun.fRunNumber", '=', kRunNumber);
                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 -1;
    }

    tasks.PrintStatistics();

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