/*
 * Copyright (C) 2003 Robert Kooima
 *
 * NEVERBALL 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.
 */

#include <SDL.h>

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

#include "config.h"
#include "common.h"
#include "fs.h"

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

/* Integer options. */

int CONFIG_FULLSCREEN;
int CONFIG_DISPLAY;
int CONFIG_WIDTH;
int CONFIG_HEIGHT;
int CONFIG_STEREO;
int CONFIG_CAMERA;
int CONFIG_TEXTURES;
int CONFIG_REFLECTION;
int CONFIG_MULTISAMPLE;
int CONFIG_MIPMAP;
int CONFIG_ANISO;
int CONFIG_BACKGROUND;
int CONFIG_SHADOW;
int CONFIG_AUDIO_BUFF;
int CONFIG_MOUSE_SENSE;
int CONFIG_MOUSE_RESPONSE;
int CONFIG_MOUSE_INVERT;
int CONFIG_VSYNC;
int CONFIG_HMD;
int CONFIG_HIGHDPI;
int CONFIG_MOUSE_CAMERA_1;
int CONFIG_MOUSE_CAMERA_2;
int CONFIG_MOUSE_CAMERA_3;
int CONFIG_MOUSE_CAMERA_TOGGLE;
int CONFIG_MOUSE_CAMERA_L;
int CONFIG_MOUSE_CAMERA_R;
int CONFIG_NICE;
int CONFIG_FPS;
int CONFIG_SOUND_VOLUME;
int CONFIG_MUSIC_VOLUME;
int CONFIG_JOYSTICK;
int CONFIG_JOYSTICK_DEVICE;
int CONFIG_JOYSTICK_RESPONSE;
int CONFIG_JOYSTICK_AXIS_X0;
int CONFIG_JOYSTICK_AXIS_Y0;
int CONFIG_JOYSTICK_AXIS_X1;
int CONFIG_JOYSTICK_AXIS_Y1;
int CONFIG_JOYSTICK_AXIS_X0_INVERT;
int CONFIG_JOYSTICK_AXIS_Y0_INVERT;
int CONFIG_JOYSTICK_AXIS_X1_INVERT;
int CONFIG_JOYSTICK_AXIS_Y1_INVERT;

int CONFIG_JOYSTICK_BUTTON_A;
int CONFIG_JOYSTICK_BUTTON_B;
int CONFIG_JOYSTICK_BUTTON_X;
int CONFIG_JOYSTICK_BUTTON_Y;
int CONFIG_JOYSTICK_BUTTON_L1;
int CONFIG_JOYSTICK_BUTTON_R1;
int CONFIG_JOYSTICK_BUTTON_L2;
int CONFIG_JOYSTICK_BUTTON_R2;
int CONFIG_JOYSTICK_BUTTON_START;
int CONFIG_JOYSTICK_DPAD_L;
int CONFIG_JOYSTICK_DPAD_R;
int CONFIG_JOYSTICK_DPAD_U;
int CONFIG_JOYSTICK_DPAD_D;

int CONFIG_KEY_CAMERA_1;
int CONFIG_KEY_CAMERA_2;
int CONFIG_KEY_CAMERA_3;
int CONFIG_KEY_CAMERA_TOGGLE;
int CONFIG_KEY_CAMERA_R;
int CONFIG_KEY_CAMERA_L;
int CONFIG_KEY_FORWARD;
int CONFIG_KEY_BACKWARD;
int CONFIG_KEY_LEFT;
int CONFIG_KEY_RIGHT;
int CONFIG_KEY_RESTART;
int CONFIG_KEY_SCORE_NEXT;
int CONFIG_KEY_ROTATE_FAST;
int CONFIG_VIEW_FOV;
int CONFIG_VIEW_DP;
int CONFIG_VIEW_DC;
int CONFIG_VIEW_DZ;
int CONFIG_ROTATE_FAST;
int CONFIG_ROTATE_SLOW;
int CONFIG_CHEAT;
int CONFIG_STATS;
int CONFIG_SCREENSHOT;
int CONFIG_LOCK_GOALS;
int CONFIG_CAMERA_1_SPEED;
int CONFIG_CAMERA_2_SPEED;
int CONFIG_CAMERA_3_SPEED;


/* String options. */

int CONFIG_PLAYER;
int CONFIG_BALL_FILE;
int CONFIG_WIIMOTE_ADDR;
int CONFIG_REPLAY_NAME;
int CONFIG_LANGUAGE;
int CONFIG_THEME;

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

static struct
{
    int        *sym;
    const char *name;
    const int   def;
    int         cur;
} option_d[] = {
    { &CONFIG_FULLSCREEN,   "fullscreen",   0 },
    { &CONFIG_DISPLAY,      "display",      0 },
    { &CONFIG_WIDTH,        "width",        800 },
    { &CONFIG_HEIGHT,       "height",       600 },
    { &CONFIG_STEREO,       "stereo",       0 },
    { &CONFIG_CAMERA,       "camera",       0 },
    { &CONFIG_TEXTURES,     "textures",     1 },
    { &CONFIG_REFLECTION,   "reflection",   1 },
    { &CONFIG_MULTISAMPLE,  "multisample",  0 },
    { &CONFIG_MIPMAP,       "mipmap",       1 },
    { &CONFIG_ANISO,        "aniso",        8 },
    { &CONFIG_BACKGROUND,   "background",   1 },
    { &CONFIG_SHADOW,       "shadow",       1 },
    { &CONFIG_AUDIO_BUFF,   "audio_buff",   AUDIO_BUFF_HI },
    { &CONFIG_MOUSE_SENSE,  "mouse_sense",  300 },
    { &CONFIG_MOUSE_RESPONSE, "mouse_response", 50 },
    { &CONFIG_MOUSE_INVERT, "mouse_invert", 0 },
    { &CONFIG_VSYNC,        "vsync",        1 },
    { &CONFIG_HMD,          "hmd",          0 },
    { &CONFIG_HIGHDPI,      "highdpi",      1 },

    { &CONFIG_MOUSE_CAMERA_1,      "mouse_camera_1",      0 },
    { &CONFIG_MOUSE_CAMERA_2,      "mouse_camera_2",      0 },
    { &CONFIG_MOUSE_CAMERA_3,      "mouse_camera_3",      0 },
    { &CONFIG_MOUSE_CAMERA_TOGGLE, "mouse_camera_toggle", SDL_BUTTON_MIDDLE },
    { &CONFIG_MOUSE_CAMERA_L,      "mouse_camera_l",      SDL_BUTTON_LEFT },
    { &CONFIG_MOUSE_CAMERA_R,      "mouse_camera_r",      SDL_BUTTON_RIGHT },

    { &CONFIG_NICE,         "nice",         0 },
    { &CONFIG_FPS,          "fps",          0 },
    { &CONFIG_SOUND_VOLUME, "sound_volume", 10 },
    { &CONFIG_MUSIC_VOLUME, "music_volume", 6 },

    { &CONFIG_JOYSTICK,                "joystick",                1 },
    { &CONFIG_JOYSTICK_DEVICE,         "joystick_device",         0 },
    { &CONFIG_JOYSTICK_RESPONSE,       "joystick_response",       250 },
    { &CONFIG_JOYSTICK_AXIS_X0,        "joystick_axis_x0",        0 },
    { &CONFIG_JOYSTICK_AXIS_Y0,        "joystick_axis_y0",        1 },
    { &CONFIG_JOYSTICK_AXIS_X1,        "joystick_axis_x1",        2 },
    { &CONFIG_JOYSTICK_AXIS_Y1,        "joystick_axis_y1",        3 },
    { &CONFIG_JOYSTICK_AXIS_X0_INVERT, "joystick_axis_x0_invert", 0 },
    { &CONFIG_JOYSTICK_AXIS_Y0_INVERT, "joystick_axis_y0_invert", 0 },
    { &CONFIG_JOYSTICK_AXIS_X1_INVERT, "joystick_axis_x1_invert", 0 },
    { &CONFIG_JOYSTICK_AXIS_Y1_INVERT, "joystick_axis_y1_invert", 0 },

    { &CONFIG_JOYSTICK_BUTTON_A,      "joystick_button_a",      0 },
    { &CONFIG_JOYSTICK_BUTTON_B,      "joystick_button_b",      1 },
    { &CONFIG_JOYSTICK_BUTTON_X,      "joystick_button_x",      2 },
    { &CONFIG_JOYSTICK_BUTTON_Y,      "joystick_button_y",      3 },
    { &CONFIG_JOYSTICK_BUTTON_L1,     "joystick_button_l1",     4 },
    { &CONFIG_JOYSTICK_BUTTON_R1,     "joystick_button_r1",     5 },
    { &CONFIG_JOYSTICK_BUTTON_L2,     "joystick_button_l2",     6 },
    { &CONFIG_JOYSTICK_BUTTON_R2,     "joystick_button_r2",     7 },
    { &CONFIG_JOYSTICK_BUTTON_START,  "joystick_button_start",  8 },
    { &CONFIG_JOYSTICK_DPAD_L,        "joystick_dpad_l",       -1 },
    { &CONFIG_JOYSTICK_DPAD_R,        "joystick_dpad_r",       -1 },
    { &CONFIG_JOYSTICK_DPAD_U,        "joystick_dpad_u",       -1 },
    { &CONFIG_JOYSTICK_DPAD_D,        "joystick_dpad_d",       -1 },

    { &CONFIG_KEY_CAMERA_1,      "key_camera_1",      SDLK_1 },
    { &CONFIG_KEY_CAMERA_2,      "key_camera_2",      SDLK_2 },
    { &CONFIG_KEY_CAMERA_3,      "key_camera_3",      SDLK_3 },
    { &CONFIG_KEY_CAMERA_TOGGLE, "key_camera_toggle", SDLK_e },
    { &CONFIG_KEY_CAMERA_R,      "key_camera_r",      SDLK_d },
    { &CONFIG_KEY_CAMERA_L,      "key_camera_l",      SDLK_s },
    { &CONFIG_KEY_FORWARD,       "key_forward",       SDLK_UP },
    { &CONFIG_KEY_BACKWARD,      "key_backward",      SDLK_DOWN },
    { &CONFIG_KEY_LEFT,          "key_left",          SDLK_LEFT },
    { &CONFIG_KEY_RIGHT,         "key_right",         SDLK_RIGHT },
    { &CONFIG_KEY_RESTART,       "key_restart",       SDLK_r },
    { &CONFIG_KEY_SCORE_NEXT,    "key_score_next",    SDLK_TAB },
    { &CONFIG_KEY_ROTATE_FAST,   "key_rotate_fast",   SDLK_LSHIFT },

    { &CONFIG_VIEW_FOV,    "view_fov",    50 },
    { &CONFIG_VIEW_DP,     "view_dp",     75 },
    { &CONFIG_VIEW_DC,     "view_dc",     25 },
    { &CONFIG_VIEW_DZ,     "view_dz",     200 },
    { &CONFIG_ROTATE_FAST, "rotate_fast", 300 },
    { &CONFIG_ROTATE_SLOW, "rotate_slow", 150 },
    { &CONFIG_CHEAT,       "cheat",       0 },
    { &CONFIG_STATS,       "stats",       0 },
    { &CONFIG_SCREENSHOT,  "screenshot",  0 },
    { &CONFIG_LOCK_GOALS,  "lock_goals",  1 },

    { &CONFIG_CAMERA_1_SPEED, "camera_1_speed", 250 },
    { &CONFIG_CAMERA_2_SPEED, "camera_2_speed", 0 },
    { &CONFIG_CAMERA_3_SPEED, "camera_3_speed", -1 },
};

static struct
{
    int        *sym;
    const char *name;
    const char *def;
    char       *cur;
} option_s[] = {
    { &CONFIG_PLAYER,       "player",       "" },
    { &CONFIG_BALL_FILE,    "ball_file",    "ball/basic-ball/basic-ball" },
    { &CONFIG_WIIMOTE_ADDR, "wiimote_addr", "" },
    { &CONFIG_REPLAY_NAME,  "replay_name",  "%s-%l" },
    { &CONFIG_LANGUAGE,     "language",     "" },
    { &CONFIG_THEME,        "theme",        "classic" }
};

static int dirty = 0;

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

static void config_key(const char *s, int i)
{
    SDL_Keycode c = SDL_GetKeyFromName(s);

    if (c == SDLK_UNKNOWN)
        config_set_d(i, option_d[i].def);
    else
        config_set_d(i, c);
}

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

static void config_mouse(const char *s, int i)
{
    if      (strcmp(s, "none") == 0)
        config_set_d(i, 0);
    else if (strcmp(s, "left") == 0)
        config_set_d(i, SDL_BUTTON_LEFT);
    else if (strcmp(s, "right") == 0)
        config_set_d(i, SDL_BUTTON_RIGHT);
    else if (strcmp(s, "middle") == 0)
        config_set_d(i, SDL_BUTTON_MIDDLE);
    else
        config_set_d(i, atoi(s));
}

static const char *config_mouse_name(int b)
{
    static char buff[sizeof ("256")];

    sprintf(buff, "%d", b);

    switch (b)
    {
    case 0:                    return "none";      break;
    case SDL_BUTTON_LEFT:      return "left";      break;
    case SDL_BUTTON_RIGHT:     return "right";     break;
    case SDL_BUTTON_MIDDLE:    return "middle";    break;
    default:                   return buff;        break;
    }
}

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

void config_init(void)
{
    int i;

    /*
     * Store index of each option in its associated config symbol and
     * initialise current values with defaults.
     */

    for (i = 0; i < ARRAYSIZE(option_d); i++)
    {
        *option_d[i].sym = i;
        config_set_d(i, option_d[i].def);
    }

    for (i = 0; i < ARRAYSIZE(option_s); i++)
    {
        *option_s[i].sym = i;
        config_set_s(i, option_s[i].def);
    }
}

/*
 * Scan an option string and store pointers to the start of key and
 * value at the passed-in locations.  No memory is allocated to store
 * the strings; instead, the option string is modified in-place as
 * needed.  Return 1 on success, 0 on error.
 */
static int scan_key_and_value(char **dst_key, char **dst_val, char *line)
{
    if (line)
    {
        int ks, ke, vs;

        ks = -1;
        ke = -1;
        vs = -1;

        sscanf(line, " %n%*s%n %n", &ks, &ke, &vs);

        if (ks < 0 || ke < 0 || vs < 0)
            return 0;

        if (vs - ke < 1)
            return 0;

        line[ke] = 0;

        *dst_key = line + ks;
        *dst_val = line + vs;

        return 1;
    }

    return 0;
}

void config_load(void)
{
    fs_file fh;

    SDL_assert(SDL_WasInit(SDL_INIT_VIDEO));

    if ((fh = fs_open_read(USER_CONFIG_FILE)))
    {
        char *line, *key, *val;

        while (read_line(&line, fh))
        {
            if (scan_key_and_value(&key, &val, line))
            {
                int i;

                /* Look up an integer option by that name. */

                for (i = 0; i < ARRAYSIZE(option_d); i++)
                {
                    if (strcmp(key, option_d[i].name) == 0)
                    {
                        /* Translate some strings to integers. */

                        if (i == CONFIG_MOUSE_CAMERA_1      ||
                            i == CONFIG_MOUSE_CAMERA_2      ||
                            i == CONFIG_MOUSE_CAMERA_3      ||
                            i == CONFIG_MOUSE_CAMERA_TOGGLE ||
                            i == CONFIG_MOUSE_CAMERA_L      ||
                            i == CONFIG_MOUSE_CAMERA_R)
                        {
                            config_mouse(val, i);
                        }
                        else if (i == CONFIG_KEY_FORWARD       ||
                                 i == CONFIG_KEY_BACKWARD      ||
                                 i == CONFIG_KEY_LEFT          ||
                                 i == CONFIG_KEY_RIGHT         ||
                                 i == CONFIG_KEY_CAMERA_1      ||
                                 i == CONFIG_KEY_CAMERA_2      ||
                                 i == CONFIG_KEY_CAMERA_3      ||
                                 i == CONFIG_KEY_CAMERA_TOGGLE ||
                                 i == CONFIG_KEY_CAMERA_R      ||
                                 i == CONFIG_KEY_CAMERA_L      ||
                                 i == CONFIG_KEY_RESTART       ||
                                 i == CONFIG_KEY_SCORE_NEXT    ||
                                 i == CONFIG_KEY_ROTATE_FAST)
                        {
                            config_key(val, i);
                        }
                        else
                            config_set_d(i, atoi(val));

                        /* Stop looking. */

                        break;
                    }
                }

                /* Look up a string option by that name.*/

                for (i = 0; i < ARRAYSIZE(option_s); i++)
                {
                    if (strcmp(key, option_s[i].name) == 0)
                    {
                        config_set_s(i, val);
                        break;
                    }
                }
            }
            free(line);
        }
        fs_close(fh);

        dirty = 0;
    }
}

void config_save(void)
{
    fs_file fh;

    SDL_assert(SDL_WasInit(SDL_INIT_VIDEO));

    if (dirty && (fh = fs_open_write(USER_CONFIG_FILE)))
    {
        int i;

        /* Write out integer options. */

        for (i = 0; i < ARRAYSIZE(option_d); i++)
        {
            const char *s = NULL;

            /* Translate some integers to strings. */

            if (i == CONFIG_MOUSE_CAMERA_1      ||
                i == CONFIG_MOUSE_CAMERA_2      ||
                i == CONFIG_MOUSE_CAMERA_3      ||
                i == CONFIG_MOUSE_CAMERA_TOGGLE ||
                i == CONFIG_MOUSE_CAMERA_L      ||
                i == CONFIG_MOUSE_CAMERA_R)
            {
                s = config_mouse_name(option_d[i].cur);
            }
            else if (i == CONFIG_KEY_FORWARD       ||
                     i == CONFIG_KEY_BACKWARD      ||
                     i == CONFIG_KEY_LEFT          ||
                     i == CONFIG_KEY_RIGHT         ||
                     i == CONFIG_KEY_CAMERA_1      ||
                     i == CONFIG_KEY_CAMERA_2      ||
                     i == CONFIG_KEY_CAMERA_3      ||
                     i == CONFIG_KEY_CAMERA_TOGGLE ||
                     i == CONFIG_KEY_CAMERA_R      ||
                     i == CONFIG_KEY_CAMERA_L      ||
                     i == CONFIG_KEY_RESTART       ||
                     i == CONFIG_KEY_SCORE_NEXT    ||
                     i == CONFIG_KEY_ROTATE_FAST)
            {
                s = SDL_GetKeyName((SDL_Keycode) option_d[i].cur);
            }
            else if (i == CONFIG_CHEAT)
            {
                if (!config_cheat())
                    continue;
            }

            if (s)
                fs_printf(fh, "%-25s %s\n", option_d[i].name, s);
            else
                fs_printf(fh, "%-25s %d\n", option_d[i].name, option_d[i].cur);
        }

        /* Write out string options. */

        for (i = 0; i < ARRAYSIZE(option_s); i++)
            fs_printf(fh, "%-25s %s\n", option_s[i].name, option_s[i].cur);

        fs_close(fh);
    }

    dirty = 0;
}

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

void config_set_d(int i, int d)
{
    option_d[i].cur = d;
    dirty = 1;
}

void config_tgl_d(int i)
{
    option_d[i].cur = (option_d[i].cur ? 0 : 1);
    dirty = 1;
}

int config_tst_d(int i, int d)
{
    return (option_d[i].cur == d) ? 1 : 0;
}

int config_get_d(int i)
{
    return option_d[i].cur;
}

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

void config_set_s(int i, const char *src)
{
    if (option_s[i].cur)
        free(option_s[i].cur);

    option_s[i].cur = strdup(src);

    dirty = 1;
}

const char *config_get_s(int i)
{
    return option_s[i].cur;
}

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

int config_cheat(void)
{
    return config_get_d(CONFIG_CHEAT);
}

void config_set_cheat(void)
{
    config_set_d(CONFIG_CHEAT, 1);
}

void config_clr_cheat(void)
{
    config_set_d(CONFIG_CHEAT, 0);
}

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

int config_screenshot(void)
{
    return ++option_d[CONFIG_SCREENSHOT].cur;
}

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