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

/////////////////////////////////////////////////////////////////////////////
//
// MF                                                              
//
// With this filter you can filter in all variables from Mars parameter
// containers.
//
// In the constructor you can give the filter rule, like
//   "MHillas.fLength < 15"
// Where MHillas is the name of the parameter container in the parameter
// list and fLength is the name of the data member which should be used
// for the filter rule. If the name of the container is use specified
// (MyHillas) the name to give would be:
//   "MyHillas.fLength < 15"
//
// Also more difficult rules are allowed, like:
//   "MHillas.fWidth<0.5 && MHillas.fLength<0.6"
//
// You can also use brackets:
//   "MHillas.fSize>200 || (MHillas.fWidth<0.5 && MHillas.fLength<0.6)"
//
// If you want to use mathematic expressions (as defined in MDataChain)
// you must encapsulate it in {}-Brackets, eg:
//   "{log10(MHillas.fSize)}>3"
//
// The allowed logigal conditionals are:
//   &&: logical and
//   ||: logical or
//   ^:  exclusive or
//
// As conditional signs, for now, only:
//   <, >
// are allowed.
//
//
// 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.fSize>200 || MHillas.fWidth<0.5 && MHillas.fLength<0.6"
//
//    is parses as
//
//   "(MHillas.fSize>200 || MHillas.fWidth<0.5) && MHillas.fLength<0.6"
//
//
//  If you intend to use Data Chains in filters enclose the chains in
//  this {}-parenthesis, eg.
//
//   "{MHillas.fSize*MHillas.fWidth}<0.5"
//
// FIXME: The possibility to use also complete filters is missing.
//        Maybe we can use gInterpreter->Calc("") for this.
//        gROOT->ProcessLineFast("line");
//
/////////////////////////////////////////////////////////////////////////////
#include "MF.h"

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

#include <TMethodCall.h>

#include "MParList.h"

#include "MFilterList.h"
#include "MFDataChain.h"
#include "MFDataMember.h"

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

ClassImp(MF);

using namespace std;

const TString MF::gsDefName  = "MF";
const TString MF::gsDefTitle = "Filter setup by a text-rule";

// --------------------------------------------------------------------------
//
// Default Constructor. Don't use.
//
MF::MF() : fF(NULL)
{
    fName  = gsDefName.Data();
    fTitle = gsDefTitle.Data();
}

// --------------------------------------------------------------------------
//
// Constructor. For the text describing the filter rule please see
// the class description above.
//
MF::MF(const char *text, const char *name, const char *title)
{
    fName  = name  ? name  : gsDefName.Data();
    fTitle = title ? title : gsDefTitle.Data();

    *fLog << inf << "Trying to resolve filter rule..." << endl;
    if (!(fF=ParseString(text, 1)))
    {
        *fLog << err << dbginf << "Parsing '" << text << "' failed." << endl;
        return;
    }

    *fLog << inf << endl;
    *fLog << "Using Filter rule " << fF->GetName();
    *fLog << " for " << fName << ":" << endl;
    fF->Print();
    *fLog << endl << endl;
}

// --------------------------------------------------------------------------
//
// Destructor. Delete filters.
//
MF::~MF()
{
    if (fF)
        delete fF;
}

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

    return l;
}

MFilter *MF::ParseRule(TString &txt, MFilter *filter0, Int_t level) const
{
    TString text;

    Bool_t isrule = kFALSE;

    if (txt[0]=='{')
    {
        //
        // 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;
            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);

        text=sub;
        isrule = kTRUE;
    }
    else
    {
        int i = IsAlNum(txt);

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

        text = txt(0, i);

        txt.Remove(0, i);
    }

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

    if (txt.IsNull())
    {
        *fLog << err << dbginf << "Syntax Error: No conditional in '" << text << "'" << endl;
        return NULL;
    }

    char c;
    switch (txt[0])
    {
    case '>':
    case '<':
        c = txt[0];
        txt.Remove(0, 1);
        break;

    default:
        *fLog << err << dbginf << "Syntax Error: Conditional '" << txt[0] << "' unknown." << endl;
        return NULL;
    }

    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;
        return NULL;
    }

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

    MFilter *newfilter;
    if (isrule)
    {
        Int_t lvl = gLog.GetDebugLevel();
        gLog.SetDebugLevel(1);
        newfilter = new MFDataChain(text.Data(), c, num);
        newfilter->SetName(Form("Chain%02d%c%f", level, c, num));
        gLog.SetDebugLevel(lvl);
    }
    else
    {
        newfilter = new MFDataMember(text.Data(), c, num);
        newfilter->SetName(Form("%s%c%f", text.Data(), c, num));
    }

    return newfilter;
}
// --------------------------------------------------------------------------
//
// Parse a text string. Returns a corresponding filter of filter list.
//
MFilter *MF::ParseString(TString txt, Int_t level)
{
    MFilter *filter0=NULL;

    TString type;
    int nlist = 0;

    while (!txt.IsNull())
    {
        MFilter *newfilter = NULL;

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

        //*fLog << all << setw(level) << " " << "Text: " << level << " '" << txt << "'" << endl;

        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 (filter0)
                        delete filter0;
                    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
                //
                newfilter = ParseString(sub, level+1);
                if (!newfilter)
                {
                    *fLog << err << dbginf << "Parsing '" << sub << "' failed." << endl;
                    if (filter0)
                        delete filter0;
                    return NULL;
                }
            }
            break;

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


        case '&':
        case '|':
        case '^':
            if (filter0)
            {
                //
                // Check for the type of the conditional
                //
                TString is = txt[0];
                txt.Remove(0, 1);

                if (is==txt[0] && is!='^')
                {
                    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 (!filter0->InheritsFrom(MFilterList::Class()) || type!=is)
                {
                    MFilterList *list = new MFilterList(is);
                    list->SetName(Form("List_%s_%d", (const char*)is, 10*level+nlist++));

                    list->SetOwner();
                    list->AddToList(filter0);
                    filter0 = list;

                    type = is;
                }
                continue;
            }

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

        default:
            newfilter = ParseRule(txt, filter0, level++);
            if (!newfilter)
            {
                if (filter0)
                    delete filter0;
                return NULL;
            }
        }

        if (!filter0)
        {
            filter0 = newfilter;
            continue;
        }

        if (!filter0->InheritsFrom(MFilterList::Class()))
            continue;

        ((MFilterList*)filter0)->AddToList(newfilter);
    }

    return filter0;
}

// --------------------------------------------------------------------------
//
// PreProcess all filters.
//
Int_t MF::PreProcess(MParList *plist)
{
    if (!fF)
    {
        *fLog << err << dbginf << "No filter rule available." << endl;
        return kFALSE;
    }

    if (!fF->CallPreProcess(plist))
    {
        *fLog << err << dbginf << "PreProcessing filters in ";
        *fLog << fName << " failed." << endl;
        return kFALSE;
    }

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// Process all filters.
//
Int_t MF::Process()
{
    return fF->CallProcess();
}

// --------------------------------------------------------------------------
//
//  Postprocess all filters.
//
Int_t MF::PostProcess()
{
    return fF->CallPostProcess();
}

// --------------------------------------------------------------------------
//
// Return the result of the filter rule.
//
Bool_t MF::IsExpressionTrue() const
{
    return fF->IsConditionTrue();
}

void MF::StreamPrimitive(ofstream &out) const
{
    out << "   MF " << GetUniqueName();

    if (!fF)
    {
        out << ";" << endl;
        return;
    }

    out << "(\"" << fF->GetRule() << "\"";
        if (fName!=gsDefName || fTitle!=gsDefTitle)
    {
        out << "(\"" << fName << "\"";
        if (fTitle!=gsDefTitle)
            out << ", \"" << fTitle << "\"";
    }
    out << ");" << endl;

}

void MF::Print(Option_t *opt) const
{
    *fLog << all << underline << GetDescriptor() << endl;
    fF->Print();
    *fLog << endl << endl;
}
