/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <axiom_xpath.h>
#include "xpath_internals.h"
#include "xpath_internals_parser.h"
#include "xpath_internals_engine.h"
#include "xpath_functions.h"
#include "xpath_streaming.h"

/* Create XPath context */
AXIS2_EXTERN axiom_xpath_context_t * AXIS2_CALL
axiom_xpath_context_create(
    const axutil_env_t *env,
    axiom_node_t * root_node)
{
    axiom_xpath_context_t* context;

    /*HACK: xpath impl requires a dummy root node in order to process properly.*/
    axiom_node_t * dummy_root;
    dummy_root = axiom_node_create(env);
    axiom_node_add_child(dummy_root, env, root_node);

    context = AXIS2_MALLOC(env->allocator,
        sizeof(axiom_xpath_context_t));

    context->env = env;
    context->root_node = dummy_root;
    context->node = dummy_root;
    context->expr = NULL;
    context->attribute = NULL;
    context->namespaces = NULL;
    context->functions = NULL;

    axiom_xpath_register_default_functions_set(context);

    return context;
}

/* Compile XPath expression */
AXIS2_EXTERN axiom_xpath_expression_t * AXIS2_CALL
axiom_xpath_compile_expression(
    const axutil_env_t *env,
    const axis2_char_t* xpath_expr)
{
    axiom_xpath_expression_t* expr;

    expr = AXIS2_MALLOC(env->allocator,
        sizeof(axiom_xpath_expression_t));

    expr->expr_str = axutil_strdup(env, xpath_expr);

    if (axiom_xpath_compile(env, expr) == AXIOM_XPATH_PARSE_ERROR)
    {
        axiom_xpath_free_expression(env, expr);
        return NULL;
    }
    else
    {
        return expr;
    }
}

/* Evaluate compiled XPath expression */
AXIS2_EXTERN axiom_xpath_result_t * AXIS2_CALL
axiom_xpath_evaluate(
    axiom_xpath_context_t *context,
    axiom_xpath_expression_t *xpath_expr)
{
    axiom_xpath_expression_copy(context, xpath_expr);

    context->streaming = AXIS2_FALSE;

    return axiom_xpath_run(context);
}

AXIS2_EXTERN axiom_xpath_result_t * AXIS2_CALL
axiom_xpath_evaluate_streaming(
    axiom_xpath_context_t *context,
    axiom_xpath_expression_t *xpath_expr)
{
    axiom_xpath_result_t *res;

    axiom_xpath_expression_copy(context, xpath_expr);

    if (axiom_xpath_streaming_check(context->env, xpath_expr))
    {
        context->streaming = AXIS2_TRUE;
        return axiom_xpath_run(context);
    }
    else
    {
        res = AXIS2_MALLOC(
            context->env->allocator, sizeof(axiom_xpath_result_t));
        res->nodes = NULL;
        res->flag = AXIOM_XPATH_ERROR_STREAMING_NOT_SUPPORTED;

        return res;
    }
}

AXIS2_EXTERN void AXIS2_CALL
axiom_xpath_register_default_functions_set(
    axiom_xpath_context_t *context)
{
    axiom_xpath_register_function(
        context, "count", axiom_xpath_function_count);
}

AXIS2_EXTERN void AXIS2_CALL
axiom_xpath_register_function(
    axiom_xpath_context_t *context,
    axis2_char_t *name,
    axiom_xpath_function_t func)
{
    if (name && func)
    {
        if (!context->functions)
        {
            context->functions = axutil_hash_make(context->env);
        }

        axutil_hash_set(context->functions, name, AXIS2_HASH_KEY_STRING, (const void *)func);
    }
}

AXIS2_EXTERN axiom_xpath_function_t AXIS2_CALL
axiom_xpath_get_function(
    axiom_xpath_context_t *context,
    axis2_char_t *name)
{
    axiom_xpath_function_t func = NULL;

    if(context->functions)
    {
        func = (axiom_xpath_function_t)axutil_hash_get(context->functions, name, AXIS2_HASH_KEY_STRING);
    }

    return func;
}

AXIS2_EXTERN void AXIS2_CALL
axiom_xpath_register_namespace(
    axiom_xpath_context_t *context,
    axiom_namespace_t *ns)
{
    axis2_char_t *prefix = NULL;

    if (!context->namespaces)
    {
        context->namespaces = axutil_hash_make(context->env);
    }

    prefix = axiom_namespace_get_prefix(ns, context->env);

    if (prefix)
    {
        axutil_hash_set(context->namespaces, prefix, AXIS2_HASH_KEY_STRING, (const void *)ns);
    }
}

AXIS2_EXTERN axiom_namespace_t * AXIS2_CALL
axiom_xpath_get_namespace(
    axiom_xpath_context_t *context,
    axis2_char_t *prefix)
{
    axiom_namespace_t *ns = NULL;

    if (context->namespaces)
    {
        ns = (axiom_namespace_t *)axutil_hash_get(context->namespaces, prefix, AXIS2_HASH_KEY_STRING);
    }

    return ns;
}

AXIS2_EXTERN void AXIS2_CALL
axiom_xpath_clear_namespaces(
    axiom_xpath_context_t *context)
{
    axutil_hash_index_t *hi;
    void *val;

    if (context->namespaces)
    {
        for (hi = axutil_hash_first(context->namespaces, context->env);
            hi;
            hi = axutil_hash_next(context->env, hi))
        {
            axutil_hash_this(hi, NULL, NULL, &val);
            axiom_namespace_free((axiom_namespace_t *)val, context->env);
        }

        axutil_hash_free(context->namespaces, context->env);
    }

    context->namespaces = NULL;
}

/* Cast to boolean */
AXIS2_EXTERN axis2_bool_t AXIS2_CALL
axiom_xpath_cast_node_to_boolean(
    const axutil_env_t *env,
    axiom_xpath_result_node_t * node)
{
    if(node->type == AXIOM_XPATH_TYPE_BOOLEAN)
    {
        return *(axis2_bool_t *)node->value;
    }
    else if(node->type == AXIOM_XPATH_TYPE_NUMBER)
    {
        /* Cannot evaluate as *(double *)(node->value) == 1e-12
         since there might be an precision error */
        if(*(double *)(node->value) > 1e-12 || *(double *)(node->value) < -1e-12)
        {
            return AXIS2_TRUE;
        }
        else
        {
            return AXIS2_FALSE;
        }
    }
    else if(node->value)
    {
        return AXIS2_TRUE;
    }
    else
    {
        return AXIS2_FALSE;
    }
}

/* Cast to double */
AXIS2_EXTERN double AXIS2_CALL
axiom_xpath_cast_node_to_number(
    const axutil_env_t *env,
    axiom_xpath_result_node_t * node)
{
    if (node->type == AXIOM_XPATH_TYPE_BOOLEAN)
    {
        if (*(axis2_bool_t *)(node->value) == AXIS2_TRUE)
        {
            return 1.0;
        }
        else
        {
            return 0.0;
        }
    }
    else if (node->type == AXIOM_XPATH_TYPE_NUMBER)
    {
        return *(double *)node->value;
    }
    else if (node->value)
    {
        return 1.0;
    }
    else
    {
        return 0.0;
    }
}

/* Cast to text */
AXIS2_EXTERN axis2_char_t * AXIS2_CALL
axiom_xpath_cast_node_to_string(
    const axutil_env_t *env,
    axiom_xpath_result_node_t * node)
{
    axiom_element_t *ele;
    axis2_char_t *res;

    if (!node->value)
    {
        return NULL;
    }

    if (node->type == AXIOM_XPATH_TYPE_BOOLEAN)
    {
        if (*(axis2_bool_t *)(node->value) == AXIS2_TRUE)
        {
            return axutil_strdup(env, "true");
        }
        else
        {
            return axutil_strdup(env, "false");
        }
    }
    else if (node->type == AXIOM_XPATH_TYPE_NUMBER)
    {
        /* Allocate 50 bytes */
        res = AXIS2_MALLOC(env->allocator, sizeof(axis2_char_t) * 50);

        sprintf(res, "%lf", *(double *)(node->value));

        return res;
    }
    else if (node->type == AXIOM_XPATH_TYPE_TEXT)
    {
        return (axis2_char_t *)node->value;
    }
    else if (node->type == AXIOM_XPATH_TYPE_NODE)
    {
        ele = (axiom_element_t *)axiom_node_get_data_element(
            (axiom_node_t *)(node->value), env);

        if (ele)
        {
            return axiom_element_get_text(
                ele, env, (axiom_node_t *)(node->value));
        }
        else
        {
            return NULL;
        }
    }
    else if (node->type == AXIOM_XPATH_TYPE_ATTRIBUTE)
    {
        return axiom_attribute_get_value(
            (axiom_attribute_t *)(node->value), env);
    }
    else if (node->type == AXIOM_XPATH_TYPE_NAMESPACE)
    {
        return axiom_namespace_get_prefix(
            (axiom_namespace_t *)(node->value), env);
    }

    return NULL;
}

/* Cast to axiom node */
AXIS2_EXTERN axiom_node_t * AXIS2_CALL
axiom_xpath_cast_node2axiom_node(
    const axutil_env_t *env,
    axiom_xpath_result_node_t * node)
{
    if (node->type == AXIOM_XPATH_TYPE_NODE && node->value)
    {
        return (axiom_node_t *)node->value;
    }
    else
    {
        return NULL;
    }
}

/* Free context */
AXIS2_EXTERN void AXIS2_CALL
axiom_xpath_free_context(
    const axutil_env_t *env,
    axiom_xpath_context_t *context)
{
    if (context)
    {
        /* Free the expression if not freed */
        if (context->expr)
        {
            axiom_xpath_free_expression(env, context->expr);

            context->expr = NULL;
        }

        if (context->root_node)
        {
            axiom_node_detach(axiom_node_get_first_child(context->root_node, context->env), context->env);
            axiom_node_free_tree(context->root_node, context->env);
            context->root_node = NULL;
        }

        if (context->functions)
        {
            axutil_hash_free(context->functions, context->env);
            context->functions = NULL;
        }

        if(context->namespaces)
        {
            axiom_xpath_clear_namespaces(context);
            context->namespaces = NULL;
        }

        if(context->attribute)
        {
            axiom_attribute_free(context->attribute, context->env);
            context->attribute = NULL;
        }

        AXIS2_FREE(env->allocator, context);
    }
}

/* Free expression */
AXIS2_EXTERN void AXIS2_CALL
axiom_xpath_free_expression(
    const axutil_env_t *env,
    axiom_xpath_expression_t * xpath_expr)
{
    int num = 0;
    int i = 0;
    axiom_xpath_operation_t * op = NULL;
    axiom_xpath_node_test_t * node_test = NULL;

    if (xpath_expr)
    {
        if (xpath_expr->expr_str)
        {
            AXIS2_FREE(env->allocator, xpath_expr->expr_str);

            xpath_expr->expr_str = NULL;
        }

        if (xpath_expr->operations)
        {
            num = axutil_array_list_size(xpath_expr->operations, env);
            for (i=0; i<num; i++)
            {
                op = (axiom_xpath_operation_t *) axutil_array_list_get(xpath_expr->operations, env, i);
                if (op)
                {
                    if(op->opr)
                    {
                        if (op->opr != AXIOM_XPATH_OPERATION_LITERAL)
                        {
                            if ((op->par1) && (op->par1 != AXIOM_XPATH_PARSE_END))
                            {
                                node_test = (axiom_xpath_node_test_t *)op->par1;
                                if (node_test)
                                {
                                    if (node_test->type >= 1 && node_test->type <= 6)
                                    {
                                        if (node_test->name)
                                        {
                                            AXIS2_FREE(env->allocator, node_test->name);
                                        }

                                        if (node_test->prefix)
                                        {
                                            AXIS2_FREE(env->allocator, node_test->prefix);
                                        }

                                        if (node_test->lit)
                                        {
                                            AXIS2_FREE(env->allocator, node_test->lit);
                                        }
                                    }
                                } 
                            }
                        }
                        AXIS2_FREE(env->allocator, op->par1);
                    }

                    if ((op->par2) && (op->par2 != AXIOM_XPATH_PARSE_END))
                    {
                        AXIS2_FREE(env->allocator, op->par2);
                    }
                    AXIS2_FREE(env->allocator, op);
                }
            }
            axutil_array_list_free(xpath_expr->operations, env);
            xpath_expr->operations = NULL;
        }

        AXIS2_FREE(env->allocator, xpath_expr);
        xpath_expr = NULL;
    }
}

/* Free result set */
AXIS2_EXTERN void AXIS2_CALL
axiom_xpath_free_result(
    const axutil_env_t *env,
    axiom_xpath_result_t* result)
{
    if (result)
    {
        if (result->nodes)
        {
            axiom_xpath_result_node_t *node = NULL;
            while(axutil_array_list_size(result->nodes, env)) {
                node = axutil_array_list_remove(result->nodes, env, 0);
                if (node)
                {
                    if (node->value)
                    {
                        if (node->type != AXIOM_XPATH_TYPE_NODE &&
                            node->type != AXIOM_XPATH_TYPE_ATTRIBUTE &&
                            node->type != AXIOM_XPATH_TYPE_NAMESPACE)
                        {
                            AXIS2_FREE(env->allocator, node->value);
                        }
                    }
                    AXIS2_FREE(env->allocator, node);
                }
            }
            axutil_array_list_free(result->nodes, env);
        }

        AXIS2_FREE(env->allocator, result);
    }
}

/* Check if the expression can be evaluated on streaming XML */
AXIS2_EXTERN axis2_bool_t AXIS2_CALL
axiom_xpath_streaming_check(
    const axutil_env_t *env,
    axiom_xpath_expression_t* expr)
{
    axiom_xpath_streaming_t r = AXIOM_XPATH_CHECK(expr->start);

    if(r == AXIOM_XPATH_STREAMING_NOT_SUPPORTED)
    {
        return AXIS2_FALSE;
    }
    else
    {
        return AXIS2_TRUE;
    }
}

