/* ======================================================================== *\
!
! *
! * 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
!
!
\* ======================================================================== */

//////////////////////////////////////////////////////////////////////////////
//
// MParContainer
//
// The MParContainer class is the base class for all MARS parameter
// containers. At the moment it is almost the same than ROOT's TNamed.
// A TNamed contains the essential elements (name, title)
// to identify a derived object in lists like our MParList or MTaskList.
// The main difference is that the name and title isn't stored and read
// to and from root files ("//!")
//
// MParContainer has several enhancements compared to TNamed:
//  - GetDescriptor():        returns name and class type
//  - GetUniqueName():        returns a unique name (used in StreamPrimitive)
//  - SetLogStream(MLog *lg): Set a logging stream to which loggingis stored
//  - Reset():                Reset content of class in an eventloop
//  - IsReadyToSave():        The contents are ready to be saved to a file
//  - IsSavedAsPrimitive():   A unique name for this instance is already
//                            existing
//  - SetVariables():         Can be overloaded if the containers stores
//                            coefficients (to be used in fits)
//  - SetDisplay():           Set a display for redirecting graphical output
//  - GetNames():             Get Name/Title from instance and store it in
//                            a TObjArray (used to store the names of the
//                            conteiners in a file
//  - SetNames():             vice versa
//  - ReadEnv(), WriteEnv():  Function which is used for automatical setup
//    IsEnvDefined()          from a TEnv file
//
//////////////////////////////////////////////////////////////////////////////
#include "MParContainer.h"

#include <ctype.h>        // isdigit
#include <fstream>        // ofstream

#include <TEnv.h>         // Env::Lookup
#include <TClass.h>       // IsA
#include <TObjArray.h>    // TObjArray
#include <TBaseClass.h>   // GetClassPointer
#include <TMethodCall.h>  // TMethodCall, AsciiWrite
#include <TDataMember.h>  // TDataMember, AsciiWrite
#include <TVirtualPad.h>  // gPad

#include "MString.h"

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

TList *gListOfPrimitives; // forard declaration in MParContainer.h

#undef DEBUG
//#define DEBUG

ClassImp(MParContainer);

using namespace std;

MParContainer::MParContainer(const char *name, const char *title) :
    fName(name), fTitle(title), fLog(&gLog), fDisplay(NULL), fReadyToSave(kFALSE)
{
}

MParContainer::MParContainer(const TString &name, const TString &title) :
    fName(name), fTitle(title), fLog(&gLog), fDisplay(NULL), fReadyToSave(kFALSE)
{
}

// --------------------------------------------------------------------------
//
//  MParContainer copy ctor
//
MParContainer::MParContainer(const MParContainer &named)
{
    fName  = named.fName;
    fTitle = named.fTitle;

    fLog = named.fLog;

    fReadyToSave = named.fReadyToSave;

    fDisplay = named.fDisplay;
}

MParContainer::~MParContainer()
{
#ifdef DEBUG
    *fLog << all << "Deleting " << GetDescriptor() << endl;
#endif
}

// --------------------------------------------------------------------------
//
//  MParContainer assignment operator.
//
MParContainer& MParContainer::operator=(const MParContainer& rhs)
{
    if (this == &rhs)
        return *this;

    TObject::operator=(rhs);

    fName  = rhs.fName;
    fTitle = rhs.fTitle;

    fLog = rhs.fLog;
    fReadyToSave = rhs.fReadyToSave;

    return *this;
}

// --------------------------------------------------------------------------
//
// Make a clone of an object using the Streamer facility.
// If newname is specified, this will be the name of the new object
//
TObject *MParContainer::Clone(const char *newname) const
{

   MParContainer *named = (MParContainer*)TObject::Clone();
   if (newname && strlen(newname)) named->SetName(newname);
   return named;
}

// --------------------------------------------------------------------------
//
//  Compare two MParContainer objects. Returns 0 when equal, -1 when this is
//  smaller and +1 when bigger (like strcmp).
//
Int_t MParContainer::Compare(const TObject *obj) const
{
    if (this == obj) return 0;
    return fName.CompareTo(obj->GetName());
}

// --------------------------------------------------------------------------
//
//  Copy this to obj.
//
void MParContainer::Copy(TObject &obj)
#if ROOT_VERSION_CODE > ROOT_VERSION(3,04,01)
const
#endif
{
    MParContainer &cont = (MParContainer&)obj;

    TObject::Copy(obj);

    cont.fName  = fName;
    cont.fTitle = fTitle;

    cont.fLog = fLog;
    cont.fReadyToSave = fReadyToSave;
}

// --------------------------------------------------------------------------
//
//  Encode MParContainer into output buffer.
//
void MParContainer::FillBuffer(char *&buffer)
{
    fName.FillBuffer(buffer);
    fTitle.FillBuffer(buffer);
}

// --------------------------------------------------------------------------
//
// Returns the name of the object. If the name of the object is not the
// class name it returns the object name and in []-brackets the class name.
//
const char *MParContainer::GetDescriptor() const
{
    //
    // Because it returns a (const char*) we cannot return a casted
    // local TString. The pointer would - immediatly after return -
    // point to a random memory segment, because the TString has gone.
    //
    MString desc;
    desc.Print("%s [%s]", fName.Data(), ClassName());
    return fName==ClassName() ? ClassName() : desc.Data();
}

// --------------------------------------------------------------------------
//
//  Return a unique name for this container. It is created from
//  the container name and the unique Id. (This is mostly used
//  in the StreamPrimitive member functions)
//
const TString MParContainer::GetUniqueName() const
{
    TString ret = ToLower(fName);

    if (isdigit(ret[ret.Length()-1]))
        ret+="_";

    ret+=GetUniqueID();

    return ret;
}

// --------------------------------------------------------------------------
//
//  List MParContainer name and title.
//
void MParContainer::ls(Option_t *) const
{
    TROOT::IndentLevel();
    *fLog << all << GetDescriptor() << " " << GetTitle() << ": kCanDelete=";
    *fLog << Int_t(TestBit(kCanDelete)) << endl;
}

// --------------------------------------------------------------------------
//
//  Print MParContainer name and title.
//
void MParContainer::Print(Option_t *) const
{
    *fLog << all << GetDescriptor() << " " << GetTitle() << endl;
}

// --------------------------------------------------------------------------
//
//  Change (i.e. set) the name of the MParContainer.
//  WARNING !!
//  If the object is a member of a THashTable, THashList container
//  The HashTable must be Rehashed after SetName
//  For example the list of objects in the current directory is a THashList
//
void MParContainer::SetName(const char *name)
{
    fName = name;
    ResetBit(kIsSavedAsPrimitive);
    if (gPad && TestBit(kMustCleanup)) gPad->Modified();
}

// --------------------------------------------------------------------------
//
//  Change (i.e. set) all the MParContainer parameters (name and title).
//  See also WARNING in SetName
//
void MParContainer::SetObject(const char *name, const char *title)
{
    fName  = name;
    fTitle = title;
    ResetBit(kIsSavedAsPrimitive);
    if (gPad && TestBit(kMustCleanup)) gPad->Modified();
}

// --------------------------------------------------------------------------
//
//  Change (i.e. set) the title of the MParContainer.
//
void MParContainer::SetTitle(const char *title)
{
    fTitle = title;
    ResetBit(kIsSavedAsPrimitive);
    if (gPad && TestBit(kMustCleanup)) gPad->Modified();
}

// --------------------------------------------------------------------------
//
//  Return size of the MParContainer part of the TObject.
//
Int_t MParContainer::Sizeof() const
{
    Int_t nbytes = fName.Sizeof() + fTitle.Sizeof();
    return nbytes;
}

// --------------------------------------------------------------------------
//
//  If you want to use Ascii-Input/-Output (eg. MWriteAsciiFile) of a
//  container, overload this function.
//
void MParContainer::AsciiRead(istream &fin)
{
    *fLog << warn << "To use the the ascii input of " << GetName();
    *fLog << " you have to overload " << ClassName() << "::AsciiRead." << endl;
}

// --------------------------------------------------------------------------
//
//  Write out a data member given as a TDataMember object to an output stream.
//
Bool_t MParContainer::WriteDataMember(ostream &out, const TDataMember *member, Double_t scale) const
{
    if (!member)
        return kFALSE;

    if (!member->IsPersistent() || member->Property()&kIsStatic)
        return kFALSE;

    /*const*/ TMethodCall *call = ((TDataMember*)member)->GetterMethod(); //FIXME: Root
    if (!call)
    {
        *fLog << warn << "Sorry, no getter method found for " << member->GetName() << endl;
        return kFALSE;
    }

    // For debugging: out << member->GetName() << ":";

    switch (call->ReturnType())
    {
    case TMethodCall::kLong:
        Long_t l;
        call->Execute((void*)this, l); // FIXME: const, root
        out << l << " ";
        return kTRUE;

    case TMethodCall::kDouble:
        Double_t d;
        call->Execute((void*)this, d); // FIXME: const, root
        out << (scale*d) << " ";
        return kTRUE;

    default:
    //case TMethodCall::kString:
    //case TMethodCall::kOther:
        /* someone may want to enhance this? */
        return kFALSE;
    }
}

// --------------------------------------------------------------------------
//
//  Write out a data member given by name to an output stream.
//
Bool_t MParContainer::WriteDataMember(ostream &out, const char *member, Double_t scale) const
{
    /*const*/ TClass *cls = IsA()->GetBaseDataMember(member);
    if (!cls)
        return kFALSE;

    return WriteDataMember(out, cls->GetDataMember(member), scale);
}

// --------------------------------------------------------------------------
//
//  Write out a data member from a given TList of TDataMembers.
//  returns kTRUE when at least one member was successfully written
//
Bool_t MParContainer::WriteDataMember(ostream &out, const TList *list) const
{
    Bool_t rc = kFALSE;

    TDataMember *data = NULL;

    TIter Next(list);
    while ((data=(TDataMember*)Next()))
        rc |= WriteDataMember(out, data);

    return rc;
}

// --------------------------------------------------------------------------
//
//  If you want to use Ascii-Input/-Output (eg. MWriteAsciiFile) of a
//  container, you may overload this function. If you don't overload it
//  the data member of a class are written to the file in the order of
//  appearance in the class header (be more specfic: root dictionary)
//  Only data members which are of integer (Bool_t, Int_t, ...) or
//  floating point (Float_t, Double_t, ...) type are written.
//  returns kTRUE when at least one member was successfully written
//
Bool_t MParContainer::AsciiWrite(ostream &out) const
{
    // *fLog << warn << "To use the the ascii output of " << GetName();
    // *fLog << " you have to overload " << ClassName() << "::AsciiWrite." << endl;

    Bool_t rc = WriteDataMember(out, IsA()->GetListOfDataMembers());

    TIter NextBaseClass(IsA()->GetListOfBases());
    TBaseClass *base;
    while ((base = (TBaseClass*) NextBaseClass()))
    {
        /*const*/ TClass *cls = base->GetClassPointer();

        if (!cls)
            continue;

        if (cls->GetClassVersion())
            rc |= WriteDataMember(out, cls->GetListOfDataMembers());
    }

    return rc;
}

TMethodCall *MParContainer::GetterMethod(const char *name) const
{
    const TString n(name);
    const Int_t pos1 = n.First('.');

    const TString part1 = pos1<0 ? n : n(0, pos1);
    const TString part2 = pos1<0 ? TString("") : n(pos1+1, n.Length());

    TClass *cls = IsA()->GetBaseDataMember(part1);
    if (cls)
    {
        TDataMember *member = cls->GetDataMember(part1);
        if (!member)
        {
            *fLog << err << "Datamember '" << part1 << "' not in " << GetDescriptor() << endl;
            return NULL;
        }

        // This handles returning references of contained objects, eg
        // class X { TObject fO; TObject &GetO() { return fO; } };
        if (!member->IsBasic() && !part2.IsNull())
        {
            cls = gROOT->GetClass(member->GetTypeName());
            if (!cls)
            {
                *fLog << err << "Datamember " << part1 << " [";
                *fLog << member->GetTypeName() << "] not in dictionary." << endl;
                return NULL;
            }
            if (!cls->InheritsFrom(MParContainer::Class()))
            {
                *fLog << err << "Datamember " << part1 << " [";
                *fLog << member->GetTypeName() << "] does not inherit from ";
                *fLog << "MParContainer." << endl;
                return NULL;
            }

            const MParContainer *sub = (MParContainer*)((ULong_t)this+member->GetOffset());
            return sub->GetterMethod(part2);
        }

        if (member->IsaPointer())
        {
            *fLog << warn << "Data-member " << part1 << " is a pointer..." << endl;
            *fLog << dbginf << "Not yet implemented!" << endl;
            //TClass *test = gROOT->GetClass(member->GetTypeName());
            return 0;
        }

        TMethodCall *call = member->GetterMethod();
        if (call)
            return call;
    }

    *fLog << warn << "No standard access for '" << part1 << "' in ";
    *fLog << GetDescriptor() << " or one of its base classes." << endl;

    TMethodCall *call = NULL;

    *fLog << warn << "Trying to find MethodCall '" << ClassName();
    *fLog << "::Get" << part1 << "' instead <LEAKS MEMORY>" << endl;
    call = new TMethodCall(IsA(), (TString)"Get"+part1, "");
    if (call->GetMethod())
        return call;

    delete call;

    *fLog << warn << "Trying to find MethodCall '" << ClassName();
    *fLog << "::" << part1 << "' instead <LEAKS MEMORY>" << endl;
    call = new TMethodCall(IsA(), part1, "");
    if (call->GetMethod())
        return call;

    delete call;

    *fLog << err << "Sorry, no getter method found for " << part1 << endl;
    return NULL;
}

// --------------------------------------------------------------------------
//
// 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 MParContainer::SavePrimitive(ofstream &out, Option_t *o)
{
    static UInt_t uid = 0;

    if (IsSavedAsPrimitive())
        return;

    SetUniqueID(uid++);
    SetBit(kIsSavedAsPrimitive);

    if (gListOfPrimitives && !gListOfPrimitives->FindObject(this))
        gListOfPrimitives->Add(this);

    StreamPrimitive(out);
}

// --------------------------------------------------------------------------
//
// Creates the string written by SavePrimitive and returns it.
//
void MParContainer::StreamPrimitive(ofstream &out) const
{
    out << "   // Using MParContainer::StreamPrimitive" << endl;
    out << "   " << ClassName() << " " << GetUniqueName() << "(\"";
    out << fName << "\", \"" << fTitle << "\");" << endl;
}

void MParContainer::GetNames(TObjArray &arr) const
{
    arr.AddLast(new TNamed(fName, fTitle));
}

void MParContainer::SetNames(TObjArray &arr)
{
    TNamed *name = (TNamed*)arr.First();

    fName  = name->GetName();
    fTitle = name->GetTitle();

    delete arr.Remove(name);
    arr.Compress();
}

// --------------------------------------------------------------------------
//
// Creates a new instance of this class. The idea is to create a clone of
// this class in its initial state.
//
MParContainer *MParContainer::New() const
{
    return (MParContainer*)IsA()->New();
}

// --------------------------------------------------------------------------
//
// 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.
//
// Return values:
//  kTRUE:  Environment string found
//  kFALSE: Environment string not found
//  kERROR: Error occured, eg. environment invalid
//
// Overload this if you don't want to control the level of setup-string. In
// this case ReadEnv gets called with the different possibilities, see TestEnv.
//
Int_t MParContainer::ReadEnv(const TEnv &env, TString prefix, Bool_t print)
{
    if (!IsEnvDefined(env, prefix, "", print))
        return kFALSE;

    *fLog << warn << "WARNING - Resource " << prefix+fName << " found, but no " << ClassName() << "::ReadEnv." << endl;
    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 MParContainer::WriteEnv(TEnv &env, TString prefix, Bool_t print) const
{
    if (!IsEnvDefined(env, prefix, "", print))
        return kFALSE;

    *fLog << warn << "WARNING - Resource " << prefix+fName << " found, but " << ClassName() << "::WriteEnv not overloaded." << endl;
    return kTRUE;
}

// --------------------------------------------------------------------------
//
// Take the prefix and call ReadEnv for:
//   prefix.containername
//   prefix.classname
//   containername
//   classname
//
//  The existance of an environment variable is done in this order. If
//  ReadEnv return kTRUE the existance of the container setup is assumed and
//  the other tests are skipped. If kFALSE is assumed the sequence is
//  continued. In case of kERROR failing of the setup from a file is assumed.
//
// Overload this if you want to control the handling of level of setup-string
// mentioned above. In this case ReadEnv gets never called if you don't call
// it explicitly.
//
Int_t MParContainer::TestEnv(const TEnv &env, TString prefix, Bool_t print)
{
    if (print)
        *fLog << all << "Testing Prefix+ContName: " << prefix+GetName() << endl;
    Int_t rc = ReadEnv(env, prefix+GetName(), print);
    if (rc==kERROR || rc==kTRUE)
        return rc;

    // Check For: Job4.MClassName.Varname
    if (print)
        *fLog << all << "Testing Prefix+ClasName: " << prefix+ClassName() << endl;
    rc = ReadEnv(env, prefix+ClassName(), print);
    if (rc==kERROR || rc==kTRUE)
        return rc;

    // Check For: ContainerName.Varname
    if (print)
        *fLog << all << "Testing ContName: " << GetName() << endl;
    rc = ReadEnv(env, GetName(), print);
    if (rc==kERROR || rc==kTRUE)
        return rc;

    // Check For: MClassName.Varname
    if (print)
        *fLog << all << "Testing ClassName: " << ClassName() << endl;
    rc = ReadEnv(env, ClassName(), print);
    if (rc==kERROR || rc==kTRUE)
        return rc;

    // Not found
    return kFALSE;
}

Bool_t MParContainer::IsEnvDefined(const TEnv &env, TString prefix, TString postfix, Bool_t print) const
{
    if (!postfix.IsNull())
        postfix.Insert(0, ".");

    return IsEnvDefined(env, prefix+postfix, print);
}

Bool_t MParContainer::IsEnvDefined(const TEnv &env, TString name, Bool_t print) const
{
    if (print)
        *fLog << all << GetDescriptor() << " - " << name << "... " << flush;

    if (!((TEnv&)env).Defined(name))
    {
        if (print)
            *fLog << "not found." << endl;
        return kFALSE;
    }

    if (print)
        *fLog << "found." << endl;

    return kTRUE;
}

Int_t MParContainer::GetEnvValue(const TEnv &env, TString prefix, TString postfix, Int_t dflt) const
{
    return GetEnvValue(env, prefix+"."+postfix, dflt);
}

Double_t MParContainer::GetEnvValue(const TEnv &env, TString prefix, TString postfix, Double_t dflt) const
{
    return GetEnvValue(env, prefix+"."+postfix, dflt);
}

const char *MParContainer::GetEnvValue(const TEnv &env, TString prefix, TString postfix, const char *dflt) const
{
    return GetEnvValue(env, prefix+"."+postfix, dflt);
}

Int_t MParContainer::GetEnvValue(const TEnv &env, TString prefix, Int_t dflt) const
{
    return ((TEnv&)env).GetValue(prefix, dflt);
}

Double_t MParContainer::GetEnvValue(const TEnv &env, TString prefix, Double_t dflt) const
{
    return ((TEnv&)env).GetValue(prefix, dflt);
}

const char *MParContainer::GetEnvValue(const TEnv &env, TString prefix, const char *dflt) const
{
    return ((TEnv&)env).GetValue(prefix, dflt);
}
