/******************************************************************************
 *
 * Component: ODS formula Engine
 * Purpose:
 * Author: Even Rouault <even dot rouault at spatialys.com>
 *
 ******************************************************************************
 * Copyright (C) 2010 Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2012, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include <cctype>
#include <cmath>

#include "cpl_conv.h"
#include "ods_formula.h"

CPL_CVSID("$Id$")

namespace {
#include "ods_formula_parser.hpp"

int ods_formulalex( ods_formula_node **ppNode,
                    ods_formula_parse_context *context );

#include "ods_formula_parser.cpp"
} /* end of anonymous namespace */

#define YYSTYPE  ods_formula_node*

static const SingleOpStruct apsSingleOp[] =
{
    { "ABS", ODS_ABS, fabs },
    { "SQRT", ODS_SQRT, sqrt },
    { "COS", ODS_COS, cos },
    { "SIN", ODS_SIN, sin },
    { "TAN", ODS_TAN, tan },
    { "ACOS", ODS_ACOS, acos },
    { "ASIN", ODS_ASIN, asin },
    { "ATAN", ODS_ATAN, atan },
    { "EXP", ODS_EXP, exp },
    { "LN", ODS_LN, log },
    { "LOG", ODS_LOG, log10 },
    { "LOG10", ODS_LOG, log10 },
};

const SingleOpStruct* ODSGetSingleOpEntry(const char* pszName)
{
    for(size_t i = 0; i < sizeof(apsSingleOp) / sizeof(apsSingleOp[0]); i++)
    {
        if (EQUAL(pszName, apsSingleOp[i].pszName))
            return &apsSingleOp[i];
    }
    return nullptr;
}

const SingleOpStruct* ODSGetSingleOpEntry(ods_formula_op eOp)
{
    for(size_t i = 0; i < sizeof(apsSingleOp) / sizeof(apsSingleOp[0]); i++)
    {
        if (eOp == apsSingleOp[i].eOp)
            return &apsSingleOp[i];
    }
    return nullptr;
}

/************************************************************************/
/*                               swqlex()                               */
/*                                                                      */
/*      Read back a token from the input.                               */
/************************************************************************/
namespace {
int ods_formulalex( YYSTYPE *ppNode, ods_formula_parse_context *context )
{
    const char *pszInput = context->pszNext;

    *ppNode = nullptr;

/* -------------------------------------------------------------------- */
/*      Do we have a start symbol to return?                            */
/* -------------------------------------------------------------------- */
    if( context->nStartToken != 0 )
    {
        int nRet = context->nStartToken;
        context->nStartToken = 0;
        return nRet;
    }

/* -------------------------------------------------------------------- */
/*      Skip white space.                                               */
/* -------------------------------------------------------------------- */
    while( *pszInput == ' ' || *pszInput == '\t'
           || *pszInput == 10 || *pszInput == 13 )
        pszInput++;

    if( *pszInput == '\0' )
    {
        context->pszNext = pszInput;
        return EOF;
    }

/* -------------------------------------------------------------------- */
/*      Handle string constants.                                        */
/* -------------------------------------------------------------------- */
    if( *pszInput == '"' )
    {
        pszInput++;

        char *token = static_cast<char *>( CPLMalloc(strlen(pszInput)+1) );
        int i_token = 0;

        while( *pszInput != '\0' )
        {
            if( *pszInput == '\\' && pszInput[1] == '"' )
                pszInput++;
            else if( *pszInput == '\\' && pszInput[1] == '\'' )
                pszInput++;
            else if( *pszInput == '\'' && pszInput[1] == '\'' )
                pszInput++;
            else if( *pszInput == '"' )
            {
                pszInput++;
                break;
            }
            else if( *pszInput == '\'' )
            {
                pszInput++;
                break;
            }

            token[i_token++] = *(pszInput++);
        }
        token[i_token] = '\0';

        *ppNode = new ods_formula_node( token );
        CPLFree( token );

        context->pszNext = pszInput;

        return ODST_STRING;
    }

/* -------------------------------------------------------------------- */
/*      Handle numbers.                                                 */
/* -------------------------------------------------------------------- */
    else if( *pszInput >= '0' && *pszInput <= '9' )
    {
        const char *pszNext = pszInput + 1;

        CPLString osToken;
        osToken += *pszInput;

        // collect non-decimal part of number
        while( *pszNext >= '0' && *pszNext <= '9' )
            osToken += *(pszNext++);

        // collect decimal places.
        if( *pszNext == '.' )
        {
            osToken += *(pszNext++);
            while( *pszNext >= '0' && *pszNext <= '9' )
                osToken += *(pszNext++);
        }

        // collect exponent
        if( *pszNext == 'e' || *pszNext == 'E' )
        {
            osToken += *(pszNext++);
            if( *pszNext == '-' || *pszNext == '+' )
                osToken += *(pszNext++);
            while( *pszNext >= '0' && *pszNext <= '9' )
                osToken += *(pszNext++);
        }

        context->pszNext = pszNext;

        if( strstr(osToken,".")
            || strstr(osToken,"e")
            || strstr(osToken,"E") )
        {
            *ppNode = new ods_formula_node( CPLAtof(osToken) );
        }
        else
        {
            GIntBig nVal = CPLAtoGIntBig(osToken);
            if( osToken.size() >= 12 || nVal < INT_MIN || nVal > INT_MAX  )
                *ppNode = new ods_formula_node( CPLAtof(osToken) );
            else
                *ppNode = new ods_formula_node( static_cast<int>(nVal) );
        }

        return ODST_NUMBER;
    }

/* -------------------------------------------------------------------- */
/*      Handle alpha-numerics.                                          */
/* -------------------------------------------------------------------- */
    else if( *pszInput == '.' || isalnum( *pszInput ) )
    {
        int nReturn = ODST_IDENTIFIER;
        const char *pszNext = pszInput + 1;

        CPLString osToken;
        osToken += *pszInput;

        // collect text characters
        while( isalnum( *pszNext ) || *pszNext == '_'
               || ((unsigned char) *pszNext) > 127 )
            osToken += *(pszNext++);

        context->pszNext = pszNext;

        /* Constants */
        if( EQUAL(osToken,"TRUE") )
        {
            *ppNode = new ods_formula_node( 1 );
            return ODST_NUMBER;
        }
        else if( EQUAL(osToken,"FALSE") )
        {
            *ppNode = new ods_formula_node( 0 );
            return ODST_NUMBER;
        }

        else if( EQUAL(osToken,"NOT") )
            nReturn = ODST_NOT;
        else if( EQUAL(osToken,"AND") )
            nReturn = ODST_AND;
        else if( EQUAL(osToken,"OR") )
            nReturn = ODST_OR;
        else if( EQUAL(osToken,"IF") )
            nReturn = ODST_IF;

        /* No-arg functions */
        else if( EQUAL(osToken,"PI") )
        {
            *ppNode = new ods_formula_node( ODS_PI );
            return ODST_FUNCTION_NO_ARG;
        }

        /* Single-arg functions */
        else if( EQUAL(osToken,"LEN") )
        {
            *ppNode = new ods_formula_node( ODS_LEN );
            return ODST_FUNCTION_SINGLE_ARG;
        }
        /*
        else if( EQUAL(osToken,"T") )
        {
            *ppNode = new ods_formula_node( ODS_T );
            return ODST_FUNCTION_SINGLE_ARG;
        }*/

        /* Tow-arg functions */
        else if( EQUAL(osToken,"MOD") )
        {
            *ppNode = new ods_formula_node( ODS_MODULUS );
            return ODST_FUNCTION_TWO_ARG;
        }
        else if( EQUAL(osToken,"LEFT") )
        {
            *ppNode = new ods_formula_node( ODS_LEFT );
            return ODST_FUNCTION_TWO_ARG;
        }
        else if( EQUAL(osToken,"RIGHT") )
        {
            *ppNode = new ods_formula_node( ODS_RIGHT );
            return ODST_FUNCTION_TWO_ARG;
        }

        /* Three-arg functions */
        else if( EQUAL(osToken,"MID") )
        {
            *ppNode = new ods_formula_node( ODS_MID );
            return ODST_FUNCTION_THREE_ARG;
        }

        /* Multiple-arg functions */
        else if( EQUAL(osToken,"SUM") )
        {
            *ppNode = new ods_formula_node( ODS_SUM );
            nReturn = ODST_FUNCTION_ARG_LIST;
        }
        else if( EQUAL(osToken,"AVERAGE") )
        {
            *ppNode = new ods_formula_node( ODS_AVERAGE );
            nReturn = ODST_FUNCTION_ARG_LIST;
        }
        else if( EQUAL(osToken,"MIN") )
        {
            *ppNode = new ods_formula_node( ODS_MIN );
            nReturn = ODST_FUNCTION_ARG_LIST;
        }
        else if( EQUAL(osToken,"MAX") )
        {
            *ppNode = new ods_formula_node( ODS_MAX );
            nReturn = ODST_FUNCTION_ARG_LIST;
        }
        else if( EQUAL(osToken,"COUNT") )
        {
            *ppNode = new ods_formula_node( ODS_COUNT );
            nReturn = ODST_FUNCTION_ARG_LIST;
        }
        else if( EQUAL(osToken,"COUNTA") )
        {
            *ppNode = new ods_formula_node( ODS_COUNTA );
            nReturn = ODST_FUNCTION_ARG_LIST;
        }

        else
        {
            const SingleOpStruct* psSingleOp = ODSGetSingleOpEntry(osToken);
            if (psSingleOp != nullptr)
            {
                *ppNode = new ods_formula_node( psSingleOp->eOp );
                nReturn = ODST_FUNCTION_SINGLE_ARG;
            }
            else
            {
                *ppNode = new ods_formula_node( osToken );
                nReturn = ODST_IDENTIFIER;
            }
        }

        return nReturn;
    }

/* -------------------------------------------------------------------- */
/*      Handle special tokens.                                          */
/* -------------------------------------------------------------------- */
    else
    {
        context->pszNext = pszInput+1;
        return *pszInput;
    }
}
} /* end of anonymous namespace */

/************************************************************************/
/*                        ods_formula_compile()                         */
/************************************************************************/

ods_formula_node* ods_formula_compile( const char *expr )

{
    ods_formula_parse_context context;

    context.pszInput = expr;
    context.pszNext = expr;
    context.nStartToken = ODST_START;

    if( ods_formulaparse( &context ) == 0 )
    {
        return context.poRoot;
    }

    delete context.poRoot;
    return nullptr;
}
