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

/////////////////////////////////////////////////////////////////////////////
//
// MTaskList
//
// Collection of tasks.
//
// A tasklist is necessary to run the eventloop. It contains the scheduled
// tasks, which should be executed in your program.
//
// To add a task use AddToList.
//
// The tasklist itself is a task, too. You can add a tasklist to another
// tasklist. This makes sense, if you want to filter the execution of
// more than one task of your tasklist using the same filter.
//
// The tasks in the list are idetified by their names. If more than one
// task has the same name, the tasklist will still work correctly, but
// you might run into trouble trying to get a pointer to a task by name
// from the list.
//
// Remark: The Process function is only executed if the class of your task
//         overloads Process() or if the task itself is a MTask. This
//         means if you have a task without Process() (only PreProcess
//         and PostProcess no time is lost during execution)
//
// Warning:
//  Be carefull if you are writing your tasklist
//  (eg. MWriteRootFile("file.root", "MTaskList")) to a file. You may
//  not be able to initialize a new working tasklist from a file if
//   a) Two Paramerer containers with the same names are existing in the
//      MParList.
//   b) You used a container somewhere which is not part of MParList.
//      (eg. You specified a pointer to a MH container in MFillH which is
//      not added to the parameter list.
//
/////////////////////////////////////////////////////////////////////////////
#include "MTaskList.h"

#include <fstream>           // ofstream, SavePrimitive

#include <TSystem.h>         // gSystem
#include <TOrdCollection.h>  // TOrdCollection

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

#include "MIter.h"
#include "MFilter.h"
#include "MParList.h"
#include "MInputStreamID.h"

#include "MStatusDisplay.h"

ClassImp(MTaskList);

using namespace std;

const TString MTaskList::gsDefName  = "MTaskList";
const TString MTaskList::gsDefTitle = "A list for tasks to be executed";

// --------------------------------------------------------------------------
//
// the name for the task list must be the same for all task lists
// because the task list (at the moment) is identified by exactly
// this name in the parameter list (by MEvtLoop::SetParList)
//
MTaskList::MTaskList(const char *name, const char *title)
{
    fName  = name  ? name  : gsDefName.Data();
    fTitle = title ? title : gsDefTitle.Data();

    fTasks = new TList;

    gROOT->GetListOfCleanups()->Add(fTasks);
    gROOT->GetListOfCleanups()->Add(&fTasksProcess);
    fTasks->SetBit(kMustCleanup);
    fTasksProcess.SetBit(kMustCleanup);
}

// --------------------------------------------------------------------------
//
//   CopyConstructor
//   creates a new TaskList and put the contents of an existing
//   TaskList in the new TaskList.
//
MTaskList::MTaskList(MTaskList &ts)
{
    fTasks->AddAll(ts.fTasks);
}

// --------------------------------------------------------------------------
//
//  If the 'IsOwner' bit is set (via SetOwner()) all tasks are deleted
//  by the destructor
//
MTaskList::~MTaskList()
{
    if (TestBit(kIsOwner))
        fTasks->SetOwner();

    delete fTasks;
}

// --------------------------------------------------------------------------
//
//  If the 'IsOwner' bit is set (via SetOwner()) all containers are deleted
//  by the destructor
//
void MTaskList::SetOwner(Bool_t enable)
{
    enable ? SetBit(kIsOwner) : ResetBit(kIsOwner);
}


// --------------------------------------------------------------------------
//
//  Set the logging stream for the all tasks in the list and the tasklist
//  itself.
//
void MTaskList::SetLogStream(MLog *log)
{
    fTasks->ForEach(MTask, SetLogStream)(log);
    MTask::SetLogStream(log);
}

// --------------------------------------------------------------------------
//
//  Set the display for the all tasks in the list and the tasklist itself.
//
void MTaskList::SetDisplay(MStatusDisplay *d)
{
    fTasks->ForEach(MTask, SetDisplay)(d);
    MTask::SetDisplay(d);
}

// --------------------------------------------------------------------------
//
//  Set the serial number for the all tasks in the list and the tasklist
//  itself.
//
void MTaskList::SetSerialNumber(Byte_t num)
{
    fTasks->ForEach(MTask, SetSerialNumber)(num);
    MTask::SetSerialNumber(num);
}

Bool_t MTaskList::CheckAddToList(MTask *task, const char *type, const MTask *where) const
{
    //
    // Sanity check
    //
    if (!task)
    {
        *fLog << err << "ERROR - task argument=NULL." << endl;
        return kFALSE;
    }

    //
    // Get Name of new task
    //
    const char *name = task->GetName();

    //
    // Check if the new task is already existing in the list
    //
    const TObject *objn = fTasks->FindObject(name);
    const TObject *objt = fTasks->FindObject(task);

    if (objn || objt)
    {
        //
        // If the task is already in the list ignore it.
        //
        if (objt || objn==task)
        {
            *fLog << warn << dbginf << "Warning: Task '" << task->GetName() << ", 0x" << (void*)task;
            *fLog << "' already existing in '" << GetName() << "'... ignoring." << endl;
            return kTRUE;
        }

        //
        // Otherwise add it to the list, but print a warning message
        //
        *fLog << warn << dbginf << "Warning: Task '" << task->GetName();
        *fLog << "' already existing in '" << GetName() << "'." << endl;
        *fLog << "You may not be able to get a pointer to this task by name." << endl;
    }

    if (!where)
        return kTRUE;

    if (fTasks->FindObject(where))
        return kTRUE;

    *fLog << err << dbginf << "Error: Cannot find task after which the new task should be scheduled!" << endl;
    return kFALSE;
}


// --------------------------------------------------------------------------
//
// schedule task for execution, before 'where'.
// 'type' is the event type which should be processed
//
Bool_t MTaskList::AddToListBefore(MTask *task, const MTask *where, const char *type)
{
    if (task==this)
    {
        *fLog << warn << "WARNING - You cannot add a tasklist to itself.  This" << endl;
        *fLog << "          would create infinite recursions...ignored." << endl;
        return kFALSE;

    }

    // FIXME: We agreed to put the task into list in an ordered way.
    if (!CheckAddToList(task, type, where))
        return kFALSE;

    *fLog << inf << "Adding " << task->GetName() << " to " << GetName() << " for " << type << "... " << flush;
    task->SetStreamId(type);
    task->SetBit(kMustCleanup);
    fTasks->AddBefore((TObject*)where, task);
    *fLog << "Done." << endl;

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// schedule task for execution, after 'where'.
// 'type' is the event type which should be processed
//
Bool_t MTaskList::AddToListAfter(MTask *task, const MTask *where, const char *type)
{
    if (task==this)
    {
        *fLog << warn << "WARNING - You cannot add a tasklist to itself.  This" << endl;
        *fLog << "          would create infinite recursions...ignored." << endl;
        return kFALSE;

    }

    // FIXME: We agreed to put the task into list in an ordered way.
    if (!CheckAddToList(task, type, where))
        return kFALSE;

    *fLog << inf << "Adding " << task->GetName() << " to " << GetName() << " for " << type << "... " << flush;
    task->SetStreamId(type);
    task->SetBit(kMustCleanup);
    fTasks->AddAfter((TObject*)where, task);
    *fLog << "Done." << endl;

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// schedule task for execution, 'type' is the event type which should
// be processed
//
Bool_t MTaskList::AddToList(MTask *task, const char *type)
{
    if (task==this)
    {
        *fLog << warn << "WARNING - You cannot add a tasklist to itself.  This" << endl;
        *fLog << "          would create infinite recursions...ignored." << endl;
        return kFALSE;

    }

    // FIXME: We agreed to put the task into list in an ordered way.
    if (!CheckAddToList(task, type))
        return kFALSE;

    *fLog << inf << "Adding " << task->GetName() << " to " << GetName() << " for " << type << "... " << flush;
    task->SetStreamId(type);
    task->SetBit(kMustCleanup);
    fTasks->Add(task);
    *fLog << "Done." << endl;

    return kTRUE;
}

// --------------------------------------------------------------------------
//
//  Find an object in the list.
//  'name' is the name of the object you are searching for.
//
TObject *MTaskList::FindObject(const char *name) const
{
    return fTasks->FindObject(name);
}

// --------------------------------------------------------------------------
//
//  check if the object is in the list or not
//
TObject *MTaskList::FindObject(const TObject *obj) const
{
    return fTasks->FindObject(obj);
}

// --------------------------------------------------------------------------
//
// find recursively a tasklist which contains a task with name task
//
MTaskList *MTaskList::FindTaskList(const char *task)
{
    if (FindObject(task))
        return this;

    TIter Next(fTasks);
    TObject *o = 0;
    while ((o=Next()))
    {
        MTaskList *l = dynamic_cast<MTaskList*>(o);
        if (!l)
            continue;

        if (l->FindObject(task))
            return l;
    }
    return 0;
}

// --------------------------------------------------------------------------
//
// find recursively a tasklist which contains a task task
//
MTaskList *MTaskList::FindTaskList(const MTask *task)
{
    if (FindObject(task))
        return this;

    TIter Next(fTasks);
    TObject *o = 0;
    while ((o=Next()))
    {
        MTaskList *l = dynamic_cast<MTaskList*>(o);
        if (!l)
            continue;

        if (l->FindObject(task))
            return l;
    }

    return 0;
}

// --------------------------------------------------------------------------
//
//  removes a task from the list (used in PreProcess).
//  if kIsOwner is set the task is deleted. (see SetOwner())
//
void MTaskList::Remove(MTask *task)
{
    TObject *obj = fTasks->Remove(task);

    if (TestBit(kIsOwner))
        delete obj;
}

// --------------------------------------------------------------------------
//
//  do pre processing (before eventloop) of all tasks in the task-list
//  Only if a task overwrites the Process function the task is
//  added to the fTaskProcess-List. This makes the execution of the
//  tasklist a little bit (only a little bit) faster, bacause tasks
//  doing no Processing are not Processed.
//
Int_t MTaskList::PreProcess(MParList *pList)
{
    *fLog << all << "Preprocessing... " << flush;
    if (fDisplay)
    {
        // Set status lines
        fDisplay->SetStatusLine1("PreProcessing...");
        fDisplay->SetStatusLine2("");
    }

    fParList = pList;

    //
    // Make sure, that the ReadyToSave flag is not reset from a tasklist
    // running as a task in another tasklist.
    //
    const Bool_t noreset = fParList->TestBit(MParList::kDoNotReset);
    if (!noreset)
        fParList->SetBit(MParList::kDoNotReset);

    //
    //  create the Iterator over the tasklist
    //
    TIter Next(fTasks);

    MTask *task=NULL;

    //
    // loop over all tasks for preproccesing
    //
    while ((task=(MTask*)Next()))
    {
        //
        // PreProcess the task and check for it's return value.
        //
        switch (task->CallPreProcess(fParList))
        {
        case kFALSE:
            return kFALSE;

        case kTRUE:
            // Handle GUI events (display changes, mouse clicks)
            if (fDisplay)
                gSystem->ProcessEvents();
            continue;

        case kSKIP:
            Remove(task);
            continue;
        }

        *fLog << err << dbginf << "PreProcess of " << task->GetDescriptor();
        *fLog << " returned an unknown value... aborting." << endl;
        return kFALSE;
    }

    *fLog << all << endl;

    //
    // Reset the ReadyToSave flag.
    //
    if (!noreset)
    {
        fParList->SetReadyToSave(kFALSE);
        fParList->ResetBit(MParList::kDoNotReset);
    }

    //
    // loop over all tasks to fill fTasksProcess
    //
    Next.Reset();
    fTasksProcess.Clear();
    while ((task=(MTask*)Next()))
        if (task->IsA()==MTask::Class() || task->OverwritesProcess())
            fTasksProcess.Add(task);

    return kTRUE;
}

// --------------------------------------------------------------------------
//
//  do reinit of all tasks in the task-list
//
Bool_t MTaskList::ReInit(MParList *pList)
{ 
    *fLog << all << "Reinit... " << flush;

    if (!pList)
        pList = fParList;

    //
    // Make sure, that the ReadyToSave flag is not reset from a tasklist
    // running as a task in another tasklist.
    //
    const Bool_t noreset = pList->TestBit(MParList::kDoNotReset);
    if (!noreset)
        pList->SetBit(MParList::kDoNotReset);

    //
    //  create the Iterator over the tasklist
    //
    TIter Next(fTasks);

    MTask *task=NULL;

    //
    // loop over all tasks for reinitialization
    //
    while ((task=(MTask*)Next()))
    {
        *fLog << all << task->GetName() << "... " << flush;

        if (!task->ReInit(pList/*?pList:fParList*/))
        {
            *fLog << err << "ERROR - ReInit of Task '" << task->GetDescriptor() << "' failed." << endl;
            return kFALSE;
        }
    }

    *fLog << all << endl;

    //
    // Reset the ReadyToSave flag.
    //
    if (!noreset)
    {
        pList->SetReadyToSave(kFALSE);
        pList->ResetBit(MParList::kDoNotReset);
    }

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// do the event execution of all tasks in the task-list
//
Int_t MTaskList::Process()
{
    //
    // Check whether there is something which can be processed, otherwise
    // stop the eventloop.
    //
    if (fTasksProcess.GetSize()==0)
    {
        *fLog << warn << "Warning: No entries in " << GetDescriptor() << " for Processing." << endl;
        return kFALSE;
    }

    //
    // Reset the ReadyToSave flag.
    // Reset all containers.
    //
    // Make sure, that the parameter list is not reset from a tasklist
    // running as a task in another tasklist.
    //
    const Bool_t noreset = fParList->TestBit(MParList::kIsProcessing);
    if (!noreset)
    {
        fParList->SetBit(MParList::kIsProcessing);
        fParList->Reset();
    }

    //
    //  create the Iterator for the TaskList
    //
    TIter Next(&fTasksProcess);
    MTask *task=NULL;

    //
    // loop over all tasks for processing
    //
    Int_t rc = kTRUE;
    while ( (task=(MTask*)Next()) )
    {
        //
        // if the task has the wrong stream id skip it.
        //
        if (GetStreamId() != task->GetStreamId() &&
            task->GetStreamId() !=  "All")
            continue;

        //
        // if it has the right stream id execute the CallProcess() function
        // and check what the result of it is.
        // The CallProcess() function increases the execution counter and
        // calls the Process() function dependent on the existance and
        // return value of a filter.
        //
        switch (task->CallProcess())
        {
        case kTRUE:
            //
            // everything was OK: go on with the next task
            //
            continue;

        case kFALSE:
            //
            // an error occured: stop eventloop
            //
            rc = kFALSE;
            *fLog << inf << task->GetDescriptor() << " has stopped execution of " << GetDescriptor() << "." << endl;
            break;

        case kERROR:
            //
            // an error occured: stop eventloop and return: failed
            //
            *fLog << err << dbginf << "Fatal error occured... stopped." << endl;
            rc = kERROR;
            break;

        case kCONTINUE:
            //
            // something occured: skip the rest of the tasks for this event
            //
            rc = kCONTINUE;
            break;

        default:
            *fLog << warn << dbginf << "Unknown return value from MTask::Process()... ignored." << endl;
            continue;
        }
        break;
    }

    if (!noreset)
    {
        fParList->SetReadyToSave(kFALSE);
        fParList->ResetBit(MParList::kIsProcessing);
    }

    return rc;
}

// --------------------------------------------------------------------------
//
//  do post processing (before eventloop) of all tasks in the task-list
//  only tasks which have successfully been preprocessed are postprocessed.
//
Int_t MTaskList::PostProcess()
{
    *fLog << all << "Postprocessing... " << flush;
    if (fDisplay)
    {
        // Set status lines
        fDisplay->SetStatusLine1("PostProcessing...");
        fDisplay->SetStatusLine2("");
    }

    //
    // Make sure, that the ReadyToSave flag is not reset from a tasklist
    // running as a task in another tasklist.
    //
    const Bool_t noreset = fParList->TestBit(MParList::kDoNotReset);
    if (!noreset)
    {
        fParList->SetBit(MParList::kDoNotReset);
        fParList->Reset();
    }

    //
    //  create the Iterator for the TaskList
    //
    TIter Next(fTasks);

    MTask *task=NULL;

    //
    //  loop over all tasks for postprocessing
    //  only tasks which have successfully been preprocessed are postprocessed.
    //
    while ( (task=(MTask*)Next()) )
    {
        if (!task->CallPostProcess())
            return kFALSE;

        // Handle GUI events (display changes, mouse clicks)
        if (fDisplay)
            gSystem->ProcessEvents();
    }

    *fLog << all << endl;

    //
    // Reset the ReadyToSave flag.
    //
    if (!noreset)
    {
        fParList->SetReadyToSave(kFALSE);
        fParList->ResetBit(MParList::kDoNotReset);
    }

    return kTRUE;
}

// --------------------------------------------------------------------------
//
//  Prints the number of times all the tasks in the list has been.
//  For convinience the lvl argument results in a number of spaces at the
//  beginning of the line. So that the structur of a tasklist can be
//  identified. If a Tasklist or task has filter applied the name of the
//  filter is printer in <>-brackets behind the number of executions.
//  Use MTaskList::PrintStatistics without an argument.
//
void MTaskList::PrintStatistics(const Int_t lvl, Bool_t title, Double_t time) const
{
    if (lvl==0)
    {
        *fLog << all << underline << "Process execution Statistics:" << endl;
        *fLog << GetDescriptor();
        if (GetFilter())
            *fLog << " <" << GetFilter()->GetName() << ">";
        if (title)
            *fLog << "\t" << fTitle;
        if (time>=0)
            *fLog << Form(" %5.1f", GetCpuTime()/time*100) << "%";
        else
            *fLog << " 100.0%";
        *fLog << endl;
    }
    else
        MTask::PrintStatistics(lvl, title, time);

    //
    //  create the Iterator for the TaskList
    //
    fTasks->ForEach(MTask, PrintStatistics)(lvl+1, title, GetCpuTime());

    if (lvl==0)
        *fLog << endl;
}

// --------------------------------------------------------------------------
//
// Call 'Print()' of all tasks
//
void MTaskList::Print(Option_t *t) const
{
    *fLog << all << underline << GetDescriptor() << ":" << endl;

    fTasks->Print();

    *fLog << endl;
}

// --------------------------------------------------------------------------
//
// Implementation of SavePrimitive. Used to write the call to a constructor
// to a macro. In the original root implementation it is used to write
// gui elements to a macro-file.
//
void MTaskList::StreamPrimitive(ofstream &out) const
{
    out << "   MTaskList " << GetUniqueName();
    if (fName!=gsDefName || fTitle!=gsDefTitle)
    {
        out << "(\"" << fName << "\"";
        if (fTitle!=gsDefTitle)
            out << ", \"" << fTitle << "\"";
        out <<")";
    }
    out << ";" << endl << endl;

    MIter Next(fTasks);

    MParContainer *cont = NULL;
    while ((cont=Next()))
    {
        cont->SavePrimitive(out, "");
        out << "   " << GetUniqueName() << ".AddToList(&";
        out << cont->GetUniqueName() << ");" << endl << endl;
    }
}

void MTaskList::GetNames(TObjArray &arr) const
{
    MParContainer::GetNames(arr);
    fTasks->ForEach(MParContainer, GetNames)(arr);
}

void MTaskList::SetNames(TObjArray &arr)
{
    MParContainer::SetNames(arr);
    fTasks->ForEach(MParContainer, SetNames)(arr);
}

// --------------------------------------------------------------------------
//
// Read the contents/setup of a parameter container/task from a TEnv
// instance (steering card/setup file).
// The key to search for in the file should be of the syntax:
//    prefix.vname
// While vname is a name which is specific for a single setup date
// (variable) of this container and prefix is something like:
//    evtloopname.name
// While name is the name of the containers/tasks in the parlist/tasklist
//
// eg.  Job4.MImgCleanStd.CleaningLevel1:  3.0
//      Job4.MImgCleanStd.CleaningLevel2:  2.5
//
// If this cannot be found the next step is to search for
//      MImgCleanStd.CleaningLevel1:  3.0
// And if this doesn't exist, too, we should search for:
//      CleaningLevel1:  3.0
//
// Warning: The programmer is responsible for the names to be unique in
//          all Mars classes.
//
Int_t MTaskList::ReadEnv(const TEnv &env, TString prefix, Bool_t print)
{
    if (print)
        *fLog << all << "MTaskList::ReadEnv: " << prefix << " (" << (int)print << ")" << endl;

    MParContainer *cont = NULL;

    MIter Next(fTasks);
    while ((cont=Next()))
    {
        if (cont->InheritsFrom("MTaskList"))
        {
            if (cont->ReadEnv(env, prefix, print)==kERROR)
                return kERROR;
            continue;
        }

        if (cont->TestEnv(env, prefix, print)==kERROR)
            return kERROR;
    }

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// Write the contents/setup of a parameter container/task to a TEnv
// instance (steering card/setup file).
// The key to search for in the file should be of the syntax:
//    prefix.vname
// While vname is a name which is specific for a single setup date
// (variable) of this container and prefix is something like:
//    evtloopname.name
// While name is the name of the containers/tasks in the parlist/tasklist
//
// eg.  Job4.MImgCleanStd.CleaningLevel1:  3.0
//      Job4.MImgCleanStd.CleaningLevel2:  2.5
//
// If this cannot be found the next step is to search for
//      MImgCleanStd.CleaningLevel1:  3.0
// And if this doesn't exist, too, we should search for:
//      CleaningLevel1:  3.0
//
// Warning: The programmer is responsible for the names to be unique in
//          all Mars classes.
//
Bool_t MTaskList::WriteEnv(TEnv &env, TString prefix, Bool_t print) const
{
    MParContainer *cont = NULL;

    MIter Next(fTasks);
    while ((cont=Next()))
        if (!cont->WriteEnv(env, prefix, print))
            return kFALSE;
    return kTRUE;
}

// --------------------------------------------------------------------------
//
// Removes a task from the tasklist. Returns kFALSE if the object was not
// found in the list.
//
Bool_t MTaskList::RemoveFromList(MTask *task)
{
    TObject *obj = fTasks->Remove(task);

    //
    // If the task was found in the list try to remove it from the second
    // list, too.
    //
    if (obj)
        fTasksProcess.Remove(task);

    return obj ? kTRUE : kFALSE;

}

// --------------------------------------------------------------------------
//
//  Find an object with the same name in the list and replace it with
//  the new one. If the kIsOwner flag is set and the object was not
//  created automatically, the object is deleted.
//
Bool_t MTaskList::Replace(MTask *task)
{
    //
    //  check if the object (you want to add) exists
    //
    if (!task)
        return kFALSE;

    if (task==this)
    {
        *fLog << warn << "WARNING - You cannot add a tasklist to itself.  This" << endl;
        *fLog << "          would create infinite recursions...ignored." << endl;
        return kFALSE;

    }

    MTask *obj = (MTask*)FindObject(task->GetName());
    if (!obj)
    {
        *fLog << warn << "No object with the same name '";
        *fLog << task->GetName() << "' in list... adding." << endl;
        return AddToList(task);
    }

    if (task==obj)
        return kTRUE;

    *fLog << inf << "Adding " << task->GetName() << " to " << GetName() << " for " << obj->GetStreamId() << "... " << flush;
    task->SetStreamId(obj->GetStreamId());
    task->SetBit(kMustCleanup);
    fTasks->AddAfter((TObject*)obj, task);
    *fLog << "Done." << endl;

    RemoveFromList(obj);

    if (TestBit(kIsOwner))
        delete obj;

    //*fLog << inf << "MTask '" << task->GetName() << "' found and replaced..." << endl;

    return kTRUE;
}
