#include <string.h>
#include <stdio.h>
#include <time.h>
#include "course_table.h"
#include "pc/ini.h"

#define FILENAME_FORMAT "save_file_%d.sav"
#define NUM_COURSES 15
#define NUM_BONUS_COURSES 10
#define NUM_FLAGS 21
#define NUM_CAP_ON 4

/* Flag keys */
const char *sav_flags[NUM_FLAGS] = {
    "file_exists", "wing_cap", "metal_cap", "vanish_cap", "key_1", "key_2",
    "basement_door", "upstairs_door", "ddd_moved_back", "moat_drained",
    "pps_door", "wf_door", "ccm_door", "jrb_door", "bitdw_door",
    "bitfs_door", "", "", "", "", "50star_door"
};

/* Main course keys */
const char *sav_courses[NUM_COURSES] = {
    "bob", "wf", "jrb", "ccm", "bbh", "hmc", "lll",
    "ssl", "ddd", "sl", "wdw", "ttm", "thi", "ttc", "rr"
};

/* Bonus courses keys (including Castle Course) */
const char *sav_bonus_courses[NUM_BONUS_COURSES] = {
    "hub", "bitdw", "bitfs", "bits", "pss", "cotmc",
    "totwc", "vcutm", "wmotr", "sa",
};

/* Mario's cap type keys */
const char *cap_on_types[NUM_CAP_ON] = {
    "ground", "klepto", "ukiki", "mrblizzard"
};

/* Sound modes */
const char *sound_modes[3] = {
    "stereo", "mono", "headset"
};

/* Get current timestamp */
static void get_timestamp(char* buffer) {
    time_t timer;
    struct tm* tm_info;

    timer = time(NULL);
    tm_info = localtime(&timer);

    strftime(buffer, 26, "%Y-%m-%d %H:%M:%S", tm_info);
}

/* Convert 'binary' integer to decimal integer */
static u32 bin_to_int(u32 n) {
    s32 dec = 0, i = 0, rem;
    while (n != 0) {
        rem = n % 10;
        n /= 10;
        dec += rem * (1 << i);
        ++i;
    }
    return dec;
}

/* Convert decimal integer to 'binary' integer */
static u32 int_to_bin(u32 n) {
    s32 bin = 0, rem, i = 1;
    while (n != 0) {
        rem = n % 2;
        n /= 2;
        bin += rem * i;
        i *= 10;
    }
    return bin;
}

/**
 * Write SaveFile and MainMenuSaveData structs to a text-based savefile.
 */
static s32 write_text_save(s32 fileIndex) {
    FILE* file;
    struct SaveFile *savedata;
    struct MainMenuSaveData *menudata;
    char filename[32] = { 0 };
    char value[32] = { 0 };
    u32 i, bit, flags, coins, stars, starFlags;

    /* Define savefile's name */
    if (sprintf(filename, FILENAME_FORMAT, fileIndex) < 0)
        return -1;

    file = fopen(filename, "wt");
    if (file == NULL)
        return -1;
    else
        printf("Updating savefile in '%s'\n", filename);

    /* Write header */
    fprintf(file, "# Super Mario 64 save file\n");
    fprintf(file, "# Comment starts with #\n");
    fprintf(file, "# True = 1, False = 0\n");

    /* Write current timestamp */
    get_timestamp(value);
    fprintf(file, "# %s\n", value);

    /* Write MainMenuSaveData info */
    menudata = &gSaveBuffer.menuData[0];
    fprintf(file, "\n[menu]\n");
    fprintf(file, "coin_score_age = %d\n", menudata->coinScoreAges[fileIndex]);
    
    /* Sound mode */
    if (menudata->soundMode == 0) {
        fprintf(file, "sound_mode = %s\n", sound_modes[0]);     // stereo
    }
    else if (menudata->soundMode == 3) {
        fprintf(file, "sound_mode = %s\n", sound_modes[1]);     // mono
    }
    else if (menudata->soundMode == 1) {
        fprintf(file, "sound_mode = %s\n", sound_modes[2]);     // headset
    }
    else {
        printf("Undefined sound mode!");
        return -1;
    }
    
    /* Write all flags */
    fprintf(file, "\n[flags]\n");
    for (i = 1; i < NUM_FLAGS; i++) {
        if (strcmp(sav_flags[i], "")) {
            flags = save_file_get_flags();
            flags = (flags & (1 << i));      /* Get a specific bit */
            flags = (flags) ? 1 : 0;         /* Determine if bit is set or not */

            fprintf(file, "%s = %d\n", sav_flags[i], flags);
        }
    }

    /* Write coin count and star flags from each course (except Castle Grounds) */
    fprintf(file, "\n[courses]\n");
    for (i = 0; i < NUM_COURSES; i++) {
        stars = save_file_get_star_flags(fileIndex, i);
        coins = save_file_get_course_coin_score(fileIndex, i);
        starFlags = int_to_bin(stars);          /* 63 -> 111111 */
            
        fprintf(file, "%s = \"%d, %07d\"\n", sav_courses[i], coins, starFlags);
    }

    /* Write star flags from each bonus cource (including Castle Grounds) */
    fprintf(file, "\n[bonus]\n");
    for (i = 0; i < NUM_BONUS_COURSES; i++) {
        if (i == 0) {
            stars = save_file_get_star_flags(fileIndex, -1);
        } else {
            stars = save_file_get_star_flags(fileIndex, i+15);
        }
        starFlags = int_to_bin(stars);

        fprintf(file, "%s = %d\n", sav_bonus_courses[i], starFlags);
    }

    /* Write who steal Mario's cap */
    fprintf(file, "\n[cap]\n");
    for (i = 0; i < NUM_CAP_ON; i++) {
        flags = save_file_get_flags();      // Read all flags
        bit = (1 << (i+16));                // Determine current flag
        flags = (flags & bit);              // Get `cap` flag
        flags = (flags) ? 1 : 0;            // Determine if bit is set or not
        if (flags) {
            fprintf(file, "type = %s\n", cap_on_types[i]);
            break;
        }
    }

    /* Write in what course and area Mario losted its cap, and cap's position */
    savedata = &gSaveBuffer.files[fileIndex][0];
    switch(savedata->capLevel) {
        case COURSE_SSL:
            fprintf(file, "level = %s\n", "ssl");
            break;
        case COURSE_SL:
            fprintf(file, "level = %s\n", "sl");
            break;
        case COURSE_TTM:
            fprintf(file, "level = %s\n", "ttm");
            break;
        default:
            fprintf(file, "level = %s\n", "none");
            break;
    }
    fprintf(file, "area = %d\n", savedata->capArea);

    fclose(file);
    return 1;
}

/**
 * Read gSaveBuffer data from a text-based savefile.
 */
static s32 read_text_save(s32 fileIndex) {
    char filename[32] = { 0 };
    char temp[32] = { 0 };
    const char *value;
    ini_t *savedata;
    
    u32 i, flag, coins, stars, starFlags;
    u32 capArea;
    
    /* Define savefile's name */
    if (sprintf(filename, FILENAME_FORMAT, fileIndex) < 0)
        return -1;

    /* Try to open the file */
    savedata = ini_load(filename);
    if (savedata == NULL) {
        return -1;
    } else {
        printf("Loading savefile from '%s'\n", filename);
    }

    /* Read coin score age for selected file and sound mode */
    ini_sget(savedata, "menu", "coin_score_age", "%d",
                &gSaveBuffer.menuData[0].coinScoreAges[fileIndex]);
    
    value = ini_get(savedata, "menu", "sound_mode");
    if (value) {
        if (strcmp(value, sound_modes[0]) == 0) {
            gSaveBuffer.menuData[0].soundMode = 0;  // stereo
        }
        else if (strcmp(value, sound_modes[1]) == 0) {
            gSaveBuffer.menuData[0].soundMode = 3;  // mono
        }
        else if (strcmp(value, sound_modes[2]) == 0) {
            gSaveBuffer.menuData[0].soundMode = 1;  // headset
        }
    }
    else {
        printf("Invalid 'menu:sound_mode' flag!\n");
        return -1;
    }
    
    
    /* Parse main flags */
    for (i = 1; i < NUM_FLAGS; i++) {
        value = ini_get(savedata, "flags", sav_flags[i]);
        
        if (value) {
            flag = strtol(value, &temp, 10);
            if (flag) {
                flag = 1 << i;  /* Look #define in header.. */
                gSaveBuffer.files[fileIndex][0].flags |= flag;
            }
        }
    }

    /* Parse coin and star values for each main course */
    for (i = 0; i < NUM_COURSES; i++) {
        value = ini_get(savedata, "courses", sav_courses[i]);
        if (value) {
            sscanf(value, "%d, %d", &coins, &stars); 
            starFlags = bin_to_int(stars);  /* 111111 -> 63 */

            save_file_set_star_flags(fileIndex, i, starFlags);
            gSaveBuffer.files[fileIndex][0].courseCoinScores[i] = coins;
        }
    }

    /* Parse star values for each bonus course */
    for (i = 0; i < NUM_BONUS_COURSES; i++) {
        value = ini_get(savedata, "bonus", sav_bonus_courses[i]);
        if (value) {
            sscanf(value, "%d", &stars);
            starFlags = bin_to_int(stars);

            if (strlen(value) == 5) {
                /* Process Castle Grounds */
                save_file_set_star_flags(fileIndex, -1, starFlags);
            } else if (strlen(value) == 2) {
                /* Process Princess's Secret Slide */
                save_file_set_star_flags(fileIndex, COURSE_PSS, starFlags);
            } else {
                /* Process another shitty bonus course */
                save_file_set_star_flags(fileIndex, i+15, starFlags);
            }
        }
    }

    /* Find, who steal Mario's cap ... */
    for (i = 0; i < NUM_CAP_ON; i++) {
        value = ini_get(savedata, "cap", "type");
        if (value) {
            if (!strcmp(value, cap_on_types[i])) {
                flag = (1 << (16 + i));
                gSaveBuffer.files[fileIndex][0].flags |= flag;
                break;
            }
        }
    }
    
    /* ... it's level ... */
    value = ini_get(savedata, "cap", "level");
    if (value) {
        if (strcmp(value, "ssl") == 0) {
            gSaveBuffer.files[fileIndex][0].capLevel = 8; // ssl
        }
        else if (strcmp(value, "sl") == 0) {
            gSaveBuffer.files[fileIndex][0].capLevel = 10; // sl
        }
        else if (strcmp(value, "ttm") == 0) {
            gSaveBuffer.files[fileIndex][0].capLevel = 12; // ttm
        }
        else if (strcmp(value, "none") == 0) {
            gSaveBuffer.files[fileIndex][0].capLevel = 0;
        }
        else {
            printf("Invalid 'cap:level' flag!\n");
            return -1;
        }
    }
    
    /* ... and it's area */
    value = ini_get(savedata, "cap", "area");
    if (value) {
        sscanf(value, "%d", &capArea);
        if (capArea > 1 && capArea < 2) {
            printf("Invalid 'cap:area' flag: %d!\n", capArea);
            return -1;
        }
        else {
            gSaveBuffer.files[fileIndex][0].capArea = capArea; 
        }
    }
    
    /* Good, file exists for gSaveBuffer */
    gSaveBuffer.files[fileIndex][0].flags |= SAVE_FLAG_FILE_EXISTS;

    /* Make a backup */
    bcopy(&gSaveBuffer.files[fileIndex][0], &gSaveBuffer.files[fileIndex][1],
          sizeof(gSaveBuffer.files[fileIndex][1]));
    
    /* Cleaning up after ourselves */
    ini_free(savedata);
    return 0;
}
