/*
 * $Id: internal.c 44 2006-08-25 19:38:03Z kulibali $
 *
 * Copyright (c) 2006, The Narwhal Project 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *    * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *    * 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.
 *
 *    * Neither the name of the Narwhal Project 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 COPYRIGHT OWNER OR CONTRIBUTORS 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 "internal.h"
#include "peg_parser.h"

#include <assert.h>
#include <stdlib.h>
#include <malloc.h>
#include <wchar.h>
#include <wctype.h>

/*************************************************/

/**
 * Stores information about a rule call, for convenience in checking invalid identifiers.
 */
typedef struct _rule_call
{
    wchar_t *rule_name;
    syntax_node *node;
    rule_exp *exp;
}
rule_call;

/**
 * Stores context information for assembling rules.
 */

typedef struct _rule_context
{
    input_buffer *ib;

    dynarray *rule_records; /* array of rule_rec * */
    dynarray *rule_calls;   /* array of rule_call */
}
rule_context;

/*************************************************/

static void collect_rule_name(rule_rec *rec, syntax_node *node, input_buffer *ib)
{
    syntax_node **cur;
    int i, len;

    assert(node->children);

        len = 0;
        for (cur = node->children; *cur; ++cur)
            ++len;

        // get name
        if (len)
        {
            syntax_node *id_node = node->children[0];

            if (id_node->type == PEG_IDENT_NODE)
            {
                dynarray name;
                array_init(&name, sizeof(wchar_t), 0);
                input_buffer_read_string(ib, id_node->begin, id_node->end, &name);
                rec->rule_name = wcsdup(name.data);
                wcs_trim(&rec->rule_name);
                array_deinit(&name);
            }
        }

        // get description, if any
        if (len > 1)
        {
            for (i = 0; i < len; ++i)
            {
                syntax_node *child = node->children[i];
                if (child->type == PEG_LITERAL_NODE)
                {
                    dynarray desc;
                    array_init(&desc, sizeof(wchar_t), 0);
                    input_buffer_read_string(ib, child->begin, child->end, &desc);
                    rec->rule_desc = wcsdup(desc.data);
                    wcs_trim(&rec->rule_desc);
                    array_deinit(&desc);

                    break;
                }
            }
        }
} /* collect_rule_name() */


static rule_exp *collect_rule_exp(syntax_node *node, rule_context *context);

static rule_exp *collect_rule_children(syntax_node **children, int exp_type, rule_context *context)
{
    syntax_node **child;
    rule_exp *first = 0;
    rule_exp **cur = &first;
    int i, len;

    assert(children);

    len = 0;
        for (child = children; *child; ++child)
            ++len;

        if (!len)
            return 0;

        for (i = 0; i < len-1; ++i)
        {
            syntax_node *child = children[i];

            if (child->type != PEG_SLASH_NODE)
            {
                *cur = (rule_exp *) calloc(1, sizeof(rule_exp));
                (*cur)->type = exp_type;
                (*cur)->left = collect_rule_exp(child, context);
                cur = &(*cur)->right;
            }
        }

        *cur = collect_rule_exp(children[len-1], context);

    return first;
} /* collect_rule_children() */


static rule_exp *collect_prefix_exp(syntax_node **children, rule_context *context)
{
    syntax_node **cur;
    int len;

    assert(children);

    len = 0;
        for (cur = children; *cur; ++cur)
            ++len;

        if (len == 2)
        {
            int rule_type = 0;
            syntax_node *pref = children[0];

            if (pref->type == PEG_AND_NODE)
                rule_type = RULE_EXP_AMP;
            else if (pref->type == PEG_NOT_NODE)
                rule_type = RULE_EXP_BANG;

            if (rule_type)
            {
                rule_exp *exp = (rule_exp *) calloc(1, sizeof(rule_exp));
                exp->type = rule_type;
                exp->left = collect_rule_exp(children[1], context);
                return exp;
            }
        }
        else if (len == 1)
        {
            return collect_rule_exp(children[0], context);
        }

    /* can't happen */
    assert(0);
    return 0;
} /* collect_prefix_exp() */


static rule_exp *collect_suffix_exp(syntax_node **children, rule_context *context)
{
    syntax_node **cur;
    int len;

    assert(children);

    len = 0;
        for (cur = children; *cur; ++cur)
            ++len;

        if (len == 2)
        {
            int rule_type = 0;
            syntax_node *op = children[1];

            if (op->type == PEG_QUESTION_NODE)
                rule_type = RULE_EXP_QUES;
            else if (op->type == PEG_STAR_NODE)
                rule_type = RULE_EXP_STAR;
            else if (op->type == PEG_PLUS_NODE)
                rule_type = RULE_EXP_PLUS;

            if (rule_type)
            {
                rule_exp *exp = (rule_exp *) calloc(1, sizeof(rule_exp));
                exp->type = rule_type;
                exp->left = collect_rule_exp(children[0], context);
                return exp;
            }
        }
        else if (len == 1)
        {
            return collect_rule_exp(children[0], context);
        }

    /* can't happen */
    assert(0);
    return 0;
} /* collect_suffix_exp() */


static rule_exp *collect_rule_call_exp(syntax_node *node, rule_context *context)
{
    rule_exp *exp;
    rule_call call;
    dynarray str;

    /* initialize exp */
    exp = (rule_exp *) calloc(1, sizeof(rule_exp));
    exp->type = RULE_EXP_CALL;

    /* get name */
    array_init(&str, sizeof(wchar_t), 0);
    input_buffer_read_string(context->ib, node->begin, node->end, &str);
    exp->data.str = wcsdup(str.data);
    wcs_trim(&exp->data.str);
    array_deinit(&str);

    /* record call */
    call.node = node;
    call.rule_name = exp->data.str;
    call.exp = exp;
    array_add(context->rule_calls, &call);

    /* return */
    return exp;
} /* collect_rule_call_exp() */


static rule_exp *collect_string_exp(syntax_node *node, rule_context *context)
{
    rule_exp *exp = 0;
    dynarray str;
    syntax_node *first, *last, *temp, **cur;
    int i, len;

    assert(node->children);

    /* initialize exp */
    exp = (rule_exp *) calloc(1, sizeof(rule_exp));
    exp->type = RULE_EXP_STR;

    /* ignore following space */
        len = 0;
        for (cur = node->children; *cur; ++cur)
            ++len;

        if (len)
        {
            first = node->children[0];
            last = first;
            for (i = 1; i < len; ++i)
            {
                temp = node->children[i];
                if (temp->type == PEG_CHAR_NODE)
                    last = temp;
                else
                    break;
            }

            /* get string */
            array_init(&str, sizeof(wchar_t), 0);
            input_buffer_read_string(context->ib, first->begin, last->end, &str);
            exp->data.str = wcsdup(str.data);
            array_deinit(&str);
        }

    return exp;
} /* collect_string_exp() */


static wchar_t get_escaped_char(dynarray *str, int index, int len, int *span)
{
    wchar_t ch = *(wchar_t *) array_item(str, index);
    *span = 1;

    if (ch == L'\\')
    {
        /* check for octal-coded characters */
        if (index < (len-3))
        {
            wchar_t d[3];

            d[0] = *(wchar_t *) array_item(str, index+1);
            d[1] = *(wchar_t *) array_item(str, index+2);
            d[2] = (index < (len+4)) ? *(wchar_t *) array_item(str, index+3) : 0;

            if (iswdigit(d[0]) && iswdigit(d[1]))
            {
                if (iswdigit(d[2]))
                {
                    *span = 4;
                    return (wchar_t) (((d[0] - L'0') * 64) + ((d[1] - L'0') * 8) + (d[2] - L'0'));
                }
                else
                {
                    *span = 3;
                    return (wchar_t) (((d[0] - L'0') * 8) + (d[1] - L'0'));
                }
            }
        }

        /* check for other escaped chars */
        if (index < (len-1))
        {
            wchar_t e = *(wchar_t *) array_item(str, index+1);

            switch (e)
            {
            case L'n':
                ch = L'\n';
                break;
            case L'r':
                ch = L'\r';
                break;
            case L't':
                ch = L'\t';
                break;
            default:
                ch = e;
                break;
            }

            *span = 2;
        }
    }

    return ch;
} /* get_escaped_char() */


static rule_exp *collect_class_exp(syntax_node *node, rule_context *context)
{
    rule_exp *exp = 0;
    dynarray str, cc;
    syntax_node *first, *last, *temp, **cur;
    int i, len;

    assert(node->children);

    /* initialize exp */
    exp = (rule_exp *) calloc(1, sizeof(rule_exp));
    exp->type = RULE_EXP_CLASS;

    /* get string */
    array_init(&str, sizeof(wchar_t), 0);

    len = 0;
    for (cur = node->children; *cur; ++cur)
        ++len;
    
    if (len)
    {
        first = node->children[0];
        last = first;

        for (i = 0; i < len; ++i)
        {
            temp = node->children[i];
            if (temp->type == PEG_RANGE_NODE)
                last = temp;
            else
                break;
        }

        input_buffer_read_string(context->ib, first->begin, last->end, &str);
    }

    /* assemble char class */
    array_init(&cc, sizeof(wchar_t), 0);
    
    if ((len = array_size(&str)))
    {
        int span = 1;

        for (i = 0; i < len; i += span)
        {
            wchar_t first = get_escaped_char(&str, i, len, &span);

            /* check for range */
            if (i < (len-span))
            {
                wchar_t dash = *(wchar_t *) array_item(&str, i+span);

                if (dash == L'-' && i < (len-(span+1)))
                {
                    int last_span = 1;
                    wchar_t cur, last = get_escaped_char(&str, i+span+1, len, &last_span);

                    if (first <= last)
                    {
                        for (cur = first; cur <= last; ++cur)
                            array_add(&cc, &cur);
                    }
                    else
                    {
                        array_add(&cc, &first);
                        array_add(&cc, &last);
                    }

                    span += last_span+1;
                    continue;
                }
            }

            /* otherwise, add char */
            array_add(&cc, &first);
        }
    }
    else
    {
        wchar_t ch = 0;
        array_add(&cc, &ch);
    }

    exp->data.str = wcsdup(cc.data);

    /* clean up and return */
    array_deinit(&cc);
    array_deinit(&str);

    return exp;
} /* collect_class_exp() */


static rule_exp *collect_term_exp(syntax_node **children, rule_context *context)
{
    syntax_node **cur;
    int len;

    assert(children);

    len = 0;
    for (cur = children; *cur; ++cur)
        ++len;

    if (len == 1)
    {
        syntax_node *node = children[0];

        switch (node->type)
        {
        case PEG_IDENT_NODE:
            return collect_rule_call_exp(node, context);
        case PEG_LITERAL_NODE:
            return collect_string_exp(node, context);
        case PEG_CLASS_NODE:
            return collect_class_exp(node, context);
        case PEG_DOT_NODE:
            {
                rule_exp *exp = (rule_exp *) calloc(1, sizeof(rule_exp));
                exp->type = RULE_EXP_DOT;
                return exp;
            }
        }
    }
    else if (len == 3)
    {
        syntax_node *disj = children[1];
        if (disj->type == PEG_DISJ_NODE)
            return collect_rule_exp(disj, context);
    }

    /* can't happen */
    assert(0);
    return 0;
} /* collect_term_exp() */


static rule_exp *collect_rule_exp(syntax_node *node, rule_context *context)
{
    assert(node);
    assert(context);

    switch (node->type)
    {
    case PEG_DISJ_NODE:
        return collect_rule_children(node->children, RULE_EXP_DISJ, context);
    case PEG_CONJ_NODE:
        return collect_rule_children(node->children, RULE_EXP_SEQ, context);
    case PEG_PREFIX_EXP_NODE:
        return collect_prefix_exp(node->children, context);
    case PEG_SUFFIX_EXP_NODE:
        return collect_suffix_exp(node->children, context);
    case PEG_TERM_NODE:
        return collect_term_exp(node->children, context);
    default:
        break;
    }

    /* can't happen */
    assert(0);
    return 0;
} /* collect_rule_exp() */


static int collect_rule_data(syntax_node *node, void *data)
{
    assert(node);
    assert(data);

    if (node->type == PEG_RULE_NODE)
    {
        rule_context *context = (rule_context *) data;
        rule_rec *rec = (rule_rec *) calloc(1, sizeof(rule_rec));
        syntax_node **cur;
       
        /* get rule name */
        rec->node = node;
        collect_rule_name(rec, node, context->ib);

        /* get rule expression */
        for (cur = node->children; *cur; ++cur)
        {
            syntax_node *child = *cur;
            if (child->type == PEG_DISJ_NODE)
            {
                rec->rule_spec = collect_rule_exp(child, context);
                break;
            }
        }

        /* add to the list of rules */
        array_add(context->rule_records, &rec);

        return 1;
    }
    else
    {
        return 0;
    }
} /* collect_rule_data() */


/*************************************************/

static void cleanup_rule_exp(rule_exp *exp)
{
    assert(exp);

    switch (exp->type)
    {
    case RULE_EXP_CALL:
    case RULE_EXP_STR:
        free(exp->data.str);
        break;
    }

    if (exp->left)
        cleanup_rule_exp(exp->left);
    if (exp->right)
        cleanup_rule_exp(exp->right);
    free(exp);
} /* cleanup_rule_exp() */


void cleanup_rule(rule_rec *rec)
{
    assert(rec);

    free(rec->rule_name);
    free(rec->rule_desc);

    if (rec->rule_spec)
        cleanup_rule_exp(rec->rule_spec);
} /* cleanup_rule() */

/*************************************************/

static void resolve_rule_calls(dynarray *rule_records, dynarray *rule_calls, dynarray *errors)
{
    int i, j, num_calls, num_rules;

    num_calls = array_size(rule_calls);
    num_rules = array_size(rule_records);

    /* go through the rule calls */
    for (i = 0; i < num_calls; ++i)
    {
        int found = 0;
        rule_rec *rec = 0;

        /* find in the rule definitions */
        rule_call *rc = (rule_call *) array_item(rule_calls, i);
        for (j = 0; j < num_rules; ++j)
        {
            rec = *(rule_rec **) array_item(rule_records, j);

            if (wcscmp(rc->rule_name, rec->rule_name) == 0)
            {
                found = 1;
                break;
            }
        }

        /* record error if not found */
        if (!found)
        {
            static wchar_t *msg = L"unknown grammar rule '%ls'";
            int len = (int) (wcslen(msg) + wcslen(rc->rule_name) + 32);
            wchar_t *buf = (wchar_t *) calloc(len+1, sizeof(wchar_t));
            swprintf(buf, len, msg, rc->rule_name);
            add_error(errors, rc->node->begin, buf);
            free(buf);
        }
    }
} /* resolve_rule_calls() */

/*************************************************/

static void print_rule_exp(rule_exp *exp)
{
    switch (exp->type)
    {
    case RULE_EXP_SEQ:
        fprintf(stdout, "SEQ(");
        print_rule_exp(exp->left);
        fprintf(stdout, ", ");
        print_rule_exp(exp->right);
        fprintf(stdout, ")");
        break;
    case RULE_EXP_DISJ:
        fprintf(stdout, "DISJ(");
        print_rule_exp(exp->left);
        fprintf(stdout, ", ");
        print_rule_exp(exp->right);
        fprintf(stdout, ")");
        break;
    case RULE_EXP_STAR:
        fprintf(stdout, "STAR(");
        print_rule_exp(exp->left);
        fprintf(stdout, ")");
        break;
    case RULE_EXP_PLUS:
        fprintf(stdout, "PLUS(");
        print_rule_exp(exp->left);
        fprintf(stdout, ")");
        break;
    case RULE_EXP_QUES:
        fprintf(stdout, "QUES(");
        print_rule_exp(exp->left);
        fprintf(stdout, ")");
        break;
    case RULE_EXP_BANG:
        fprintf(stdout, "BANG(");
        print_rule_exp(exp->left);
        fprintf(stdout, ")");
        break;
    case RULE_EXP_AMP:
        fprintf(stdout, "AMP(");
        print_rule_exp(exp->left);
        fprintf(stdout, ")");
        break;
    case RULE_EXP_CALL:
        fprintf(stdout, "T(%ls)", exp->data.str);
        break;
    case RULE_EXP_STR:
        fprintf(stdout, "S(\"%ls\")", exp->data.str);
        break;
    case RULE_EXP_DOT:
        fprintf(stdout, "DOT");
        break;
    case RULE_EXP_CLASS:
        fprintf(stdout, "C([%ls])", exp->data.str);
        break;
    }
} /* print_rule_exp() */

void print_rules(dynarray *rule_records)
{
    int i, len = array_size(rule_records);
    
    for (i = 0; i < len; ++i)
    {
        rule_rec *rec = *(rule_rec **) array_item(rule_records, i);

        /* name and description */
        fprintf(stdout, "rule '%ls'", rec->rule_name);
        if (rec->rule_desc && *rec->rule_desc)
            fprintf(stdout, " ('%ls')", rec->rule_desc);
        fprintf(stdout, ": ");

        if (rec->rule_spec)
            print_rule_exp(rec->rule_spec);

        /* end of line */
        fprintf(stdout, "\n");
    }
} /* print_rules() */

/*************************************************/

void get_internal_representation(syntax_node *parsed_spec, input_buffer *ib, dynarray *rule_records, dynarray *line_endings, dynarray *errors)
{
    dynarray rule_calls;   /* array of rule_call */
    rule_context context;

    array_init(&rule_calls, sizeof(rule_call), 0);

    /* get rule names and expressions */
    context.ib = ib;
    context.rule_records = rule_records;
    context.rule_calls = &rule_calls;
    syntax_node_traverse_inorder(parsed_spec, &context, collect_rule_data);

    /* error check */
    resolve_rule_calls(rule_records, &rule_calls, errors);

    /* clean up */
    array_deinit(&rule_calls);
} /* get_internal_representation() */
