// -*- mode: c++; c-basic-offset:4 -*-

// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.

// Copyright (c) 2002,2003 OPeNDAP, Inc.
// Author: James Gallagher <jgallagher@opendap.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.

#include "config.h"

//#define DODS_DEBUG

#include "ServerFunctionsList.h"
#include "ConstraintEvaluator.h"
#include "Clause.h"
#include "DataDDS.h"

#include "ce_parser.h"
#include "debug.h"
#include "parser.h"
#include "expr.h"

struct yy_buffer_state;

int ce_exprparse(libdap::ce_parser_arg *arg);

// Glue routines declared in expr.lex
void ce_expr_switch_to_buffer(void *new_buffer);
void ce_expr_delete_buffer(void * buffer);
void *ce_expr_string(const char *yy_str);

extern int ce_exprdebug;

namespace libdap {

ConstraintEvaluator::ConstraintEvaluator()
{
    // Functions are now held in BES modules. jhrg 1/30/13

    // modules load functions to this list; this class searches the list
    // instead of having it's own copy. This is very similar to the BES'
    // various List classes, but this one is part of libdap and not the
    // BES. The List class is a singleton, so each function module can
    // register it's functions to the list object.
    d_functions_list = ServerFunctionsList::TheList();
}

ConstraintEvaluator::~ConstraintEvaluator()
{
    // delete all the constants created by the parser for CE evaluation
    for (Constants_iter j = constants.begin(); j != constants.end(); j++) {
        BaseType *btp = *j;
        delete btp;
        btp = 0;
    }

    for (Clause_iter k = expr.begin(); k != expr.end(); k++) {
        Clause *cp = *k;
        delete cp;
        cp = 0;
    }
}

/** Returns the first clause in a parsed constraint expression. */
ConstraintEvaluator::Clause_iter ConstraintEvaluator::clause_begin()
{
    return expr.begin();
}

/** Returns a reference to the end of the list of clauses in a parsed
 constraint expression. It does not reference the last clause */
ConstraintEvaluator::Clause_iter ConstraintEvaluator::clause_end()
{
    return expr.end();
}

/** Returns the value of the indicated clause of a constraint
 expression. */
bool ConstraintEvaluator::clause_value(Clause_iter &iter, DDS &dds/*, const string &***/)
{
    if (expr.empty())
        throw InternalErr(__FILE__, __LINE__, "There are no CE clauses for *this* DDS object.");

    return (*iter)->value(dds);
}

/** @brief Add a clause to a constraint expression.

 This function adds an operator clause to the constraint
 expression.

 @param op An integer indicating the operator in use.  These
 values are generated by \c bison.
 @param arg1 A pointer to the argument on the left side of the
 operator.
 @param arg2 A pointer to a list of the arguments on the right
 side of the operator.
 */
void ConstraintEvaluator::append_clause(int op, rvalue *arg1, rvalue_list *arg2)
{
    Clause *clause = new Clause(op, arg1, arg2);

    expr.push_back(clause);
}

/** @brief Add a clause to a constraint expression.

 This function adds a boolean function clause to the constraint
 expression.

 @param func A pointer to a boolean function from the list of
 supported functions.
 @param args A list of arguments to that function.
 */
void ConstraintEvaluator::append_clause(bool_func func, rvalue_list *args)
{
    Clause *clause = new Clause(func, args);

    expr.push_back(clause);
}

/** @brief Add a clause to a constraint expression.

 This function adds a real-valued (BaseType) function clause to
 the constraint expression.

 @param func A pointer to a BaseType function from the list of
 supported functions.
 @param args A list of arguments to that function.
 */
void ConstraintEvaluator::append_clause(btp_func func, rvalue_list *args)
{
    Clause *clause = new Clause(func, args);

    expr.push_back(clause);
}

/** The Constraint Evaluator maintains a list of BaseType pointers for all the
 constants that the constraint expression parser generates. These objects
 are deleted by the Constraint Evaluator destructor. Note that there are no
 list accessors; these constants are never accessed from the list. The list
 is simply a convenient way to make sure the constants are disposed of
 properly.
 */
void ConstraintEvaluator::append_constant(BaseType *btp)
{
    constants.push_back(btp);
}

/** @brief Find a Boolean function with a given name in the function list. */
bool ConstraintEvaluator::find_function(const string &name, bool_func *f) const
{
    return d_functions_list->find_function(name, f);
}

/** @brief Find a BaseType function with a given name in the function list. */
bool ConstraintEvaluator::find_function(const string &name, btp_func *f) const
{
    return d_functions_list->find_function(name, f);
}

/** @brief Find a projection function with a given name in the function list. */
bool ConstraintEvaluator::find_function(const string &name, proj_func *f) const
{
    return d_functions_list->find_function(name, f);
}
//@}

/** @brief Does the current constraint expression return a BaseType
 pointer?
 This method does not evaluate the clause, it provides information to the
 evaluator regarding _how_ to evaluate the clause.
 @return True if the clause is a function that returns a BaseType* and
 false otherwise
 @deprecated
 */
bool ConstraintEvaluator::functional_expression()
{
    if (expr.empty())
        return false;

    Clause *cp = expr[0];
    return cp->value_clause();
}

/** @brief Evaluate a function-valued constraint expression.
 * @deprecated
 * */
BaseType *
ConstraintEvaluator::eval_function(DDS &dds, const string &)
{
    if (expr.size() != 1)
        throw InternalErr(__FILE__, __LINE__, "The length of the list of CE clauses is not 1.");

    Clause *cp = expr[0];
    BaseType *result;
    if (cp->value(dds, &result))
        return result;
    else
        return NULL;
}

/** @brief Does the current constraint expression contain function clauses

 This method does not evaluate the clauses, it provides information to the
 evaluator regarding _how_ to evaluate the clause.

 @note Added for libdap 3.11

 @return True if the current constraint contains function clauses,
 otherwise returns False */
bool ConstraintEvaluator::function_clauses()
{
    if (expr.empty())
        return false;

    for (unsigned int i = 0; i < expr.size(); ++i) {
        Clause *cp = expr[i];
        if (!cp->value_clause())
            return false;
    }

    return true;
}

/** @brief Evaluate a function-valued constraint expression that contains
 several function calls.

 This method can be called for any function-valued constraint expression.
 Unlike eval_function(), it will package the return value in a new DDS
 object. The server should free this object once it has been serialized
 and sent.

 @note While there is another type of function that can appear in a CE (a
 'projection function') those are evaluated by the ce parser - they are used
 to insert new variables into the DDS as a side effect of CE evaluation.
 That kind of function can never appear here; these are all functions that
 return BaseType pointers.

 @note Added for libdap 3.11 */
DDS *
ConstraintEvaluator::eval_function_clauses(DDS &dds)
{
    if (expr.empty())
        throw InternalErr(__FILE__, __LINE__, "The constraint expression is empty.");

    DDS *fdds = new DDS(dds.get_factory(), "function_result_" + dds.get_dataset_name());
    for (unsigned int i = 0; i < expr.size(); ++i) {
        Clause *cp = expr[i];
        BaseType *result;
        if (cp->value(dds, &result)) {
            // This is correct: The function must allocate the memory for the result
            // variable. 11/30/12 jhrg
            fdds->add_var_nocopy(result);
        }
        else {
            delete fdds;
            throw Error(internal_error, "A function was called but failed to return a value.");
        }
    }

    return fdds;
}

/** @brief Evaluate a function-valued constraint expression that contains
 several function calls. Takes and returns a DataDDS.

 @see ConstraintEvaluator::eval_function_clauses(DataDDS &dds)
 @note Added for libdap 3.11 */
DataDDS *
ConstraintEvaluator::eval_function_clauses(DataDDS &dds)
{
    if (expr.empty())
        throw InternalErr(__FILE__, __LINE__, "The constraint expression is empty.");

    DataDDS *fdds = new DataDDS(dds.get_factory(), "function_result_" + dds.get_dataset_name(), dds.get_version(),
            dds.get_protocol());

    for (unsigned int i = 0; i < expr.size(); ++i) {
        Clause *cp = expr[i];
        BaseType *result;
        if (cp->value(dds, &result)) {
            fdds->add_var_nocopy(result);
        }
        else {
            delete fdds;
            throw Error(internal_error, "A function was called but failed to return a value.");
        }
    }

    return fdds;
}

/** @brief Does the current constraint expression return a boolean value? */
bool ConstraintEvaluator::boolean_expression()
{
    if (expr.empty())
        return false;

    bool boolean = true;
    for (Clause_iter i = expr.begin(); i != expr.end(); i++) {
        boolean = boolean && (*i)->boolean_clause();
    }

    return boolean;
}

/** @brief Evaluate a boolean-valued constraint expression.
 This is main method for the evaluator and is called by the
 BaseType::serialize() methods.

 @param dds Use these variables when evaluating the expressions.
 @param dataset This string is passed to the read() methods.
 @return True if the expression is true, false otherwise. */
bool ConstraintEvaluator::eval_selection(DDS &dds, const string &)
{
    if (expr.empty()) {
        DBG(cerr << "No selection recorded" << endl);
        return true;
    }

    DBG(cerr << "Eval selection" << endl);

    // A CE is made up of zero or more clauses, each of which has a boolean
    // value. The value of the CE is the logical AND of the clause
    // values. See ConstraintEvaluator::clause::value(...) for information on logical ORs in
    // CEs.
    bool result = true;
    for (Clause_iter i = expr.begin(); i != expr.end() && result; i++) {
        // A selection expression *must* contain only boolean clauses!
        if (!((*i)->boolean_clause()))
            throw InternalErr(__FILE__, __LINE__, "A selection expression must contain only boolean clauses.");
        result = result && (*i)->value(dds);
    }

    return result;
}

/** @brief Parse the constraint expression given the current DDS.

 Evaluate the constraint expression; return the value of the expression.
 As a side effect, mark the DDS so that BaseType's mfuncs can be used to
 correctly read the variable's value and send it to the client.

 @param constraint A string containing the constraint expression.
 @param dds The DDS that provides the environment within which the
 constraint is evaluated.
 @exception Throws Error if the constraint does not parse. */
void ConstraintEvaluator::parse_constraint(const string &constraint, DDS &dds)
{
    void *buffer = ce_expr_string(constraint.c_str());

    // Toggle this to debug the parser. A last resort...
    ce_exprdebug = false;

    ce_expr_switch_to_buffer(buffer);

    ce_parser_arg arg(this, &dds);

    // For all errors, exprparse will throw Error.
    try {
    	ce_exprparse(&arg);
    	ce_expr_delete_buffer(buffer);
    }
    catch (...) {
    	// Make sure to remove the buffer when there's an error
    	ce_expr_delete_buffer(buffer);
    	throw;
    }
}

} // namespace libdap
