/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2010 Robert Bieber
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

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

#include "skin_parser.h"
#include "skin_debug.h"
#include "tag_table.h"

/* Global variables for debug output */
int debug_indent_level = 0;
extern int skin_line;
extern char* skin_start;
extern char* skin_buffer;

/* Global error variables */
static int error_line;
static int error_col;
static const char *error_line_start;
static char* error_message;


static inline struct skin_element*
get_child(OFFSETTYPE(struct skin_element**) children, int child)
{
    struct skin_element **kids = SKINOFFSETTOPTR(skin_buffer, children);
    return kids[child];
}

/* Debugging functions */
void skin_error(enum skin_errorcode error, const char* cursor)
{

    error_col = 0;

    while(cursor > skin_start && *cursor != '\n')
    {
        cursor--;
        error_col++;
    }
    error_line_start = cursor+1;

    error_line = skin_line;

    switch(error)
    {
    case MEMORY_LIMIT_EXCEEDED:
        error_line_start = NULL;
        printf("Error: Memory limit exceeded at Line %d\n", skin_line);
        error_message = "Memory limit exceeded";
        break;
    case NEWLINE_EXPECTED:
        error_message = "Newline expected";
        break;
    case ILLEGAL_TAG:
        error_message = "Illegal tag";
        break;
    case ARGLIST_EXPECTED:
        error_message = "Argument list expected";
        break;
    case TOO_MANY_ARGS:
        error_message =  "Too many arguments given";
        break;
    case DEFAULT_NOT_ALLOWED:
        error_message = "Argument can not be set to default";
        break;
    case UNEXPECTED_NEWLINE:
        error_message  = "Unexpected newline";
        break;
    case INSUFFICIENT_ARGS:
        error_message = "Not enough arguments";
        break;
    case INT_EXPECTED:
        error_message =  "Expected integer";
        break;
    case DECIMAL_EXPECTED:
        error_message =  "Expected decimal";
        break;
    case SEPARATOR_EXPECTED:
        error_message = "Expected argument separator";
        break;
    case CLOSE_EXPECTED:
        error_message = "Expected list close";
        break;
    case MULTILINE_EXPECTED:
        error_message = "Expected subline separator";
        break;
    case GOT_CALLBACK_ERROR:
        error_message = "Parser callback returned error";
        break;
    };

}

int skin_error_line()
{
    return error_line;
}

int skin_error_col()
{
    return error_col;
}

char* skin_error_message()
{
    return error_message;
}

void skin_clear_errors()
{
    error_line = 0;
    error_col = 0;
    error_message = NULL;
}

#if !defined(ROCKBOX) || defined(__PCTOOL__)
void skin_debug_tree(struct skin_element* root)
{
    int i;
    char *text;

    struct skin_element* current = root;

    while(current)
    {
        skin_debug_indent();

        switch(current->type)
        {
        case UNKNOWN:
            printf("* Unknown element.. error *\n");
            break;

        case VIEWPORT:
            printf("{ Viewport \n");

            debug_indent_level++;
            skin_debug_tree(get_child(current->children, 0));
            debug_indent_level--;

            printf("}");
            break;

        case TEXT:
            text = SKINOFFSETTOPTR(skin_buffer, current->data);
            printf("* Plain text on line %d: \"%s\"\n", current->line, text);
            break;

        case COMMENT:
            printf("# Comment on line %d\n ", current->line);
            break;

        case TAG:
            if (current->params_count)
            {
                printf("( %%%s tag on line %d with %d arguments\n",
                       current->tag->name,
                       current->line, current->params_count);
                debug_indent_level++;
                skin_debug_params(current->params_count, SKINOFFSETTOPTR(skin_buffer, current->params));
                debug_indent_level--;
                skin_debug_indent();
                printf(")\n");
            }
            else
            {
                printf("[ %%%s tag on line %d ]\n",
                       current->tag->name, current->line);
            }

            break;

        case LINE_ALTERNATOR:
            printf("[ Alternator on line %d with %d sublines \n", current->line,
                   current->children_count);
            debug_indent_level++;
            for(i = 0; i < current->children_count; i++)
            {
                skin_debug_tree(get_child(current->children, i));
            }
            debug_indent_level--;

            skin_debug_indent();
            printf("]\n");
            break;

        case CONDITIONAL:
            printf("< Conditional tag %%?%s on line %d with %d enumerations \n",
                   current->tag->name, current->line, current->children_count);
            debug_indent_level++;

            for(i = 0; i < current->children_count; i++)
            {
                skin_debug_indent();
                printf("[ Enumeration %d\n", i);
                debug_indent_level++;
                skin_debug_tree(get_child(current->children, i));
                debug_indent_level--;
                skin_debug_indent();
                printf("]\n");
            }

            debug_indent_level--;
            skin_debug_indent();
            printf(">\n");


            break;

        case LINE:
            printf("[ Logical line on line %d\n", current->line);

            debug_indent_level++;
            if (current->children)
                skin_debug_tree(get_child(current->children, 0));
            debug_indent_level--;

            skin_debug_indent();
            printf("]\n");
            break;
        }

        current = SKINOFFSETTOPTR(skin_buffer, current->next);
    }

}

void skin_debug_params(int count, struct skin_tag_parameter params[])
{
    int i;
    for(i = 0; i < count; i++)
    {

        skin_debug_indent();
        switch(params[i].type)
        {
        case DEFAULT:
            printf("-");
            break;

        case STRING:
            printf("string: \"%s\"", SKINOFFSETTOPTR(skin_buffer, params[i].data.text));
            break;

        case INTEGER:
            printf("integer: %d", params[i].data.number);
            break;
            
        case DECIMAL:
            printf("decimal: %d.%d", params[i].data.number/10,
                              params[i].data.number%10);
            break;

        case CODE:
            printf("Skin Code: \n");
            debug_indent_level++;
            skin_debug_tree(SKINOFFSETTOPTR(skin_buffer, params[i].data.code));
            debug_indent_level--;
            skin_debug_indent();
            break;
        }

        printf("\n");

    }
}

void skin_debug_indent(void)
{
    int i;
    for(i = 0; i < debug_indent_level; i++)
        printf("    ");
}

#endif

#define MIN(a,b) ((a<b)?(a):(b))
void skin_error_format_message(void)
{
    int i;
    char text[128];
    if (!error_line_start)
        return;
    char* line_end = strchr(error_line_start, '\n');
    int len = MIN(line_end - error_line_start, 80);
    if (!line_end)
        len = strlen(error_line_start);
    printf("Error on line %d.\n", error_line);
    error_col--;
    if (error_col <= 10)
    {
        strncpy(text, error_line_start, len);
        text[len] = '\0';
    }
    else
    {
        int j;
        /* make it fit nicely.. "<start few chars>...<10 chars><error>" */
        strncpy(text, error_line_start, 6);
        i = 5;
        text[i++] = '.';
        text[i++] = '.';
        text[i++] = '.';
        for (j=error_col-10; error_line_start[j] && error_line_start[j] != '\n'; j++)
            text[i++] = error_line_start[j];
        text[i] = '\0';
        error_col = 18;
    }
    printf("%s\n", text);
    for (i=0; i<error_col; i++)
        text[i] = ' ';
    snprintf(&text[i],64, "^ \'%s\' Here", error_message);
    printf("%s\n", text);
}
