/**
 * @file BPredicate.c
 * @author Ambroz Bizjak <ambrop7@gmail.com>
 * 
 * @section LICENSE
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <misc/debug.h>
#include <misc/offset.h>
#include <misc/balloc.h>
#include <misc/compare.h>
#include <predicate/BPredicate_internal.h>
#include <predicate/BPredicate_parser.h>
#include <predicate/LexMemoryBufferInput.h>
#include <base/BLog.h>

#include <predicate/BPredicate.h>

#include <generated/blog_channel_BPredicate.h>

static int eval_predicate_node (BPredicate *p, struct predicate_node *root);

void yyerror (YYLTYPE *yylloc, yyscan_t scanner, struct predicate_node **result, char *str)
{
}

static int string_comparator (void *user, char *s1, char *s2)
{
    int cmp = strcmp(s1, s2);
    return B_COMPARE(cmp, 0);
}

static int eval_function (BPredicate *p, struct predicate_node *root)
{
    ASSERT(root->type == NODE_FUNCTION)
    
    // lookup function by name
    ASSERT(root->function.name)
    BAVLNode *tree_node;
    if (!(tree_node = BAVL_LookupExact(&p->functions_tree, root->function.name))) {
        BLog(BLOG_WARNING, "unknown function");
        return 0;
    }
    BPredicateFunction *func = UPPER_OBJECT(tree_node, BPredicateFunction, tree_node);
    
    // evaluate arguments
    struct arguments_node *arg = root->function.args;
    void *args[PREDICATE_MAX_ARGS];
    for (int i = 0; i < func->num_args; i++) {
        if (!arg) {
            BLog(BLOG_WARNING, "not enough arguments");
            return 0;
        }
        switch (func->args[i]) {
            case PREDICATE_TYPE_BOOL:
                if (arg->arg.type != ARGUMENT_PREDICATE) {
                    BLog(BLOG_WARNING, "expecting predicate argument");
                    return 0;
                }
                if (!eval_predicate_node(p, arg->arg.predicate)) {
                    return 0;
                }
                args[i] = &arg->arg.predicate->eval_value;
                break;
            case PREDICATE_TYPE_STRING:
                if (arg->arg.type != ARGUMENT_STRING) {
                    BLog(BLOG_WARNING, "expecting string argument");
                    return 0;
                }
                args[i] = arg->arg.string;
                break;
            default:
                ASSERT(0);
        }
        arg = arg->next;
    }
    
    if (arg) {
        BLog(BLOG_WARNING, "too many arguments");
        return 0;
    }
    
    // call callback
    #ifndef NDEBUG
    p->in_function = 1;
    #endif
    int res = func->callback(func->user, args);
    #ifndef NDEBUG
    p->in_function = 0;
    #endif
    if (res != 0 && res != 1) {
        BLog(BLOG_WARNING, "callback returned non-boolean");
        return 0;
    }
    
    root->eval_value = res;
    return 1;
}

int eval_predicate_node (BPredicate *p, struct predicate_node *root)
{
    ASSERT(root)
    
    switch (root->type) {
        case NODE_CONSTANT:
            root->eval_value = root->constant.val;
            return 1;
        case NODE_NEG:
            if (!eval_predicate_node(p, root->neg.op)) {
                return 0;
            }
            root->eval_value = !root->neg.op->eval_value;
            return 1;
        case NODE_CONJUNCT:
            if (!eval_predicate_node(p, root->conjunct.op1)) {
                return 0;
            }
            if (!root->conjunct.op1->eval_value) {
                root->eval_value = 0;
                return 1;
            }
            if (!eval_predicate_node(p, root->conjunct.op2)) {
                return 0;
            }
            if (!root->conjunct.op2->eval_value) {
                root->eval_value = 0;
                return 1;
            }
            root->eval_value = 1;
            return 1;
        case NODE_DISJUNCT:
            if (!eval_predicate_node(p, root->disjunct.op1)) {
                return 0;
            }
            if (root->disjunct.op1->eval_value) {
                root->eval_value = 1;
                return 1;
            }
            if (!eval_predicate_node(p, root->disjunct.op2)) {
                return 0;
            }
            if (root->disjunct.op2->eval_value) {
                root->eval_value = 1;
                return 1;
            }
            root->eval_value = 0;
            return 1;
        case NODE_FUNCTION:
            return eval_function(p, root);
        default:
            ASSERT(0)
            return 0;
    }
}

int BPredicate_Init (BPredicate *p, char *str)
{
    // initialize input buffer object
    LexMemoryBufferInput input;
    LexMemoryBufferInput_Init(&input, str, strlen(str));
    
    // initialize lexical analyzer
    yyscan_t scanner;
    yylex_init_extra(&input, &scanner);
    
    // parse
    struct predicate_node *root = NULL;
    int result = yyparse(scanner, &root);
    
    // free lexical analyzer
    yylex_destroy(scanner);
    
    // check for errors
    if (LexMemoryBufferInput_HasError(&input) || result != 0 || !root) {
        if (root) {
            free_predicate_node(root);
        }
        return 0;
    }
    
    // init tree
    p->root = root;
    
    // init functions tree
    BAVL_Init(&p->functions_tree, OFFSET_DIFF(BPredicateFunction, name, tree_node), (BAVL_comparator)string_comparator, NULL);
    
    // init debuggind
    #ifndef NDEBUG
    p->in_function = 0;
    #endif
    
    // init debug object
    DebugObject_Init(&p->d_obj);
    
    return 1;
}

void BPredicate_Free (BPredicate *p)
{
    ASSERT(BAVL_IsEmpty(&p->functions_tree))
    ASSERT(!p->in_function)
    
    // free debug object
    DebugObject_Free(&p->d_obj);
    
    // free tree
    free_predicate_node((struct predicate_node *)p->root);
}

int BPredicate_Eval (BPredicate *p)
{
    ASSERT(!p->in_function)
    
    if (!eval_predicate_node(p, (struct predicate_node *)p->root)) {
        return -1;
    }
    
    return ((struct predicate_node *)p->root)->eval_value;
}

void BPredicateFunction_Init (BPredicateFunction *o, BPredicate *p, char *name, int *args, int num_args, BPredicate_callback callback, void *user)
{
    ASSERT(strlen(name) <= PREDICATE_MAX_NAME)
    ASSERT(!BAVL_LookupExact(&p->functions_tree, name))
    ASSERT(num_args >= 0)
    ASSERT(num_args <= PREDICATE_MAX_ARGS)
    for (int i = 0; i < num_args; i++) {
        ASSERT(args[i] == PREDICATE_TYPE_BOOL || args[i] == PREDICATE_TYPE_STRING)
    }
    ASSERT(!p->in_function)
    
    // init arguments
    o->p = p;
    strcpy(o->name, name);
    memcpy(o->args, args, num_args * sizeof(int));
    o->num_args = num_args;
    o->callback = callback;
    o->user = user;
    
    // add to tree
    ASSERT_EXECUTE(BAVL_Insert(&p->functions_tree, &o->tree_node, NULL))
    
    // init debug object
    DebugObject_Init(&o->d_obj);
}

void BPredicateFunction_Free (BPredicateFunction *o)
{
    ASSERT(!o->p->in_function)
    
    BPredicate *p = o->p;
    
    // free debug object
    DebugObject_Free(&o->d_obj);
    
    // remove from tree
    BAVL_Remove(&p->functions_tree, &o->tree_node);
}
