#include <TROOT.h>
#include <TClass.h>
#include <TSystem.h>
#include <TGClient.h>
#include <TApplication.h>

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

#include "MArgs.h"
#include "MArray.h"
#include "MDirIter.h"

#include "MStatusDisplay.h"

#include "MSequence.h"
#include "MJPedestal.h"
#include "MJCalibration.h"
#include "MJCalibrateSignal.h"

using namespace std;

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

    //                1         2         3         4         5
    //       12345678901234567890123456789012345678901234567890
    gLog << "========================================================" << endl;
    gLog << "                 Callisto - MARS V" << MARSVER            << endl;
    gLog << "    MARS -- CALibrate LIght Signals and Time Offsets"     << endl;
    gLog << "               Compiled on <" << __DATE__ << ">"          << endl;
    gLog << "                  Using ROOT v" << ROOTVER                << 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 << " callisto [-c] [-y] [options] sequence.txt" << endl << endl;
    gLog << " Arguments:" << endl;
    gLog << "   sequence.txt:             An ascii file defining a sequence of runs" << endl;
    gLog << " Root Options:" << endl;
    gLog << "   -b                        Batch mode (no graphical output to screen)" << endl<<endl;
    gLog << " Operation Modes:" << endl;
    gLog << "   -c                        Calculate the calibration constants" << endl;
    gLog << "   -y                        Extract and calibrate signal" << endl << endl;
    gLog << " Input File Options:" << endl;
    gLog << "   -mc                       You must use this for MC files (PRELIMINARY)" << endl << endl;
    gLog << " Options:" << endl;
    gLog.Usage();
    gLog << "   --debug-env               Debug setting resources from file" << endl << endl;
    gLog << endl;
    gLog << "   -q                        Quit when job is finished" << endl;
    gLog << "   -f                        Force overwrite of existing files" << endl;
    gLog << "   -ff                       Force execution if not all files found" << endl;
    gLog << "   --ind=path                Path where to search for the data files" << endl;
    gLog << "                             [default=standard path in datacenter]" << endl;
    gLog << "   --iny=path                Path where to search for the calibration files" << endl;
    gLog << "                             [default=local path or output path of Mode-C]" << endl;
    gLog << "   --outc=path               Path to write Mode-C result to   [def=local path]" << endl;
    gLog << "   --outy=path               Path to write Mode-Y result to   [def=local path]" << endl;
    gLog << "   --out=path                Path to write the all results to [def=local path]" << endl;
    gLog << "                             (overwrites --outc and --outy)" << endl;
    gLog << "   --path=path               Path to write the all results to [def=local path]" << endl;
    gLog << "                             (overwrites --iny, --outc and --outy)" << endl;
    gLog << "   --print-seq               Print Sequence information" << endl;
    gLog << "   --print-files             Print Files taken from Sequence" << endl;
    gLog << "   --print-only              Do not excute anything except print" << endl;
    gLog << "   --config=callisto.rc      Resource file [default=callistop.rc]" << endl;
    gLog << endl;
    gLog << "   -?, -h, --help            This help" << endl << endl;
    gLog << " Setup of the two jobs run by callisto (MJPedestal and MJCalibration)" << endl;
    gLog << " can be done with the resource file. See MJPedestal and MJCalibration" << endl;
    gLog << " especially CheckEnv() for more details.   Command line options might" << endl;
    gLog << " be overwritten by the resource file." << endl << endl;
    gLog << " If running in Mode-C and Mode-Y --iny= is obsolete,  instead --outc=" << endl;
    gLog << " is used." << endl << endl;
    gLog << " Using  --iny=,  --outc=,  --outy=,  --out=  or --path= automatically" << endl;
    gLog << " enables the corresponmding modes.  In this case  -c/-y are obsolete." << endl << endl;
    gLog << "Description:" << endl;
    gLog << " callisto will calculate pedestals  from  pedestal-files defined in a" << endl;
    gLog << " sequence-file.  This pedestals are used to calculate the calibration" << endl;
    gLog << " contants. These constants are stored in a so called calibration-file" << endl;
    gLog << " together with some datacheck plots  which can be viewed using either" << endl;
    gLog << " showplot or MStatusDisplay in the interpreter.  A description  of  a" << endl;
    gLog << " sequence-file can be found in the class reference of MSequence." << endl << endl;
    gLog << "Example:" << endl;
    gLog << " callisto -f --outc=mycal/ --outy=mysignal/ --log sequence02345.txt" << endl;
    gLog << endl;
}

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

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

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

    gLog.Setup(arg);

    const TString kConfig     = arg.GetStringAndRemove("--config=", "callisto.rc");

    const Bool_t  kPrintSeq   = arg.HasOnlyAndRemove("--print-seq");
    const Bool_t  kPrintFiles = arg.HasOnlyAndRemove("--print-files");
    const Bool_t  kPrintOnly  = arg.HasOnlyAndRemove("--print-only");
    const Bool_t  kDebugEnv   = arg.HasOnlyAndRemove("--debug-env");

    const Bool_t  kQuit       = arg.HasOnlyAndRemove("-q");
    const Bool_t  kBatch      = arg.HasOnlyAndRemove("-b");
    const Bool_t  kOverwrite  = arg.HasOnlyAndRemove("-f");
    const Bool_t  kForceExec  = arg.HasOnlyAndRemove("-ff");
    const Bool_t  kIsMC       = arg.HasOnlyAndRemove("-mc");

    const TString kInpathD    = arg.GetStringAndRemove("--ind=",  "");
          TString kInpathY    = arg.GetStringAndRemove("--iny=",  "");
          TString kOutpathY   = arg.GetStringAndRemove("--outy=", "");
          TString kOutpathC   = arg.GetStringAndRemove("--outc=", "");
    const TString kOutpath    = arg.GetStringAndRemove("--out=",  "");
    const TString kPath       = arg.GetStringAndRemove("--path=", "");

          Bool_t  kModeC      = arg.HasOnlyAndRemove("-c");
          Bool_t  kModeY      = arg.HasOnlyAndRemove("-y");

    if (!kInpathY.IsNull() || !kOutpathY.IsNull() || !kOutpath.IsNull() || !kPath.IsNull())
        kModeY = kTRUE;
    if (!kOutpathC.IsNull() || !kOutpath.IsNull() || !kPath.IsNull())
        kModeC = kTRUE;

    if (!kModeC && !kModeY)
    {
        gLog << err << "Neither calibration (-c) nor signal extraction (-y) mode specified!" << endl;
        Usage();
        return 0;
    }

    if (kModeC && kOutpathC.IsNull())
        kOutpathC = ".";
    if (kModeY)
    {
        if (kInpathY.IsNull())
            kInpathY = ".";
        if (kOutpathY.IsNull())
            kOutpathY = ".";
    }
    if (!kOutpath.IsNull())
    {
        kOutpathC = kOutpath;
        kOutpathY = kOutpath;
    }
    if (!kPath.IsNull())
    {
        kOutpathC = kOutpath;
        kOutpathY = kOutpath;
        kInpathY  = kOutpath;
    }

    if (kModeC && kModeY)
        kInpathY = kOutpathC;

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

    //
    // check for the right usage of the program
    //
    if (arg.GetNumArguments()!=1)
    {
        Usage();
        return -1;
    }

    //
    // Setup sequence file and check for its existance
    //
    const TString kSequence = arg.GetArgumentStr(0);

    if (gSystem->AccessPathName(kSequence, kFileExists))
    {
        gLog << err << "Sorry, sequence file '" << kSequence << "' doesn't exist." << endl;
        return -1;
    }

    //
    // Setup sequence and check its validity
    //
    MSequence seq(kSequence);
    if (kPrintSeq)
    {
        gLog << all;
        gLog.Separator(kSequence);
        seq.Print();
        gLog << endl;
    }
    if (!seq.IsValid())
    {
        gLog << err << "Sequence read but not valid!" << endl << endl;
        return -1;
    }

    //
    // Process print options
    //
    if (kPrintFiles)
    {
        MDirIter Next1, Next2, Next3;
        seq.SetupPedRuns(Next1, kInpathD);
        seq.SetupCalRuns(Next2, kInpathD);
        seq.SetupDatRuns(Next3, kInpathD);

        gLog << all;
        gLog.Separator("Pedestal Files");
        Next1.Print("all");
        gLog << endl;
        gLog.Separator("Calibration Files");
        Next2.Print("all");
        gLog << endl;
        gLog.Separator("Data Files");
        Next3.Print("all");
        gLog << endl;
    }

    //
    // Check for existance of all files
    //
    MDirIter iter;
    const Int_t n0 = seq.SetupAllRuns(iter, kInpathD);
    const Int_t n1 = seq.GetNumAllRuns();
    if (n0 != n1)
    {
        if (kForceExec)
            gLog << warn << "WARNING";
        else
            gLog << err << "ERROR";
        gLog << " - " << n1 << " files in sequence defined, but " << n0 << " found in ";
        gLog << (kInpathD.IsNull() ? "<defaultpath>" : kInpathD.Data()) << endl;
        if (!kForceExec)
            return -1;
    }

    if (kPrintOnly)
        return 0;

    //
    // Initialize root
    //
    MArray::Class()->IgnoreTObjectStreamer();
    MParContainer::Class()->IgnoreTObjectStreamer();

    TApplication app("Callisto", &argc, argv);
    if (!gROOT->IsBatch() && !gClient || gROOT->IsBatch() && !kBatch)
    {
        gLog << err << "Bombing... maybe your DISPLAY variable is not set correctly!" << endl;
        return 1;
    }

    //
    // Update frequency by default = 1Hz
    //
    MStatusDisplay *d = new MStatusDisplay;

    // From now on each 'Exit' means: Terminate the application
    d->SetBit(MStatusDisplay::kExitLoopOnExit);
    d->SetTitle(kSequence);

    if (kModeC)
    {
        //
        // Calculate pedestal for calibration
        //
        MJPedestal job1(Form("MJPedestalC #%d", seq.GetSequence()));
        job1.SetSequence(seq);
        job1.SetEnv(kConfig);
        job1.SetEnvDebug(kDebugEnv);
        job1.SetDisplay(d);;
        job1.SetOverwrite(kOverwrite);
        job1.SetPathData(kInpathD);
        // job1.SetPathOut(kOutpathC); // not yet needed
        // job1.SetPathIn(kInpathC);   // not yet needed

        if (!job1.ProcessFile())
        {
            gLog << err << "Calculation of pedestal failed." << endl << endl;
            return -1;
        }

        if (!job1.GetDisplay())
        {
            gLog << warn << "Display closed by user... execution aborted." << endl << endl;
            return 1;
        }

        //
        // Do calibration
        //
        MJCalibration job2(Form("MJCalibration #%d", seq.GetSequence()));
        job2.SetSequence(seq);
        job2.SetEnv(kConfig);
        job2.SetEnvDebug(kDebugEnv);
        job2.SetDisplay(d);;
        job2.SetBadPixels(job1.GetBadPixels());
        job2.SetOverwrite(kOverwrite);
        job2.SetPathOut(kOutpathC);
        job2.SetPathData(kInpathD);
        // job2.SetPathIn(kInpathC); // not yet needed

        if (!job2.ProcessFile(job1.GetPedestalCam()))
        {
            gLog << err << "Calculation of calibration failed." << endl << endl;
            return -1;
        }

        if (!job2.GetDisplay())
        {
            gLog << warn << "Display closed by user... execution aborted." << endl << endl;
            return 1;
        }
    }

    if (kModeY)
    {
        d->Reset();

        //
        // Calculate starting pedestal for data extraction
        //
        MJPedestal job1(Form("MJPedestalY #%d", seq.GetSequence()));
        job1.SetSequence(seq);
        job1.SetEnv(kConfig);
        job1.SetEnvDebug(kDebugEnv);
        job1.SetDisplay(d);;
        //job1.SetPathIn(kInpathY);     // not yet needed
        //job1.SetPathOut(kOutpathY);   // not yet needed
        job1.SetPathData(kInpathD);
        job1.SetOverwrite(kOverwrite);

        if (!job1.ProcessFile())
        {
            gLog << err << "Calculation of pedestal failed." << endl << endl;
            return -1;
        }

        if (!job1.GetDisplay())
        {
            gLog << warn << "Display closed by user... execution aborted." << endl << endl;
            return 1;
        }

        //
        // Extract signal and calibrate it
        //
        MJCalibrateSignal job2(Form("MJCalibrateSignal #%d", seq.GetSequence()));
        job2.SetSequence(seq);
        job2.SetDisplay(d);;
        job2.SetEnv(kConfig);
        job2.SetEnvDebug(kDebugEnv);
        job2.SetOverwrite(kOverwrite);
        job2.SetPathIn(kInpathY);
        job2.SetPathOut(kOutpathY);
        job2.SetPathData(kInpathD);

        // Where to search for calibration files
        if (!job2.ProcessFile(job1.GetPedestalCam(), kIsMC))
            return -1;

        if (!job2.GetDisplay())
        {
            gLog << warn << "Display closed by user... execution aborted." << endl << endl;
            return 1;
        }
    }

    if (kBatch || kQuit)
    {
        delete d;
        return 0;
    }

    // From now on each 'Close' means: Terminate the application
    d->SetBit(MStatusDisplay::kExitLoopOnClose);

    // Wait until the user decides to exit the application
    app.Run(kFALSE);
    return 0;
}
