/***************************************************************************
 *             __________               __   ___.
 *   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_output.h"
#include "sgf.h"
#include "sgf_storage.h"
#include "util.h"
#include "board.h"
#include "game.h"

static void pos_to_sgf (unsigned short pos, char *buffer);

static void output_prop (int prop_handle);
static void output_all_props (void);
static bool output_current_node (void);
static void output_gametree (void);
static void output_header_props (void);
static bool output_header_helper (enum prop_type_t type);
static int stupid_num_variations (void);

bool
output_sgf (const char *filename)
{
    int current = -1;
    union prop_data_t temp_data;
    int saved = current_node;

    sgf_fd = create_or_open_file (filename);

    if (sgf_fd < 0)
    {
        return false;
    }

    DEBUGF ("outputting to: %s (%d)\n", filename, sgf_fd);

    empty_stack (&parse_stack);

    rb->lseek (sgf_fd, 0, SEEK_SET);
    rb->ftruncate (sgf_fd, 0);

    if (sgf_fd < 0)
    {
        return false;
    }

    if (tree_head < 0)
    {
        close_file (&sgf_fd);
        return false;
    }

    push_int_stack (&parse_stack, tree_head);

    while (pop_int_stack (&parse_stack, &current))
    {
        int var_to_process = 0;
        int temp_prop =
            get_prop_sgf (current, PROP_VARIATION_TO_PROCESS, NULL);

        if (temp_prop >= 0)
        {
            var_to_process = get_prop (temp_prop)->data.number;
        }

        current_node = current;

        if (var_to_process > 0)
        {
            write_char (sgf_fd, ')');
        }

        if (var_to_process == stupid_num_variations ())
        {
            delete_prop_sgf (current, PROP_VARIATION_TO_PROCESS);

            continue;
        }
        else
        {
            write_char (sgf_fd, '\n');
            write_char (sgf_fd, '(');

            /* we need to do more processing on this branchpoint, either
               to do more variations or to output the ')' */
            push_int_stack (&parse_stack, current);

            /* increment the stored variation to process */
            temp_data.number = var_to_process + 1;
            add_or_set_prop_sgf (current,
                                 PROP_VARIATION_TO_PROCESS, temp_data);
        }

        rb->yield ();

        /* now we did the setup for sibling varaitions to be processed so
           do the actual outputting of a game tree branch */

        go_to_variation_sgf (var_to_process);
        output_gametree ();
    }

    current_node = saved;
    close_file (&sgf_fd);
    DEBUGF ("done outputting, file closed\n");
    return true;
}

static void
output_header_props (void)
{
    char buffer[128];

    rb->strlcpy (buffer, "GM[1]FF[4]CA[UTF-8]AP[Rockbox Goban:1.0]ST[2]\n\n",
                 sizeof (buffer));
    write_file (sgf_fd, buffer, rb->strlen (buffer));

    /* board size */
    if (board_width != board_height)
    {
        rb->snprintf (buffer, sizeof (buffer), "%s[%d:%d]",
                      prop_names[PROP_SIZE], board_width, board_height);
    }
    else
    {
        rb->snprintf (buffer, sizeof (buffer), "%s[%d]",
                      prop_names[PROP_SIZE], board_width);
    }

    write_file (sgf_fd, buffer, rb->strlen (buffer));

    rb->snprintf (buffer, sizeof (buffer), "%s[", prop_names[PROP_KOMI]);
    write_file (sgf_fd, buffer, rb->strlen (buffer));

    snprint_fixed (buffer, sizeof (buffer), header.komi);
    write_file (sgf_fd, buffer, rb->strlen (buffer));

    write_char (sgf_fd, ']');

    output_header_helper (PROP_RULESET);
    output_header_helper (PROP_RESULT);

    output_header_helper (PROP_BLACK_NAME);
    output_header_helper (PROP_WHITE_NAME);
    output_header_helper (PROP_BLACK_RANK);
    output_header_helper (PROP_WHITE_RANK);
    output_header_helper (PROP_BLACK_TEAM);
    output_header_helper (PROP_WHITE_TEAM);

    output_header_helper (PROP_EVENT);
    output_header_helper (PROP_PLACE);
    output_header_helper (PROP_DATE);

    if (output_header_helper (PROP_OVERTIME) || header.time_limit != 0)
    {
        rb->snprintf (buffer, sizeof (buffer), "%s[%d]",
                      prop_names[PROP_TIME_LIMIT], header.time_limit);
        write_file (sgf_fd, buffer, rb->strlen (buffer));
    }

    write_char (sgf_fd, '\n');
    write_char (sgf_fd, '\n');
}

static bool
output_header_helper (enum prop_type_t type)
{
    char *buffer;
    int size;
    char temp_buffer[16];

    if (!get_header_string_and_size (&header, type, &buffer, &size))
    {
        DEBUGF ("output_header_helper called with invalid prop type!!\n");
        return false;
    }

    if (rb->strlen (buffer))
    {
        rb->snprintf (temp_buffer, sizeof (temp_buffer), "%s[",
                      prop_names[type]);

        write_file (sgf_fd, temp_buffer, rb->strlen (temp_buffer));

        write_file (sgf_fd, buffer, rb->strlen (buffer));

        rb->strcpy (temp_buffer, "]");

        write_file (sgf_fd, temp_buffer, rb->strlen (temp_buffer));

        return true;
    }

    return false;
}

bool first_node_in_tree = true;
static void
output_gametree (void)
{
    first_node_in_tree = true;

    while (output_current_node ())
    {
        current_node = get_node (current_node)->next;
    }

}

static bool
output_current_node (void)
{
    if (current_node < 0)
    {
        return false;
    }

    if (stupid_num_variations () > 1 &&
        get_prop_sgf (current_node, PROP_VARIATION_TO_PROCESS, NULL) < 0)
    {
        /* push it up for the gametree stuff to take care of it and fail
           out, stopping the node printing */
        push_int_stack (&parse_stack, current_node);
        return false;
    }

    if (first_node_in_tree)
    {
        first_node_in_tree = false;
    }
    else
    {
        write_char (sgf_fd, '\n');
    }
    write_char (sgf_fd, ';');

    output_all_props ();

    return true;
}

enum prop_type_t last_output_type = PROP_INVALID;
static void
output_all_props (void)
{
    int temp_handle = get_node (current_node)->props;

    last_output_type = PROP_INVALID;

    while (temp_handle >= 0)
    {
        output_prop (temp_handle);
        temp_handle = get_prop (temp_handle)->next;
    }
}

static void
output_prop (int prop_handle)
{
    char buffer[16];
    enum prop_type_t temp_type = get_prop (prop_handle)->type;

    buffer[0] = 't';
    buffer[1] = 't';

    if (is_handled_sgf (temp_type) && temp_type != PROP_COMMENT)
    {
        if (temp_type != last_output_type)
        {
            write_file (sgf_fd, prop_names[temp_type],
                        PROP_NAME_LEN (temp_type));
        }

        write_char (sgf_fd, '[');

        if (temp_type == PROP_HANDICAP)
        {
            rb->snprintf (buffer, sizeof (buffer), "%d",
                          get_prop (prop_handle)->data.number);
            write_file (sgf_fd, buffer, rb->strlen (buffer));
        }
        else if (temp_type == PROP_LABEL)
        {
            pos_to_sgf (get_prop (prop_handle)->data.position, buffer);
            buffer[2] = '\0';

            rb->snprintf (&buffer[2], sizeof (buffer) - 2, ":%c",
                          get_prop (prop_handle)->data.label_extra);

            write_file (sgf_fd, buffer, rb->strlen (buffer));
        }
        else
        {
            pos_to_sgf (get_prop (prop_handle)->data.position, buffer);

            write_file (sgf_fd, buffer, 2);
        }

        write_char (sgf_fd, ']');
    }
    else if (temp_type == PROP_ROOT_PROPS)
    {
        output_header_props ();
    }
    else if (temp_type == PROP_GENERIC_UNHANDLED || temp_type == PROP_COMMENT)
    {
        bool escaped = false;
        bool in_prop_value = false;
        int temp;
        bool done = false;

        rb->lseek (unhandled_fd, get_prop (prop_handle)->data.number,
                   SEEK_SET);

        while (!done)
        {
            temp = peek_char (unhandled_fd);

            switch (temp)
            {
            case ';':
                escaped = false;
                if (in_prop_value)
                {
                    break;
                }
                /* otherwise, fall through */
            case -1:
                done = true;
                break;

            case '\\':
                escaped = !escaped;
                break;

            case '[':
                escaped = false;
                in_prop_value = true;
                break;

            case ']':
                if (!escaped)
                {
                    in_prop_value = false;
                }
                escaped = false;
                break;

            default:
                escaped = false;
                break;
            };

            if (!done)
            {
                write_char (sgf_fd, temp);
                read_char (unhandled_fd);
            }
        }
    }

    last_output_type = temp_type;
}

static void
pos_to_sgf (unsigned short pos, char *buffer)
{
    if (pos == PASS_POS)
    {
        /* "tt" is a pass per SGF specification */
        buffer[0] = buffer[1] = 't';
    }
    else if (pos != INVALID_POS)
    {
        buffer[0] = 'a' + I (pos);
        buffer[1] = 'a' + J (pos);
    }
    else
    {
        DEBUGF ("invalid pos converted to SGF\n");
    }
}

static int
stupid_num_variations (void)
{
    int result = 1;
    struct prop_t *temp_prop;
    struct node_t *temp_node = get_node (current_node);

    if (temp_node == 0)
    {
        return 0;
    }

    temp_prop = get_prop (temp_node->props);

    while (temp_prop)
    {
        if (temp_prop->type == PROP_VARIATION)
        {
            ++result;
        }
        else
        {
            /* variations are at the beginning of the prop list */
            break;
        }

        temp_prop = get_prop (temp_prop->next);
    }

    return result;
}
