/*
 * Schism Tracker - a cross-platform Impulse Tracker clone
 * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
 * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
 * copyright (c) 2009 Storlek & Mrs. Brisby
 * copyright (c) 2010-2012 Storlek
 * URL: http://schismtracker.org/
 *
 * 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 program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "headers.h"

#include "it.h"
#include "song.h"
#include "page.h"

#include "sdlmain.h"

/* --------------------------------------------------------------------- */

/* ENSURE_DIALOG(optional return value)
 * will emit a warning and cause the function to return
 * if a dialog is not active. */
#ifndef NDEBUG
# define ENSURE_DIALOG(q) do { if (!(status.dialog_type & DIALOG_BOX)) { \
                fprintf(stderr, "%s called with no dialog\n", __FUNCTION__);\
                q; \
        } \
} while(0)
#else
# define ENSURE_DIALOG(q)
#endif

/* --------------------------------------------------------------------- */
/* I'm only supporting four dialogs open at a time. This is an absurdly
 * large amount anyway, since the most that should ever be displayed is
 * two (in the case of a custom dialog with a thumbbar, the value prompt
 * dialog will be open on top of the other dialog). */

static struct dialog dialogs[4];
static int num_dialogs = 0;

/* --------------------------------------------------------------------- */

void dialog_draw(void)
{
        int n, d;

        for (d = 0; d < num_dialogs; d++) {
                n = dialogs[d].total_widgets;

                /* draw the border and background */
                draw_box(dialogs[d].x, dialogs[d].y,
                         dialogs[d].x + dialogs[d].w - 1,
                         dialogs[d].y + dialogs[d].h - 1, BOX_THICK | BOX_OUTER | BOX_FLAT_LIGHT);
                draw_fill_chars(dialogs[d].x + 1, dialogs[d].y + 1,
                                dialogs[d].x + dialogs[d].w - 2, dialogs[d].y + dialogs[d].h - 2, 2);

                /* then the rest of the stuff */
                if (dialogs[d].draw_const) dialogs[d].draw_const();

                if (dialogs[d].text)
                        draw_text(dialogs[d].text, dialogs[d].text_x, 27, 0, 2);

                n = dialogs[d].total_widgets;
                while (n) {
                        n--;
                        draw_widget(dialogs[d].widgets + n, n == dialogs[d].selected_widget);
                }
        }
}

/* --------------------------------------------------------------------- */

void dialog_destroy(void)
{
        int d;

        if (num_dialogs == 0)
                return;

        d = num_dialogs - 1;

        if (dialogs[d].type != DIALOG_CUSTOM) {
                free(dialogs[d].text);
                free(dialogs[d].widgets);
        }

        num_dialogs--;
        if (num_dialogs) {
                d--;
                widgets = dialogs[d].widgets;
                selected_widget = &(dialogs[d].selected_widget);
                total_widgets = &(dialogs[d].total_widgets);
                status.dialog_type = dialogs[d].type;
        } else {
                widgets = ACTIVE_PAGE.widgets;
                selected_widget = &(ACTIVE_PAGE.selected_widget);
                total_widgets = &(ACTIVE_PAGE.total_widgets);
                status.dialog_type = DIALOG_NONE;
        }

        /* it's up to the calling function to redraw the page */
}

void dialog_destroy_all(void)
{
        while (num_dialogs)
                dialog_destroy();
}

/* --------------------------------------------------------------------- */
/* default callbacks */

void dialog_yes(void *data)
{
        void (*action) (void *);

        ENSURE_DIALOG(return);

        action = dialogs[num_dialogs - 1].action_yes;
        if (!data) data = dialogs[num_dialogs - 1].data;
        dialog_destroy();
        if (action) action(data);
        status.flags |= NEED_UPDATE;
}

void dialog_no(void *data)
{
        void (*action) (void *);

        ENSURE_DIALOG(return);

        action = dialogs[num_dialogs - 1].action_no;
        if (!data) data = dialogs[num_dialogs - 1].data;
        dialog_destroy();
        if (action) action(data);
        status.flags |= NEED_UPDATE;
}

void dialog_cancel(void *data)
{
        void (*action) (void *);

        ENSURE_DIALOG(return);

        action = dialogs[num_dialogs - 1].action_cancel;
        if (!data) data = dialogs[num_dialogs - 1].data;
        dialog_destroy();
        if (action) action(data);
        status.flags |= NEED_UPDATE;
}

void dialog_yes_NULL(void)
{
        dialog_yes(NULL);
}
void dialog_no_NULL(void)
{
        dialog_no(NULL);
}
void dialog_cancel_NULL(void)
{
        dialog_cancel(NULL);
}

/* --------------------------------------------------------------------- */

int dialog_handle_key(struct key_event * k)
{
        struct dialog *d = dialogs + num_dialogs - 1;

        ENSURE_DIALOG(return 0);

        if (d->handle_key && d->handle_key(k))
                return 1;

        /* this SHOULD be handling on !k->state but the widget key handler is stealing that key. */
        if (k->state && NO_MODIFIER(k->mod)) {
                switch (k->sym) {
                case SDLK_y:
                        switch (status.dialog_type) {
                        case DIALOG_YES_NO:
                        case DIALOG_OK_CANCEL:
                                dialog_yes(d->data);
                                return 1;
                        default:
                                break;
                        }
                        break;
                case SDLK_n:
                        switch (status.dialog_type) {
                        case DIALOG_YES_NO:
                                /* in Impulse Tracker, 'n' means cancel, not "no"!
                                (results in different behavior on sample quality convert dialog) */
                                if (!(status.flags & CLASSIC_MODE)) {
                                        dialog_no(d->data);
                                        return 1;
                                } /* else fall through */
                        case DIALOG_OK_CANCEL:
                                dialog_cancel(d->data);
                                return 1;
                        default:
                                break;
                        }
                        break;
                case SDLK_c:
                        switch (status.dialog_type) {
                        case DIALOG_YES_NO:
                        case DIALOG_OK_CANCEL:
                                break;
                        default:
                                return 0;
                        } /* and fall through */
                case SDLK_ESCAPE:
                        dialog_cancel(d->data);
                        return 1;
                case SDLK_o:
                        switch (status.dialog_type) {
                        case DIALOG_YES_NO:
                        case DIALOG_OK_CANCEL:
                                break;
                        default:
                                return 0;
                        } /* and fall through */
                case SDLK_RETURN:
                        dialog_yes(d->data);
                        return 1;
                default:
                        break;
                }
        }

        return 0;
}

/* --------------------------------------------------------------------- */
/* these get called from dialog_create below */

static void dialog_create_ok(int textlen)
{
        int d = num_dialogs;

        /* make the dialog as wide as either the ok button or the text,
         * whichever is more */
        dialogs[d].text_x = 40 - (textlen / 2);
        if (textlen > 21) {
                dialogs[d].x = dialogs[d].text_x - 2;
                dialogs[d].w = textlen + 4;
        } else {
                dialogs[d].x = 26;
                dialogs[d].w = 29;
        }
        dialogs[d].h = 8;
        dialogs[d].y = 25;

        dialogs[d].widgets = (struct widget *)mem_alloc(sizeof(struct widget));
        dialogs[d].total_widgets = 1;

        create_button(dialogs[d].widgets + 0, 36, 30, 6, 0, 0, 0, 0, 0, dialog_yes_NULL, "OK", 3);
}

static void dialog_create_ok_cancel(int textlen)
{
        int d = num_dialogs;

        /* the ok/cancel buttons (with the borders and all) are 21 chars,
         * so if the text is shorter, it needs a bit of padding. */
        dialogs[d].text_x = 40 - (textlen / 2);
        if (textlen > 21) {
                dialogs[d].x = dialogs[d].text_x - 4;
                dialogs[d].w = textlen + 8;
        } else {
                dialogs[d].x = 26;
                dialogs[d].w = 29;
        }
        dialogs[d].h = 8;
        dialogs[d].y = 25;

        dialogs[d].widgets = calloc(2, sizeof(struct widget));
        dialogs[d].total_widgets = 2;

        create_button(dialogs[d].widgets + 0, 31, 30, 6, 0, 0, 1, 1, 1, dialog_yes_NULL, "OK", 3);
        create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_cancel_NULL, "Cancel", 1);
}

static void dialog_create_yes_no(int textlen)
{
        int d = num_dialogs;

        dialogs[d].text_x = 40 - (textlen / 2);
        if (textlen > 21) {
                dialogs[d].x = dialogs[d].text_x - 4;
                dialogs[d].w = textlen + 8;
        } else {
                dialogs[d].x = 26;
                dialogs[d].w = 29;
        }
        dialogs[d].h = 8;
        dialogs[d].y = 25;

        dialogs[d].widgets = calloc(2, sizeof(struct widget));
        dialogs[d].total_widgets = 2;

        create_button(dialogs[d].widgets + 0, 30, 30, 7, 0, 0, 1, 1, 1, dialog_yes_NULL, "Yes", 3);
        create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_no_NULL, "No", 3);
}

/* --------------------------------------------------------------------- */
/* type can be DIALOG_OK, DIALOG_OK_CANCEL, or DIALOG_YES_NO
 * default_widget: 0 = ok/yes, 1 = cancel/no */

struct dialog *dialog_create(int type, const char *text, void (*action_yes) (void *data),
                   void (*action_no) (void *data), int default_widget, void *data)
{
        int textlen = strlen(text);
        int d = num_dialogs;

#ifndef NDEBUG
        if ((type & DIALOG_BOX) == 0) {
                fprintf(stderr, "dialog_create called with bogus dialog type %d\n", type);
                return NULL;
        }
#endif

        /* FIXME | hmm... a menu should probably be hiding itself when a widget gets selected. */
        if (status.dialog_type & DIALOG_MENU)
                menu_hide();

        dialogs[d].text = str_dup(text);
        dialogs[d].data = data;
        dialogs[d].action_yes = action_yes;
        dialogs[d].action_no = action_no;
        dialogs[d].action_cancel = NULL;        /* ??? */
        dialogs[d].selected_widget = default_widget;
        dialogs[d].draw_const = NULL;
        dialogs[d].handle_key = NULL;

        switch (type) {
        case DIALOG_OK:
                dialog_create_ok(textlen);
                break;
        case DIALOG_OK_CANCEL:
                dialog_create_ok_cancel(textlen);
                break;
        case DIALOG_YES_NO:
                dialog_create_yes_no(textlen);
                break;
        default:
#ifndef NDEBUG
                fprintf(stderr, "this man should not be seen\n");
#endif
                type = DIALOG_OK_CANCEL;
                dialog_create_ok_cancel(textlen);
                break;
        }

        dialogs[d].type = type;
        widgets = dialogs[d].widgets;
        selected_widget = &(dialogs[d].selected_widget);
        total_widgets = &(dialogs[d].total_widgets);

        num_dialogs++;

        status.dialog_type = type;
        status.flags |= NEED_UPDATE;
        return &dialogs[d];
}

/* --------------------------------------------------------------------- */
/* this will probably die painfully if two threads try to make custom dialogs at the same time */

struct dialog *dialog_create_custom(int x, int y, int w, int h, struct widget *dialog_widgets,
                                    int dialog_total_widgets, int dialog_selected_widget,
                                    void (*draw_const) (void), void *data)
{
        struct dialog *d = dialogs + num_dialogs;

        /* FIXME | see dialog_create */
        if (status.dialog_type & DIALOG_MENU)
                menu_hide();

        num_dialogs++;

        d->type = DIALOG_CUSTOM;
        d->x = x;
        d->y = y;
        d->w = w;
        d->h = h;
        d->widgets = dialog_widgets;
        d->selected_widget = dialog_selected_widget;
        d->total_widgets = dialog_total_widgets;
        d->draw_const = draw_const;

        d->text = NULL;
        d->data = data;
        d->action_yes = NULL;
        d->action_no = NULL;
        d->action_cancel = NULL;
        d->handle_key = NULL;

        status.dialog_type = DIALOG_CUSTOM;
        widgets = d->widgets;
        selected_widget = &(d->selected_widget);
        total_widgets = &(d->total_widgets);

        status.flags |= NEED_UPDATE;

        return d;
}

/* --------------------------------------------------------------------- */
/* Other prompt stuff */

static const char *numprompt_title, *numprompt_secondary;
static int numprompt_smp_pos1, numprompt_smp_pos2; /* used by the sample prompt */
static int numprompt_titlelen; /* used by the number prompt */
static char numprompt_buf[4];
static struct widget numprompt_widgets[2];
static void (*numprompt_finish)(int n);

/* this is bound to the textentry's activate callback.
since this dialog might be called from another dialog as well as from a page, it can't use the
normal dialog_yes handler -- it needs to destroy the prompt dialog first so that ACTIVE_WIDGET
points to whatever thumbbar actually triggered the dialog box. */
static void numprompt_value(void)
{
        char *eptr;
        long n = strtol(numprompt_buf, &eptr, 10);

        dialog_destroy();
        if (eptr > numprompt_buf && eptr[0] == '\0')
                numprompt_finish(n);
}

static void numprompt_draw_const(void)
{
        int wx = numprompt_widgets[0].x;
        int wy = numprompt_widgets[0].y;
        int ww = numprompt_widgets[0].width;

        draw_text(numprompt_title, wx - numprompt_titlelen - 1, wy, 3, 2);
        draw_box(wx - 1, wy - 1, wx + ww, wy + 1, BOX_THICK | BOX_INNER | BOX_INSET);
}

void numprompt_create(const char *prompt, void (*finish)(int n), char initvalue)
{
        int y = 26; // an indisputable fact of life
        int dlgwidth, dlgx, entryx;

        numprompt_title = prompt;
        numprompt_titlelen = strlen(prompt);
        numprompt_buf[0] = initvalue;
        numprompt_buf[1] = '\0';

        /* Dialog is made up of border, padding (2 left, 1 right), frame around the text entry, the entry
        itself, and the prompt; the text entry is offset from the left of the dialog by 4 chars (padding +
        borders) plus the length of the prompt. */
        dlgwidth = 2 + 3 + 2 + 4 + numprompt_titlelen;
        dlgx = (80 - dlgwidth) / 2;
        entryx = dlgx + 4 + numprompt_titlelen;

        create_textentry(numprompt_widgets + 0, entryx, y, 4, 0, 0, 0, NULL, numprompt_buf, 3);
        numprompt_widgets[0].activate = numprompt_value;
        numprompt_widgets[0].d.textentry.cursor_pos = initvalue ? 1 : 0;
        numprompt_finish = finish;
        dialog_create_custom(dlgx, y - 2, dlgwidth, 5, numprompt_widgets, 1, 0, numprompt_draw_const, NULL);
}


static int strtonum99(const char *s)
{
        // aaarghhhh
        int n = 0;
        if (!s || !*s)
                return -1;
        if (s[1]) {
                // two chars
                int c = tolower(*s);
                switch (c) {
                        case '0' ... '9': n = c - '0'; break;
                        case 'a' ... 'g': n = c - 'a' + 10; break;
                        case 'h' ... 'z': n = c - 'h' + 10; break;
                        default: return -1;
                }
                n *= 10;
                s++;
        }
        return *s >= '0' && *s <= '9' ? n + *s - '0' : -1;
}

static void smpprompt_value(UNUSED void *data)
{
        int n = strtonum99(numprompt_buf);
        numprompt_finish(n);
}

static void smpprompt_draw_const(void)
{
        int wx = numprompt_widgets[0].x;
        int wy = numprompt_widgets[0].y;
        int ww = numprompt_widgets[0].width;

        draw_text(numprompt_title, numprompt_smp_pos1, 25, 0, 2);
        draw_text(numprompt_secondary, numprompt_smp_pos2, 27, 0, 2);
        draw_box(wx - 1, wy - 1, wx + ww, wy + 1, BOX_THICK | BOX_INNER | BOX_INSET);
}

void smpprompt_create(const char *title, const char *prompt, void (*finish)(int n))
{
        struct dialog *dialog;

        numprompt_title = title;
        numprompt_secondary = prompt;
        numprompt_smp_pos1 = (81 - strlen(title)) / 2;
        numprompt_smp_pos2 = 41 - strlen(prompt);
        numprompt_buf[0] = '\0';

        create_textentry(numprompt_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, numprompt_buf, 2);
        create_button(numprompt_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1);
        numprompt_finish = finish;
        dialog = dialog_create_custom(26, 23, 29, 10, numprompt_widgets, 2, 0, smpprompt_draw_const, NULL);
        dialog->action_yes = smpprompt_value;
}

