/* ======================================================================== *\
!
! *
! * 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"
//
// You can also use mathmatical operators, eg:
//   "5*log10(MMcEvt.fEnergy*MHillas.fSize)"
//
// The allowed operators are:
//   exp(x)    e^x
//   log(x)    natural logarithm of x
//   pow10(x)  10^x
//   log10(x)  logarithm of x to base ten
//   cos(x)    cosine of x
//   sin(x)    sine of x
//   tan(x)    tangent of x
//   cosh(x)   hyperbolic cosine of x
//   sinh(x)   hyperbolic sine of x
//   tanh(x)   hyperbolic tangent of x
//   acos(x)   arc cosine (inverse cosine) of x
//   asin(x)   arc sine (inverse sine) of x
//   atan(x)   arc tangent (inverse tangent) of x
//   sqrt(x)   square root of x
//   abs(x)    absolute value of x, |x|
//
//
// 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 <math.h>         // fabs on Alpha
#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, OperatorType_t op)
    : fOperatorType(op)
{
    fName  = "MDataChain";
    fTitle = rule;

    fMember=ParseString(rule, 1);
}

MDataChain::MDataChain()
    : fMember(NULL), fOperatorType(kENoop)
{
}

MDataChain::MDataChain(const char *rule, const char *name, const char *title)
    : fOperatorType(kENoop)
{
    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;
}

// --------------------------------------------------------------------------
//
// Checks whether at least one member has the ready-to-save flag.
//
Bool_t MDataChain::IsReadyToSave() const
{
    *fLog << all << "fM=" << fMember << "/" << (int)fMember->IsReadyToSave() << " " << endl;
    return fMember ? fMember->IsReadyToSave() : 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;
}

Int_t MDataChain::GetBracket(TString txt)
{
    Int_t first=1;
    for (int cnt=0; first<txt.Length(); first++)
    {
        if (txt[first]=='(')
            cnt++;
        if (txt[first]==')')
            cnt--;
        if (cnt==-1)
            break;
    }
    return first;
}

// --------------------------------------------------------------------------
//
// Compare a given text with the known operators. If no operator match
// kENoop is retunred, otherwise the corresponding OperatorType_t
//
MDataChain::OperatorType_t MDataChain::ParseOperator(TString txt) const
{
    txt.ToLower();

    if (txt=="abs")   return kEAbs;
    if (txt=="log")   return kELog;
    if (txt=="log10") return kELog10;
    if (txt=="sin")   return kESin;
    if (txt=="cos")   return kECos;
    if (txt=="tan")   return kETan;
    if (txt=="sinh")  return kESinH;
    if (txt=="cosh")  return kECosH;
    if (txt=="tanh")  return kETanH;
    if (txt=="asin")  return kEASin;
    if (txt=="acos")  return kEACos;
    if (txt=="atan")  return kEATan;
    if (txt=="sqrt")  return kESqrt;
    if (txt=="exp")   return kEExp;
    if (txt=="pow10") return kEPow10;

    return kENoop;
}

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=GetBracket(txt);

                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);
            txt = txt.Strip(TString::kBoth);

            if (txt.IsNull() || txt[0]!='(')
            {
                newmember = new MDataMember(text.Data());
                break;
            }

            OperatorType_t op = ParseOperator(text);
            if (op==kENoop)
            {
                *fLog << err << dbginf << "Syntax Error: Operator '" << text << "' unknown." << endl;
                if (member0)
                    delete member0;
                return NULL;
            }

            Int_t first = GetBracket(txt);
            TString sub = txt(1, first-1);
            txt.Remove(0, first+1);

            newmember = new MDataChain(sub, op);

            if (!newmember->IsValid())
            {
                *fLog << err << dbginf << "Syntax Error: Error parsing contents '" << sub << "' of operator " << text << endl;
                delete newmember;
                if (member0)
                    delete member0;
                return NULL;
            }
        }

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

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

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

    return member0;
}

Double_t MDataChain::GetValue() const
{
    if (!fMember)
    {
        *fLog << warn << "MDataChain not valid." << endl;
        return 0;
    }

    const Double_t val = fMember->GetValue();

    switch (fOperatorType)
    {
    case kEAbs:   return fabs(val);
    case kELog:   return log(val);
    case kELog10: return log10(val);
    case kESin:   return sin(val);
    case kECos:   return cos(val);
    case kETan:   return tan(val);
    case kESinH:  return sinh(val);
    case kECosH:  return cosh(val);
    case kETanH:  return tanh(val);
    case kEASin:  return asin(val);
    case kEACos:  return acos(val);
    case kEATan:  return atan(val);
    case kESqrt:  return sqrt(val);
    case kEExp:   return exp(val);
    case kEPow10: return pow(10, val);
    case kENoop:  return val;
    }

    *fLog << warn << "No Case for " << fOperatorType << " available." << endl;

    return 0;
}

void MDataChain::Print(Option_t *opt) const
{
    Bool_t bracket = fOperatorType!=kENoop && !fMember->InheritsFrom(MDataList::Class());

    switch (fOperatorType)
    {
    case kEAbs:   *fLog << "abs"   << flush; break;
    case kELog:   *fLog << "log"   << flush; break;
    case kELog10: *fLog << "log10" << flush; break;
    case kESin:   *fLog << "sin"   << flush; break;
    case kECos:   *fLog << "cos"   << flush; break;
    case kETan:   *fLog << "tan"   << flush; break;
    case kESinH:  *fLog << "sinh"  << flush; break;
    case kECosH:  *fLog << "cosh"  << flush; break;
    case kETanH:  *fLog << "tanh"  << flush; break;
    case kEASin:  *fLog << "asin"  << flush; break;
    case kEACos:  *fLog << "acos"  << flush; break;
    case kEATan:  *fLog << "atan"  << flush; break;
    case kESqrt:  *fLog << "sqrt"  << flush; break;
    case kEExp:   *fLog << "exp"   << flush; break;
    case kEPow10: *fLog << "pow10" << flush; break;
    case kENoop:
        break;
    }

    if (bracket)
        *fLog << "(" << flush;

    fMember->Print();

    if (bracket)
        *fLog << ")" << flush;
}
