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

/////////////////////////////////////////////////////////////////////////////
//
//   MDataChain
//
// With this chain you can concatenate simple mathematical operations on
// members of mars containers.
//
// In the constructor you can give rule, like
//   "HillasSource.fDist / MHillas.fLength"
// Where MHillas/HillasSource is the name of the parameter container in
// the parameter list and fDist/fLength is the name of the data members
// in the containers. The result will be fDist divided by fLength.
//
// You can also use brackets:
//   "HillasDource.fDist / (MHillas.fLength + MHillas.fWidth)"
//
// The allowed operations are: +, -, *, /
//
// Warning: There is no priority rule build in. So better use brackets
//   to get correct results. The rule is parsed/evaluated from the left
//   to the right, which means:
//
//   "MHillas.fWidth + MHillas.fLength / HillasSource.fDist"
//
//    is parses as
//
//   "(MHillas.fWidth + MHillas.fLength) / HillasSource.fDist"
//
//
// FIXME: The possibility to use other objects inheriting from MData
//        is missing.
//        Maybe we can use gInterpreter->Calc("") for this.
//        gROOT->ProcessLineFast("line");
//
/////////////////////////////////////////////////////////////////////////////

#include "MDataChain.h"

#include <ctype.h>        // isalnum, ...
#include <stdlib.h>       // strtod, ...

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

#include "MDataList.h"
#include "MDataValue.h"
#include "MDataMember.h"
#
ClassImp(MDataChain);

MDataChain::MDataChain(const char *rule, const char *name, const char *title)
{
    fName  = name  ? name  : "MDataChain";
    fTitle = title ? title : rule;

    *fLog << inf << "Trying to resolve rule... " << flush;
    if (!(fMember=ParseString(rule, 1)))
    {
        *fLog << err << dbginf << "Parsing '" << rule << "' failed." << endl;
        return;
    }
    *fLog << inf << "found: " << flush;
    fMember->Print();
    *fLog << endl;

}

// --------------------------------------------------------------------------
//
// PreProcesses all members in the list
//
Bool_t MDataChain::PreProcess(const MParList *pList)
{
    return fMember ? fMember->PreProcess(pList) : kFALSE;
}

// --------------------------------------------------------------------------
//
// Destructor. Delete filters.
//
MDataChain::~MDataChain()
{
    if (fMember)
        delete fMember;
}

// --------------------------------------------------------------------------
//
// Returns the number of alphanumeric characters (including '.')
// in the given string
//
Int_t MDataChain::IsAlNum(TString txt)
{
    int l = txt.Length();
    for (int i = 0; i<l; i++)
        if (!isalnum(txt[i]) && txt[i]!='.')
            return i;

    return l;
}

MData *MDataChain::ParseString(TString txt, Int_t level)
{
    MData *member0=NULL;

    char type=0;
    int nlist = 0;

    while (!txt.IsNull())
    {
        MData *newmember = NULL;

        txt = txt.Strip(TString::kBoth);

        switch (txt[0])
        {
        case '(':
            {
                //
                // Search for the corresponding bracket
                //
                Int_t first=1;
                for (int cnt=0; first<txt.Length(); first++)
                {
                    if (txt[first]=='(')
                        cnt++;
                    if (txt[first]==')')
                        cnt--;

                    if (cnt==-1)
                        break;
                }

                if (first==txt.Length())
                {
                    *fLog << err << dbginf << "Syntax Error: ')' missing." << endl;
                    if (member0)
                        delete member0;
                    return NULL;
                }

                //
                // Make a copy of the 'interieur' and delete the substringä
                // including the brackets
                //
                TString sub = txt(1, first-1);
                txt.Remove(0, first+1);

                //
                // Parse the substring
                //
                newmember = ParseString(sub, level+1);
                if (!newmember)
                {
                    *fLog << err << dbginf << "Parsing '" << sub << "' failed." << endl;
                    if (member0)
                        delete member0;
                    return NULL;
                }
            }
            break;

        case ')':
            *fLog << err << dbginf << "Syntax Error: Too many ')'" << endl;
            if (member0)
                delete member0;
            return NULL;

        case '+':
        case '-':
        case '*':
        case '/':
            if (member0)
            {
                //
                // Check for the type of the conditional
                //
                char is = txt[0];
                txt.Remove(0, 1);

                //
                // If no filter is available or the available filter
                // is of a different conditional we have to create a new
                // filter list with the new conditional
                //
                if (!member0->InheritsFrom(MDataMember::Class()) || type!=is)
                {
                    MDataList *list = new MDataList(is);
                    list->SetName(Form("List_%c_%d", is, 10*level+nlist++));

                    list->SetOwner();
                    list->AddToList(member0);

                    member0 = list;

                    type = is;
                }
                continue;
            }

            *fLog << err << dbginf << "Syntax Error: First argument of condition missing." << endl;
            if (member0)
                delete member0;
            return NULL;

        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            {
                char *end;
                Double_t num = strtod(txt.Data(), &end);
                if (!end || txt.Data()==end)
                {
                    *fLog << err << dbginf << "Error trying to convert '" << txt << "' to value." << endl;
                    if (member0)
                        delete member0;
                    return NULL;
                }

                txt.Remove(0, end-txt.Data());

                newmember = new MDataValue(num);
            }
            break;

        default:
            int i = IsAlNum(txt);

            if (i==0)
            {
                *fLog << err << dbginf << "Syntax Error: Name of data member missing in '" << txt << "'" << endl;
                if (member0)
                    delete member0;
                return NULL;
            }

            TString text = txt(0, i);

            txt.Remove(0, i);

            // *fLog << all << "Creating: '" << text.Data() << "'" << endl;

            newmember = new MDataMember(text.Data());
        }

        if (!member0)
        {
            member0 = newmember;
            continue;
        }

        if (!member0->InheritsFrom(MDataList::Class()))
            continue;

        ((MDataList*)member0)->AddToList(newmember);
    }

    return member0;
}

Double_t MDataChain::GetValue() const
{
    return fMember ? fMember->GetValue() : 0;
}

void MDataChain::Print(Option_t *opt = "") const
{
    fMember->Print();
}
