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

/////////////////////////////////////////////////////////////////////////////
//
//   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
//   sqr(x)    square of x
//   abs(x)    absolute value of x, |x|
//   floor(x)  round down to the nearest integer (floor(9.9)=9)
//   r2d(x)    transform radians to degrees
//   d2r(x)    transform degrees to radians
//   rand(x)   returns a uniform deviate on the interval ( 0, x ].
//             (gRandom->Uniform(x) is returned)
//   randp(x)  returns gRandom->Poisson(x)
//   rande(x)  returns gRandom->Exp(x)
//   randi(x)  returns gRandom->Integer(x)
//   randg(x)  returns gRandom->Gaus(0, x)
//   randl(x)  returns gRandom->Landau(0, x)
//
// REMARK:
//         - All the random functions are returning 0 if gRandom==0
//         - You may get better results if you are using a TRandom3
//
// 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 <TRandom.h>

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

#include "MDataList.h"
#include "MDataValue.h"
#include "MDataMember.h"
#include "MDataElement.h"

ClassImp(MDataChain);

// --------------------------------------------------------------------------
//
//  Constructor which takes a rule and a surrounding operator as argument
//
MDataChain::MDataChain(const char *rule, OperatorType_t op)
    : fOperatorType(op)
{
    fName  = "MDataChain";
    fTitle = rule;

    fMember=ParseString(rule, 1);
}

// --------------------------------------------------------------------------
//
//  Default constructor
//
MDataChain::MDataChain()
    : fMember(NULL), fOperatorType(kENoop)
{
}

// --------------------------------------------------------------------------
//
// Constructor taking a rule as an argument. For more details see
// class description
//
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]!='.' && /*txt[i]!='['&&txt[i]!=']'&&*/
            ((txt[i]!='-' && txt[i]!='+') || i!=0))
            return i;
    }

    return l;
}

Int_t MDataChain::GetBracket(TString txt, char open, char close)
{
    Int_t first=1;
    for (int cnt=0; first<txt.Length(); first++)
    {
        if (txt[first]==open)
            cnt++;
        if (txt[first]==close)
            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=="fabs")  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=="sqr")   return kESqr;
    if (txt=="exp")   return kEExp;
    if (txt=="pow10") return kEPow10;
    if (txt=="sgn")   return kESgn;
    if (txt=="floor") return kEFloor;
    if (txt=="r2d")   return kERad2Deg;
    if (txt=="d2r")   return kEDeg2Rad;
    if (txt=="rand")  return kERandom;
    if (txt=="randp") return kERandomP;
    if (txt=="rande") return kERandomE;
    if (txt=="randi") return kERandomI;
    if (txt=="randg") return kERandomG;
    if (txt=="randl") return kERandomL;
    if (txt[0]=='-')  return kENegative;
    if (txt[0]=='+')  return kEPositive;

    return kENoop;
}

// --------------------------------------------------------------------------
//
// Core of the data chain. Here the chain is constructed out of the rule.
//
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 symbol
                //
                char is = txt[0];
                txt.Remove(0, 1);

                //
                // If no filter is available or the available filter
                // is of a different symbols we have to create a new
                // data list with the new symbol
                //
                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;
            }

            if (txt[0]!='-' && txt[0]!='+')
            {
                *fLog << err << dbginf << "Syntax Error: First argument of symbol '";
                *fLog << txt[0] << "' missing." << endl;
                if (member0)
                    delete member0;
                return NULL;
            }

            // FALLTHROU

        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            if ((txt[0]!='-' && txt[0]!='+') || isdigit(txt[1]) || txt[1]=='.')
            {
                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;
            }

            // FALLTHROUH

        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]=='[')
            {
                Int_t first = GetBracket(txt, '[', ']');
                TString op  = txt(1, first-1);
                txt.Remove(0, first+1);

                newmember = new MDataElement(text, atoi(op));
                break;
            }
            if ((txt.IsNull() || txt[0]!='(') && text[0]!='-' && text[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 = op==kENegative || op==kEPositive ? text.Remove(0,1) + txt : 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;
}

// --------------------------------------------------------------------------
//
// Returns the value described by the rule
//
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 kESqr:      return val*val;
    case kEExp:      return exp(val);
    case kEPow10:    return pow(10, val);
    case kESgn:      return val<0 ? -1 : 1;
    case kENegative: return -val;
    case kEPositive: return val;
    case kEFloor:    return floor(val);
    case kERad2Deg:  return val*180/TMath::Pi();
    case kEDeg2Rad:  return val*TMath::Pi()/180;
    case kERandom:   return gRandom ? gRandom->Uniform(val)   : 0;
    case kERandomP:  return gRandom ? gRandom->Poisson(val)   : 0;
    case kERandomE:  return gRandom ? gRandom->Exp(val)       : 0;
    case kERandomI:  return gRandom ? gRandom->Integer(val)   : 0;
    case kERandomG:  return gRandom ? gRandom->Gaus(0, val)   : 0;
    case kERandomL:  return gRandom ? gRandom->Landau(0, val) : 0;
    case kENoop:     return val;
    }

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

    return 0;
}

    /*
void MDataChain::Print(Option_t *opt) const
{
    *fLog << GetRule() << flush;
    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 kESgn:      *fLog << "sgn"   << flush; break;
    case kENegative: *fLog << "-" << flush; break;
    case kEPositive: *fLog << "+" << flush; break;
    case kENoop:
        break;
    }

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

    fMember->Print();

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

// --------------------------------------------------------------------------
//
// Builds a rule from all the chain members. This is a rule which could
// be used to rebuild the chain.
//
TString MDataChain::GetRule() const
{
    if (!fMember)
        return "<n/a>";

    TString str;

    Bool_t bracket = fOperatorType!=kENoop && !fMember->InheritsFrom(MDataList::Class());

    switch (fOperatorType)
    {
    case kEAbs:      str += "abs"  ; break;
    case kELog:      str += "log"  ; break;
    case kELog10:    str += "log10"; break;
    case kESin:      str += "sin"  ; break;
    case kECos:      str += "cos"  ; break;
    case kETan:      str += "tan"  ; break;
    case kESinH:     str += "sinh" ; break;
    case kECosH:     str += "cosh" ; break;
    case kETanH:     str += "tanh" ; break;
    case kEASin:     str += "asin" ; break;
    case kEACos:     str += "acos" ; break;
    case kEATan:     str += "atan" ; break;
    case kESqrt:     str += "sqrt" ; break;
    case kESqr:      str += "sqr"  ; break;
    case kEExp:      str += "exp"  ; break;
    case kEPow10:    str += "pow10"; break;
    case kESgn:      str += "sgn"  ; break;
    case kENegative: str += "-"    ; break;
    case kEPositive: str += "+"    ; break;
    case kEFloor:    str += "floor"; break;
    case kERad2Deg:  str += "r2d"  ; break;
    case kEDeg2Rad:  str += "d2r"  ; break;
    case kERandom:   str += "rand" ; break;
    case kERandomP:  str += "randp"; break;
    case kERandomE:  str += "rande"; break;
    case kERandomI:  str += "randi"; break;
    case kERandomG:  str += "randg"; break;
    case kERandomL:  str += "randl"; break;
    case kENoop:
        break;
    }

    if (bracket)
        str += "(";

    str += fMember->GetRule();

    if (bracket)
        str += ")";

    return str;
}

// --------------------------------------------------------------------------
//
// Return a comma seperated list of all data members used in the chain.
// This is mainly used in MTask::AddToBranchList
//
TString MDataChain::GetDataMember() const
{
    return fMember->GetDataMember();
}

