/* ======================================================================== *\
!
! *
! * 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)"
//
// 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"
//
//
// 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 <stdlib.h>       // strtod, ...
#include <ctype.h>        // isalnum, ...

#include <TMethodCall.h>

#include "MParList.h"

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

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

ClassImp(MF);

// --------------------------------------------------------------------------
//
// 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  : "MF";
    fTitle = title ? title : "Filter using a text";

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

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

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

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

    return l;
}

// --------------------------------------------------------------------------
//
// 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:
            int i = IsAlNum(txt);

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

            TString 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;
                if (filter0)
                    delete filter0;
                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;
                if (filter0)
                    delete filter0;
                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;
                if (filter0)
                    delete filter0;
                return NULL;
            }

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

            newfilter = new MFDataMember(text.Data(), c, num);
            newfilter->SetName(Form("%s%c%f", text.Data(), c, num));
        }

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

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

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

    return filter0;
}

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

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

    return kTRUE;
}

// --------------------------------------------------------------------------
//
// Process all filters.
//
Bool_t MF::Process()
{
    return fFilter->Process();
}

// --------------------------------------------------------------------------
//
//  Postprocess all filters.
//
Bool_t MF::PostProcess()
{
    return fFilter->PostProcess();
}

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