/* ======================================================================== *\
!
! *
! * 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@uni-sw.gwdg.de>
!
!   Copyright: MAGIC Software Development, 2000-2001
!
!
\* ======================================================================== */

/////////////////////////////////////////////////////////////////////////////
//                                                                         //
// 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.                                                          //
//                                                                         //
// 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.h>     // ofstream, SavePrimitive

#include <TClass.h>
#include <TBaseClass.h>
#include <TOrdCollection.h>

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

#include "MParList.h"
#include "MInputStreamID.h"

ClassImp(MTaskList);

// --------------------------------------------------------------------------
//
// 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  : "MTaskList";
    fTitle = title ? title : "A list for tasks to be executed";

    fTasks = new TList;
}

// --------------------------------------------------------------------------
//
//   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);
    MParContainer::SetLogStream(log);
}


// --------------------------------------------------------------------------
//
// schedule task for execution, whether as first task, or after
// 'where'. 'tType' is the event type which should be processed
//
Bool_t MTaskList::AddToList(MTask *task, const char *type, MTask *where)
{
    // FIXME: We agreed to put the task into list in an ordered way.

    //
    // Sanity check
    //
    if (!task)
        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)
    {
        if (!fTasks->FindObject(where))
        {
            *fLog << err << dbginf << "Error: Cannot find task after which the new task should be scheduled!" << endl;
            return kFALSE;
        }
    }

    *fLog << inf << "Adding " << name << " to " << GetName() << " for " << type << "... " << flush;

    task->SetStreamId(type);
    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);
}

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

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

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

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

    *fLog << all << endl;

    return kTRUE;
}

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

// --------------------------------------------------------------------------
//
// Check whether this task (or one of it's base classes) overloads
// MTask::Process. Only if this function is overloaded this 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.
//
Bool_t MTaskList::CheckClassForProcess(TClass *cls)
{
    //
    // Check whether the class itself overloads the Process function
    //
    if (cls->GetName()=="MTask")
        return kFALSE;

    if (cls->GetMethodAny("Process"))
        return kTRUE;

    //
    // If the class itself doesn't overload it check all it's base classes
    //
    TBaseClass *base=NULL;
    TIter NextBase(cls->GetListOfBases());
    while ((base=(TBaseClass*)NextBase()))
    {
        if (CheckClassForProcess(base->GetClassPointer()))
            return kTRUE;
    }

    return kFALSE;
}

// --------------------------------------------------------------------------
//
//  do pre processing (before eventloop) of all tasks in the task-list
//
Bool_t MTaskList::PreProcess(MParList *pList)
{ 
    *fLog << all << "Preprocessing... " << flush;

    fParList = pList;

    fTasksProcess.Clear();

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

    MTask *task=NULL;

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

        if (CheckClassForProcess(task->IsA()))
            fTasksProcess.Add(task);

        //
        // PreProcess the task and check for it's return value.
        //
        switch (task->CallPreProcess(fParList))
        {
        case kFALSE:
            return kFALSE;

        case kTRUE:
            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;

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// do the event execution of all tasks in the task-list
//
Bool_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.
    //
    // FIXME: To run a tasklist as a single task in another tasklist we
    //        have to make sure, that the Parameter list isn't reset.
    //
    fParList->SetReadyToSave(kFALSE);
    fParList->Reset();

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

    //
    // loop over all tasks for processing
    //
    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
            //
            return kFALSE;

        case kCONTINUE:
            //
            // something occured: skip the rest of the tasks for this event
            //
            return kTRUE;

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

    return kTRUE;
}

// --------------------------------------------------------------------------
//
//  do post processing (before eventloop) of all tasks in the task-list
//  only tasks which have successfully been preprocessed are postprocessed.
//
Bool_t MTaskList::PostProcess()
{
    *fLog << all << "Postprocessing... " << flush;

    //
    // Reset the ReadyToSave flag.
    // Reset all containers.
    //
    // FIXME: To run a tasklist as a single task in another tasklist we
    //        have to make sure, that the Parameter list isn't reset.
    //
    fParList->SetReadyToSave(kFALSE);
    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()) )
    {
        *fLog << all << task->GetName() << "... " << flush;

        if (!task->CallPostProcess())
            return kFALSE;
    }

    *fLog << all << endl;

    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. Use MTaskList::PrintStatistics without an argument.
//
void MTaskList::PrintStatistics(const Int_t lvl) const
{
    if (lvl==0)
    {
        *fLog << all << endl;
        *fLog << "Execution Statistics: " << endl;
        *fLog << "---------------------" << endl;
        *fLog << GetDescriptor() << endl;
    }
    else
    {
        *fLog << setw(lvl) << " " << GetDescriptor() << endl;
    }

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

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

// --------------------------------------------------------------------------
void MTaskList::Print(Option_t *t) const
{
    *fLog << all << endl;
    *fLog << GetDescriptor() << endl;
    *fLog << setfill('-') << setw(strlen(GetDescriptor())) << "" << endl;

    fTasks->Print();

    *fLog << endl;
}


void MTaskList::SavePrimitive(ofstream &out, Option_t *o="")
{
    out << "   MTaskList " << ToLower(fName) << ";" << endl << endl;

    TIter Next(fTasks);

    TObject *cont = NULL;
    while ((cont=Next()))
    {
        cont->SavePrimitive(out, "");
        out << "   " << ToLower(fName) << ".AddToList(&";
        out << ToLower(cont->GetName()) << ");" << endl << endl;
    }
}
