/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2007-2009 Joshua Simmons <mud at majidejima dot com>
 *
 * 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 "goban.h"
#include "sgf_parse.h"
#include "sgf.h"
#include "sgf_storage.h"
#include "util.h"
#include "board.h"
#include "game.h"

static void handle_prop_value (enum prop_type_t type);
static int read_prop_value (char *buffer, size_t buffer_size);
static void do_range (enum prop_type_t type, unsigned short ul,
                      unsigned short br);
static void parse_prop (void);
static void parse_node (void);
static enum prop_type_t parse_prop_type (void);

static unsigned short sgf_to_pos (char *buffer);

bool
parse_sgf (const char *filename)
{
    int saved = current_node;

    /* for parsing */
    int first_handle = 0;       /* first node in the branch */
    int file_position = 0;

    int temp;

    close_file (&sgf_fd);

    sgf_fd = rb->open (filename, O_RDONLY);

    if (sgf_fd < 0)
    {
        return false;
    }

    current_node = start_node;

    if (current_node < 0)
    {
        current_node = saved;
        return false;
    }

    empty_stack (&parse_stack);

    /* seek to the first '(' */
    while (peek_char_no_whitespace (sgf_fd) != '(')
    {
        if (read_char_no_whitespace (sgf_fd) == -1)
        {
            DEBUGF ("end of file or error before we found a '('\n");
            current_node = saved;
            return false;
        }
    }

    push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR));
    push_int_stack (&parse_stack, current_node);

    while (pop_int_stack (&parse_stack, &first_handle) &&
           pop_int_stack (&parse_stack, &file_position))
    {
        /* DEBUGF("poped off %d\n", file_position); */

        rb->yield ();

        current_node = first_handle;

        if (file_position == -1)
        {
            temp = read_char_no_whitespace (sgf_fd);
            if (temp != '(')
            {
                /* we're here because there may have been a sibling after
                   another gametree that was handled, but there's no '(',
                   so there wasnt' a sibling, so just go on to any more
                   gametrees in the stack */
                continue;
            }
            else
            {
                /* there may be more siblings after we process this one */
                push_int_stack (&parse_stack, -1);
                push_int_stack (&parse_stack, first_handle);
            }
        }
        else
        {
            /* check for a sibling after we finish with this node */
            push_int_stack (&parse_stack, -1);
            push_int_stack (&parse_stack, first_handle);

            rb->lseek (sgf_fd, file_position, SEEK_SET);


            /* we're at the start of a gametree here, right at the '(' */
            temp = read_char_no_whitespace (sgf_fd);

            if (temp != '(')
            {
                DEBUGF ("start of gametree doesn't have a '('!\n");
                current_node = saved;
                return false;
            }
        }

        while (1)
        {
            temp = peek_char_no_whitespace (sgf_fd);
            /* DEBUGF("||| %d, %c\n", absolute_position(), (char) temp); */

            if (temp == ';')
            {
                /* fill the tree_head node before moving on */
                if (current_node != tree_head ||
                    get_node (current_node)->props >= 0)
                {
                    int temp = add_child_sgf (NULL);

                    if (temp >= 0)
                    {
                        current_node = temp;
                    }
                    else
                    {
                        rb->splash (2 * HZ, "Out of memory while parsing!");
                        return false;
                    }
                }


                read_char_no_whitespace (sgf_fd);
                parse_node ();
            }
            else if (temp == ')')
            {
                /* finished this gametree */

                /* we want to end one past the ')', so eat it up: */
                read_char_no_whitespace (sgf_fd);
                break;
            }
            else if (temp == '(')
            {
                /*
                   DEBUGF ("adding %d\n", (int) rb->lseek (sgf_fd, 0,
                   SEEK_CUR)); */
                push_int_stack (&parse_stack, rb->lseek (sgf_fd, 0, SEEK_CUR));
                push_int_stack (&parse_stack, current_node);

                break;
            }
            else if (temp == -1)
            {
                break;
            }
            else
            {
                DEBUGF ("extra characters found while parsing: %c\n", temp);
                /* skip the extras i guess */
                read_char_no_whitespace (sgf_fd);
            }
        }
    }

    current_node = get_node (tree_head)->next;
    while (current_node >= 0 && get_node (current_node)->props < 0)
    {
        temp = current_node;    /* to be freed later */

        /* update the ->prev pointed on all branches of the next node */
        current_node = get_node (current_node)->next;
        /* DEBUGF("trying to set prev for branch %d\n", current_node); */
        if (current_node >= 0)
        {
            get_node (current_node)->prev = tree_head;

            struct prop_t *loop_prop =
                get_prop (get_node (current_node)->props);

            while (loop_prop != 0)
            {
                if (loop_prop->type == PROP_VARIATION)
                {
                    get_node (loop_prop->data.number)->prev = tree_head;
                }
                else
                {
                    /* all of the variations have to be up front, so we
                       can quit here */
                    break;
                }
                loop_prop = get_prop (loop_prop->next);
            }
        }

        /* update the tree head */
        get_node (tree_head)->next = get_node (temp)->next;
        /* DEBUGF("freeing %d %d %d\n", temp, start_node, saved); */
        if (start_node == temp || saved == temp)
        {
            start_node = saved = tree_head;
        }
        free_storage_sgf (temp);

        current_node = get_node (tree_head)->next;
    }

    current_node = saved;


    /* DEBUGF("got past!\n"); */
    close_file (&sgf_fd);
    return true;
}


static void
parse_node (void)
{
    int temp;

    while (1)
    {
        temp = peek_char_no_whitespace (sgf_fd);

        if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
        {
            return;
        }
        else
        {
            parse_prop ();
        }
    }
}



int start_of_prop = 0;
static void
parse_prop (void)
{
    enum prop_type_t temp_type = PROP_INVALID;
    int temp;


    while (1)
    {
        temp = peek_char_no_whitespace (sgf_fd);

        if (temp == -1 || temp == ')' || temp == '(' || temp == ';')
        {
            return;
        }
        else if (temp == '[')
        {
            handle_prop_value (temp_type);
        }
        else
        {
            start_of_prop = rb->lseek (sgf_fd, 0, SEEK_CUR);
            temp_type = parse_prop_type ();
        }
    }
}

static enum prop_type_t
parse_prop_type (void)
{
    char buffer[3];
    int pos = 0;
    int temp;

    rb->memset (buffer, 0, sizeof (buffer));

    while (1)
    {
        temp = peek_char_no_whitespace (sgf_fd);

        if (temp == ';' || temp == '[' || temp == '(' ||
            temp == -1 || temp == ')')
        {
            if (pos == 1 || pos == 2)
            {
                break;
            }
            else
            {
                return PROP_INVALID;
            }
        }
        else if (temp >= 'A' && temp <= 'Z')
        {
            buffer[pos++] = temp;

            if (pos == 2)
            {
                read_char_no_whitespace (sgf_fd);
                break;
            }
        }

        temp = read_char_no_whitespace (sgf_fd);
    }

    /* check if we're still reading a prop name, in which case we fail
       (but first we want to eat up the rest of the prop name) */
    bool failed = false;
    while (peek_char_no_whitespace (sgf_fd) != ';' &&
           peek_char_no_whitespace (sgf_fd) != '[' &&
           peek_char_no_whitespace (sgf_fd) != '(' &&
           peek_char_no_whitespace (sgf_fd) != '}' &&
           peek_char_no_whitespace (sgf_fd) != -1)
    {
        failed = true;
        read_char_no_whitespace (sgf_fd);
    }

    if (failed)
    {
        return PROP_INVALID;
    }

    int i;
    for (i = 0; i < PROP_NAMES_SIZE; ++i)
    {
        if (rb->strcmp (buffer, prop_names[i]) == 0)
        {
            return (enum prop_type_t) i;
        }
    }
    return PROP_INVALID;
}

static int
read_prop_value (char *buffer, size_t buffer_size)
{
    bool escaped = false;
    int temp;
    int bytes_read = 0;

    /* make it a string, the lazy way */
    rb->memset (buffer, 0, buffer_size);
    --buffer_size;

    if (peek_char (sgf_fd) == '[')
    {
        read_char (sgf_fd);
    }

    while (1)
    {
        temp = read_char (sgf_fd);
        if (temp == ']' && !escaped)
        {
            return bytes_read;
        }
        else if (temp == '\\')
        {
            if (escaped)
            {
                if (buffer && buffer_size)
                {
                    *(buffer++) = temp;
                    ++bytes_read;
                    --buffer_size;
                }
            }
            escaped = !escaped;
        }
        else if (temp == -1)
        {
            return bytes_read;
        }
        else
        {
            escaped = false;
            if (buffer && buffer_size)
            {
                *(buffer++) = temp;
                ++bytes_read;
                --buffer_size;
            }
        }
    }
}

static void
handle_prop_value (enum prop_type_t type)
{
    /* max size of generically supported prop values is 6, which is 5 for
       a point range ab:cd and one for the \0

       (this buffer is only used for them, things such as white and black
       player names are stored in different buffers) */

    /* make it a little bigger for other random crap, like reading in time
     */
#define PROP_HANDLER_BUFFER_SIZE 16

    char real_buffer[PROP_HANDLER_BUFFER_SIZE];
    char *buffer = real_buffer;

    int temp;
    union prop_data_t temp_data;
    bool in_prop_value = false;
    bool escaped = false;
    bool done = false;
    int temp_width, temp_height;
    unsigned short temp_pos_ul, temp_pos_br;
    int temp_size;
    char *temp_buffer;
    bool got_value;

    /* special extra handling for root properties, set a marker telling us
       the right place to spit the values out in output_sgf */
    if (type == PROP_GAME ||
        type == PROP_APPLICATION ||
        type == PROP_CHARSET ||
        type == PROP_SIZE ||
        type == PROP_FILE_FORMAT || type == PROP_VARIATION_TYPE)
    {
        header_marked = true;

        temp_data.number = 0;   /* meaningless */

        /* don't add more than one, so just set it if we found one already
         */
        add_or_set_prop_sgf (current_node, PROP_ROOT_PROPS, temp_data);
    }


    if (!is_handled_sgf (type) || type == PROP_COMMENT)
    {
        /* DEBUGF("unhandled prop %d\n", (int) type); */
        rb->lseek (sgf_fd, start_of_prop, SEEK_SET);

        temp_data.number = rb->lseek (unhandled_fd, 0, SEEK_CUR);
        /* absolute_position(&unhandled_prop_list); */

        add_prop_sgf (current_node,
                      type == PROP_COMMENT ? PROP_COMMENT :
                      PROP_GENERIC_UNHANDLED, temp_data);

        got_value = false;
        while (!done)
        {
            temp = peek_char (sgf_fd);

            switch (temp)
            {
            case -1:
                done = true;
                break;

            case '\\':
                if (got_value && !in_prop_value)
                {
                    done = true;
                }
                escaped = !escaped;
                break;
            case '[':
                escaped = false;
                in_prop_value = true;
                got_value = true;
                break;
            case ']':
                if (!escaped)
                {
                    in_prop_value = false;
                }
                escaped = false;
                break;
            case ')':
            case '(':
            case ';':
                if (!in_prop_value)
                {
                    done = true;
                }
                escaped = false;
                break;
            default:
                if (got_value && !in_prop_value)
                {
                    if (!is_whitespace (temp))
                    {
                        done = true;
                    }
                }
                escaped = false;
                break;
            };

            if (done)
            {
                write_char (unhandled_fd, ';');
            }
            else
            {
                /* don't write out-of-prop whitespace */
                if (in_prop_value || !is_whitespace (temp))
                {
                    write_char (unhandled_fd, (char) temp);
                }

                read_char (sgf_fd);
            }
        }


        return;
    }
    else if (type == PROP_BLACK_MOVE || type == PROP_WHITE_MOVE)
    {
        /* DEBUGF("move prop %d\n", (int) type); */

        temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);

        temp_data.position = INVALID_POS;

        /* empty is apparently acceptable as a pass */
        if (temp == 0)
        {
            temp_data.position = PASS_POS;
        }
        else if (temp == 2)
        {
            temp_data.position = sgf_to_pos (buffer);
        }
        else
        {
            DEBUGF ("invalid move position read in, of wrong size!\n");
        }


        if (temp_data.position != INVALID_POS)
        {
            add_prop_sgf (current_node, type, temp_data);
        }

        return;
    }
    else if (type == PROP_ADD_BLACK ||
             type == PROP_ADD_WHITE ||
             type == PROP_ADD_EMPTY ||
             type == PROP_CIRCLE ||
             type == PROP_SQUARE ||
             type == PROP_TRIANGLE ||
             type == PROP_DIM || type == PROP_MARK || type == PROP_SELECTED)
    {
        /* DEBUGF("add prop %d\n", (int) type); */

        temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
        if (temp == 2)
        {
            temp_data.position = sgf_to_pos (buffer);

            if (temp_data.position != INVALID_POS &&
                temp_data.position != PASS_POS)
            {
                add_prop_sgf (current_node, type, temp_data);
            }
        }
        else if (temp == 5)
        {
            /* example: "ab:cd", two positions separated by a colon */
            temp_pos_ul = sgf_to_pos (buffer);
            temp_pos_br = sgf_to_pos (&(buffer[3]));

            if (!on_board (temp_pos_ul) || !on_board (temp_pos_br) ||
                buffer[2] != ':')
            {
                DEBUGF ("invalid range value!\n");
            }

            do_range (type, temp_pos_ul, temp_pos_br);
        }
        else
        {
            DEBUGF ("invalid position or range read in. wrong size!\n");
        }
        return;
    }
    else if (type == PROP_LABEL)
    {
        temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);

        if (temp < 4 || buffer[2] != ':')
        {
            DEBUGF ("invalid LaBel property '%s'", buffer);
        }
        temp_data.position = sgf_to_pos (buffer);

        if (!on_board (temp_data.position))
        {
            DEBUGF ("LaBel set on invalid position!\n");
        }

        temp_data.label_extra = buffer[3];

        add_prop_sgf (current_node, type, temp_data);
        return;
    }
    else if (type == PROP_GAME)
    {
        temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
        if (temp != 1 || buffer[0] != '1')
        {
            rb->splash (2 * HZ, "This isn't a Go SGF!!  Parsing stopped.");
            DEBUGF ("incorrect game type loaded!\n");

            close_file (&sgf_fd);
        }
    }
    else if (type == PROP_FILE_FORMAT)
    {
        temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
        if (temp != 1 || (buffer[0] != '3' && buffer[0] != '4'))
        {
            rb->splash (2 * HZ, "Wrong SGF file version! Parsing stopped.");
            DEBUGF ("can't handle file format %c\n", buffer[0]);

            close_file (&sgf_fd);
        }
    }
    else if (type == PROP_APPLICATION ||
             type == PROP_CHARSET || type == PROP_VARIATION_TYPE)
    {
        /* we don't care.  on output we'll write our own values for these */
        read_prop_value (NULL, 0);
    }
    else if (type == PROP_SIZE)
    {
        temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
        if (temp == 0)
        {
            rb->splash (HZ, "Invalid board size specified in file.");
        }
        else
        {
            temp_width = rb->atoi (buffer);
            while (*buffer != ':' && *buffer != '\0')
            {
                ++buffer;
            }

            if (*buffer != '\0')
            {
                ++buffer;
                temp_height = rb->atoi (buffer);
            }
            else
            {
                temp_height = temp_width;
            }


            if (!set_size_board (temp_width, temp_height))
            {
                rb->splashf (HZ,
                             "Board too big/small! (%dx%d) Stopping parse.",
                             temp_width, temp_height);
                close_file (&sgf_fd);
            }
            else
            {
                clear_board ();
            }
        }
    }
    else if (type == PROP_KOMI)
    {
        temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);

        if (temp == 0)
        {
            header.komi = 0;
            DEBUGF ("invalid komi specification. setting to zero\n");
        }
        else
        {
            header.komi = rb->atoi (buffer) << 1;
            while (*buffer != '.' && *buffer != ',' && *buffer != '\0')
            {
                ++buffer;
            }

            if (*buffer != '\0')
            {
                ++buffer;

                if (*buffer == 0)
                {
                    /* do nothing */
                }
                else if (*buffer >= '1' && *buffer <= '9')
                {
                    header.komi += 1;
                }
                else
                {
                    if (*buffer != '0')
                    {
                        DEBUGF ("extra characters after komi value!\n");
                    }
                }
            }
        }
    }
    else if (type == PROP_BLACK_NAME ||
             type == PROP_WHITE_NAME ||
             type == PROP_BLACK_RANK ||
             type == PROP_WHITE_RANK ||
             type == PROP_BLACK_TEAM ||
             type == PROP_WHITE_TEAM ||
             type == PROP_DATE ||
             type == PROP_ROUND ||
             type == PROP_EVENT ||
             type == PROP_PLACE ||
             type == PROP_OVERTIME ||
             type == PROP_RESULT || type == PROP_RULESET)
    {
        if (!get_header_string_and_size
            (&header, type, &temp_buffer, &temp_size))
        {
            rb->splash (5 * HZ,
                        "Error getting header string.  Report this.");
        }
        else
        {
            temp = read_prop_value (temp_buffer, temp_size - 1);
#if 0
            DEBUGF ("read %d bytes into header for type: %d\n", temp, type);
            DEBUGF ("data: %s\n", temp_buffer);
#endif
        }
    }
    else if (type == PROP_TIME_LIMIT)
    {
        temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
        header.time_limit = rb->atoi (buffer);
        DEBUGF ("setting time: %d (%s)\n", header.time_limit, buffer);
    }
    else if (type == PROP_HANDICAP)
    {
        temp = read_prop_value (buffer, PROP_HANDLER_BUFFER_SIZE);
        if (start_node == tree_head)
        {
            if (rb->atoi (buffer) >= 2)
            {
                start_node = current_node;
                temp_data.number = header.handicap = rb->atoi (buffer);
                add_prop_sgf (current_node, type, temp_data);
                DEBUGF ("setting handicap: %d\n", header.handicap);
            }
            else
            {
                DEBUGF ("invalid HAndicap prop.  ignoring\n");
            }
        }
        else
        {
            rb->splash (HZ, "extraneous HAndicap prop present in file!\n");
        }
    }
    else
    {
        DEBUGF ("UNHANDLED PROP TYPE!!!\n");
        rb->splash (3 * HZ,
                    "A SGF prop was not dealt with.  Please report this");
        read_prop_value (NULL, 0);
    }
}



/* upper-left and bottom right */
static void
do_range (enum prop_type_t type, unsigned short ul, unsigned short br)
{
    /* this code is overly general and accepts ranges even if ul and br
       aren't the required corners it's easier doing that that failing if
       the input is bad */

    bool x_reverse = false;
    bool y_reverse = false;
    union prop_data_t temp_data;

    if (I (br) < I (ul))
    {
        x_reverse = true;
    }

    if (J (br) < J (ul))
    {
        y_reverse = true;
    }

    int x, y;
    for (x = I (ul);
         x_reverse ? (x >= I (br)) : (x <= I (br)); x_reverse ? --x : ++x)
    {
        for (y = J (ul);
             y_reverse ? (y >= J (br)) : (y <= J (br)); y_reverse ? --y : ++y)
        {
            temp_data.position = POS (x, y);

            DEBUGF ("adding %d %d for range (type %d)\n",
                    I (temp_data.position), J (temp_data.position), type);
            add_prop_sgf (current_node, type, temp_data);
        }
    }
}



static unsigned short
sgf_to_pos (char *buffer)
{
    if (buffer[0] == 't' && buffer[1] == 't')
    {
        return PASS_POS;
    }
    else if (buffer[0] < 'a' || buffer[0] > 'z' ||
             buffer[1] < 'a' || buffer[1] > 'z')
    {
        return INVALID_POS;
    }
    return POS (buffer[0] - 'a', buffer[1] - 'a');
}

