#include "save.h"
    /* Savegame format:
     * a block in the center is at 22, 4, 22
     * positions are multipled by 24 to obtain in-game positions
     * An isometric block's origin is the bottom back corner, x then goes right
     * down, y goes up, z goes left down.
     * y
     * |
     * |
     * |
     * _-´ `-_
     * _-´       `-_
     * z               x
     * A block at 22/4/22 will end up at 0/0/0.
     * A block at 0/0/0 will end up at -528,-96,-528. (-22*24,-4*42,-22*24)
     * A level's floor typically is 40 x 40 in size.
     */
static SaveInfo g_save_info [50];
static void add(SaveInfo * si, float x, float y, float z, float xs, float ys, float zs);
void save_info(void) {
    Application * a = app();
    char * path = land_get_save_file(URL, "info.txt");
    LandFile * f = land_file_new(path, "w");
    config_print_controls(f);
    land_file_print(f, "room %d", game->level);
    land_file_print(f, "gox %d", game->gox);
    land_file_print(f, "goz %d", game->goz);
    land_file_print(f, "dpad %d", a->dpad);
    land_file_print(f, "fullscreen %d", a->fullscreen);
    land_file_print(f, "music %d", a->music);
    land_file_print(f, "sound %d", a->sound);
    land_file_print(f, "time %d", a->time);
    land_file_print(f, "key %d", game->key);
    land_file_print(f, "deaths %d", game->deaths);
    write_game_int(dialog.orchid);
    write_game_int(dialog.skunk);
    write_game_int(dialog.elephant);
    write_game_int(dialog.gnome);
    write_game_int(dialog.chameleon);
    write_game_int(enter_x);
    write_game_int(enter_y);
    write_game_int(enter_z);
    write_game_int(enter_x2);
    write_game_int(enter_y2);
    write_game_int(enter_z2);
    write_game_int(seen);
    bool * fl = game->flower;
    land_file_print(f, "flower %d %d %d %d %d %d %d", fl [1], fl [2], fl [3], fl [4], fl [5], fl [6], fl [7]);
    bool * tt = game->test_tube;
    land_file_print(f, "testtube %d %d %d %d %d %d %d", tt [1], tt [2], tt [3], tt [4], tt [5], tt [6], tt [7]);
    for (int i = 0; i < 30; i += 1) {
        land_file_print(f, "deaths.%d %d", i, game->deaths_per_minute [i]);
        land_file_print(f, "levels.%d %d", i, game->levels_in_minute [i]);
    }
    land_file_destroy(f);
    land_free(path);
}
void load_info(void) {
    Application * a = app();
    char * path = land_get_save_file(URL, "info.txt");
    game->level = game_starting_level;
    a->dpad = 0;
    a->music = 4;
    a->sound = 7;
    LandBuffer * f = land_buffer_read_from_file(path);
    if (f) {
        LandArray * rows = land_buffer_split(f, "\n");
        land_buffer_destroy(f);
        {
            LandArrayIterator __iter0__ = LandArrayIterator_first(rows);
            for (LandBuffer * rowb = LandArrayIterator_item(rows, &__iter0__); LandArrayIterator_next(rows, &__iter0__); rowb = LandArrayIterator_item(rows, &__iter0__)) {
                char * row = land_buffer_finish(rowb);
                config_read_controls(row);
                if (land_starts_with(row, "room ")) {
                    sscanf(row, "room %d", & game->level);
                }
                if (land_starts_with(row, "gox ")) {
                    sscanf(row, "gox %d", & game->gox);
                }
                if (land_starts_with(row, "goz ")) {
                    sscanf(row, "goz %d", & game->goz);
                }
                if (land_starts_with(row, "dpad ")) {
                    sscanf(row, "dpad %d", & a->dpad);
                }
                if (land_starts_with(row, "fullscreen ")) {
                    sscanf(row, "fullscreen %d", & a->fullscreen);
                }
                if (land_starts_with(row, "music ")) {
                    sscanf(row, "music %d", & a->music);
                }
                if (land_starts_with(row, "sound ")) {
                    sscanf(row, "sound %d", & a->sound);
                }
                if (land_starts_with(row, "time ")) {
                    int t;
                    sscanf(row, "time %d", & t);
                    if (t > a->time) {
                        a->time = t;
                    }
                }
                // If a room is reloaded after dying or pausing,
                // we want to keep the current play time which
                // will always be higher.
                // If the game restarts however we want to use
                // the saved time.
                if (land_starts_with(row, "key ")) {
                    int i;
                    sscanf(row, "key %d", & i);
                    if (i) {
                        game->key = 1;
                    }
                }
                if (land_starts_with(row, "deaths ")) {
                    sscanf(row, "deaths %d", & game->deaths);
                }
                read_game_int(dialog.orchid);
                read_game_int(dialog.skunk);
                read_game_int(dialog.elephant);
                read_game_int(dialog.gnome);
                read_game_int(dialog.chameleon);
                read_game_int(enter_x);
                read_game_int(enter_y);
                read_game_int(enter_z);
                read_game_int(enter_x2);
                read_game_int(enter_y2);
                read_game_int(enter_z2);
                read_game_int(seen);
                if (land_starts_with(row, "flower ")) {
                    int i [8];
                    i [0] = 0;
                    sscanf(row, "flower %d %d %d %d %d %d %d", i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7);
                    for (int j = 0; j < 8; j += 1) {
                        game->flower [j] = i [j];
                    }
                }
                if (land_starts_with(row, "testtube ")) {
                    int i [8];
                    i [0] = 0;
                    sscanf(row, "testtube %d %d %d %d %d %d %d", i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7);
                    for (int j = 0; j < 8; j += 1) {
                        game->test_tube [j] = i [j];
                    }
                }
                if (land_starts_with(row, "deaths.")) {
                    int i, x;
                    sscanf(row, "deaths.%d %d", & i, & x);
                    game->deaths_per_minute [i] = x;
                }
                if (land_starts_with(row, "levels.")) {
                    int i, x;
                    sscanf(row, "levels.%d %d", & i, & x);
                    game->levels_in_minute [i] = x;
                }
                land_free(row);
            }
        }
        land_array_destroy(rows);
    }
    land_free(path);
}
void save_get_name(char const * base, int level, char const * suffix, char * out) {
    sprintf(out, "%s%02d%s", base, level, suffix);
    char * path = land_get_save_file(URL, out);
    strcpy(out, path);
    land_free(path);
}
void save_level(bool editing, bool at_entrance) {
    if (blocks_count() == 0) {
        print("Cannot save empty level");
        return ;
    }
    char name [1024];
    if (editing) {
        sprintf(name, "data/levels/level%02d.txt", game->level);
        overview_update_level(game->overview, game->level);
    }
    else if (at_entrance) {
        save_get_name("save", game->level, ".txt", name);
        save_info();
    }
    else {
        save_get_name("save", 0, ".txt", name);
        save_info();
    }
    Blocks * blocks = game->blocks;
    LandFile * f = land_file_new(name, "w");
    char * st = land_strdup(game->hint);
    land_replace_all(& st, "\n", "|");
    land_file_print(f, "hint %s", st);
    land_file_print(f, "title %s", game->title);
    land_free(st);
    float s = 24;
    int n = 0;
    LandArray * arrays [] = {blocks->transparent, blocks->dynamic, blocks->fixed};
    for (int i = 0; i < 3; i += 1) {
        LandArray * array = arrays [i];
        if (! array) {
            continue;
        }
        {
            LandArrayIterator __iter0__ = LandArrayIterator_first(array);
            for (Block * block = LandArrayIterator_item(array, &__iter0__); LandArrayIterator_next(array, &__iter0__); block = LandArrayIterator_item(array, &__iter0__)) {
                float x = block->x / s + 22;
                float y = block->y / s + 4;
                float z = block->z / s + 22;
                int xi = floor(x);
                int yi = floor(y);
                int zi = floor(z);
                land_file_print(f, "make %s %d %d %d", block->block_type->name, xi, yi, zi);
                xi = x * 100 - xi * 100;
                yi = y * 100 - yi * 100;
                zi = z * 100 - zi * 100;
                if (xi || yi || zi) {
                    land_file_print(f, "move %d %d %d", xi, yi, zi);
                }
                if (block->frame != 0) {
                    land_file_print(f, "frame %d", block->frame);
                }
                n++;
            }
        }
    }
    land_file_destroy(f);
    print("save_level %s %d", name, n);
}
static void add(SaveInfo * si, float x, float y, float z, float xs, float ys, float zs) {
    float xy [14];
    float * p = xy;
    Viewport v = {0, 0, 1};
    project(& v, x + xs, y + ys, z, p + 0, p + 1);
    p += 2;
    project(& v, x, y + ys, z, p + 0, p + 1);
    p += 2;
    project(& v, x, y + ys, z + zs, p + 0, p + 1);
    p += 2;
    project(& v, x, y, z + zs, p + 0, p + 1);
    p += 2;
    project(& v, x + xs, y, z + zs, p + 0, p + 1);
    p += 2;
    project(& v, x + xs, y, z, p + 0, p + 1);
    p += 2;
    project(& v, x + xs, y + ys, z + zs, p + 0, p + 1);
    p += 2;
    si->xy = land_realloc(si->xy, (si->n + 14) * sizeof (float));
    memcpy(si->xy + si->n, xy, sizeof (float) * 14);
    si->n += 14;
}
void count_seen(void) {
    game->seen = 0;
    char name [1024];
    for (int i = 1; i < 50; i += 1) {
        save_get_name("save", i, ".txt", name);
        if (land_file_exists(name)) {
            game->seen++;
        }
    }
}
void save_check(int level) {
    char name [1024];
    LandBuffer * f = NULL;
    SaveInfo * si = g_save_info + level;
    if (level == game->level) {
        sprintf(name, "levels/level%02d.txt", level);
        f = land_buffer_read_from_file(name);
    }
    if (! f) {
        save_get_name("save", level, ".txt", name);
        f = land_buffer_read_from_file(name);
    }
    if (! f) {
        return ;
    }
    memset(si, 0, sizeof (* si));
    si->saved = 1;
    LandArray * rows = land_buffer_split(f, "\n");
    land_buffer_destroy(f);
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(rows);
        for (LandBuffer * rowb = LandArrayIterator_item(rows, &__iter0__); LandArrayIterator_next(rows, &__iter0__); rowb = LandArrayIterator_item(rows, &__iter0__)) {
            char * row = land_buffer_finish(rowb);
            if (land_starts_with(row, "make ")) {
                int xi, yi, zi;
                char bname [100];
                sscanf(row, "make %s %d %d %d", bname, & xi, & yi, & zi);
                BlockType * bt = block_type_by_name(bname);
                float x = (xi - 22 - 3) * 24;
                float y = (yi - 4) * 24;
                float z = (zi - 22 - 3) * 24;
                if (y < - 5000) {
                    continue;
                }
                add(si, x, y, z, bt->xs, bt->ys, bt->zs);
            }
            land_free(row);
        }
    }
    land_array_destroy(rows);
}
void load_level(bool editing, bool at_entrance) {
    Application * a = app();
    land_pause();
    char name [1024];
    Game * self = game;
    self->pristine = 0;
    LandBuffer * f = NULL;
    if (! editing) {
        if (! at_entrance) {
            save_get_name("save", 0, ".txt", name);
            print("Loading %d from %s", game->level, name);
            f = land_buffer_read_from_file(name);
        }
        if (! f) {
            save_get_name("save", game->level, ".txt", name);
            print("Loading %d from %s", game->level, name);
            f = land_buffer_read_from_file(name);
        }
    }
    if (! f) {
        if (! editing) {
            print("    failed from %s", name);
        }
        sprintf(name, "levels/level%02d.txt", game->level);
        print("Loading %d from %s", game->level, name);
        f = land_buffer_read_from_file(name);
        self->pristine = 1;
    }
    self->skunk = NULL;
    self->elephant = NULL;
    self->state = "play";
    self->ticks = 0;
    self->state_tick = 0;
    self->waypoints_count = 0;
    editor->picked = NULL;
    self->lever = NULL;
    Blocks * blocks = game->blocks;
    blocks_reset(blocks);
    if (! f) {
        print("    permanently failed from %s", name);
        land_unpause();
        return ;
    }
    save_load_from_offset(f, 0, 0, 0);
    if (! editing) {
        if (have_friend() && ! game->elephant) {
            game->elephant = block_new(blocks, game->enter_x2, game->enter_y2, game->enter_z2, Data_Elephant);
            block_add(game->elephant);
            print("placed elephant");
            avoid_collision(game->elephant);
        }
        if (! game->skunk) {
            game->skunk = block_new(blocks, game->enter_x, game->enter_y, game->enter_z, Data_Skunk);
            block_add(game->skunk);
            print("placed skunk");
            avoid_collision(game->skunk);
        }
    }
    int n = 0;
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(blocks->fixed);
        for (Block * b = LandArrayIterator_item(blocks->fixed, &__iter0__); LandArrayIterator_next(blocks->fixed, &__iter0__); b = LandArrayIterator_item(blocks->fixed, &__iter0__)) {
            n++;
            // If a level is reloaded, and a flower is not picked (which
            // sets y to somewhere around -9000), it means we failed or
            // reset the level and so ought to get the flower again.
            int flower = block_type_flower(b->block_type);
            if (flower) {
                if (b->y > - 8000) {
                    game->flower [flower] = 0;
                }
            }
        }
    }
    bool visible = 1;
    for (int i = 1; i < 8; i += 1) {
        visible = visible && self->flower [i];
    }
    if (self->key) {
        visible = 0;
    }
    if (a->editor) {
        visible = 1;
    }
    if (game->level == game_starting_level) {
        if (! editing && self->pristine) {
            a->show_map = 0;
        }
    }
    if (game->elephant) {
        game->target_x = game->elephant->x;
        game->target_z = game->elephant->z;
    }
    land_unpause();
    if (n == 0) {
        print("was empty");
        // FIXME: How does that happen?
        save_reset_room(game->level);
    }
}
void save_load_from_offset(LandBuffer * f, int ox, int oy, int oz) {
    LandArray * rows = land_buffer_split(f, "\n");
    land_buffer_destroy(f);
    float s = 24;
    int xi, yi, zi;
    Blocks * blocks = game->blocks;
    Block * block = NULL;
    strcpy(game->hint, "");
    strcpy(game->title, "");
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(rows);
        for (LandBuffer * rowb = LandArrayIterator_item(rows, &__iter0__); LandArrayIterator_next(rows, &__iter0__); rowb = LandArrayIterator_item(rows, &__iter0__)) {
            char * row = land_buffer_finish(rowb);
            if (land_starts_with(row, "make ")) {
                char name [100];
                sscanf(row, "make %s %d %d %d", name, & xi, & yi, & zi);
                float x = (ox + xi - 22) * s;
                float y = (oy + yi - 4) * s;
                float z = (oz + zi - 22) * s;
                if (land_equals(name, "block-exitl")) {
                    strcpy(name, "block-exit");
                }
                if (land_equals(name, "block-exitr")) {
                    strcpy(name, "block-exit_r");
                }
                if (land_equals(name, "block-exitu")) {
                    strcpy(name, "block-exit_u");
                }
                if (land_equals(name, "block-exitd")) {
                    strcpy(name, "block-exit_d");
                }
                BlockType * bt = block_type_by_name(name);
                if (bt == Data_Elephant) {
                    game->target_x = x;
                    game->target_z = z;
                }
                block = NULL;
                if (bt) {
                    block = block_new(blocks, x, y, z, bt);
                    block_add(block);
                }
                else {
                    print("Could not find block '%s'", name);
                }
            }
            if (land_starts_with(row, "move ")) {
                sscanf(row, "move %d %d %d", & xi, & yi, & zi);
                if (block) {
                    block->x += xi * s / 100.0;
                    block->y += yi * s / 100.0;
                    block->z += zi * s / 100.0;
                }
            }
            if (land_starts_with(row, "frame ")) {
                sscanf(row, "frame %d", & xi);
                if (block) {
                    block->frame = xi;
                }
            }
            if (land_starts_with(row, "hint ")) {
                char * st = land_strdup(row + 5);
                land_replace_all(& st, "|", "\n");
                land_string_copy(game->hint, st, 1024);
                land_free(st);
            }
            if (land_starts_with(row, "title ")) {
                land_string_copy(game->title, row + 6, 1024);
            }
            land_free(row);
        }
    }
    land_array_destroy(rows);
}
void save_reset_room(int i) {
    char name [1024];
    save_get_name("save", i, ".txt", name);
    if (! land_file_remove(name)) {
        land_log_message("Cannot remove %s.", name);
    }
    SaveInfo * si = g_save_info + i;
    si->saved = 0;
    game->state = NULL;
    save_get_name("save", 0, ".txt", name);
    land_file_remove(name);
}
void save_new(void) {
    Application * a = app();
    for (int i = 1; i < 50; i += 1) {
        save_reset_room(i);
    }
    game->level = game_starting_level;
    game->deaths = 0;
    game->seen = 0;
    game->key = 0;
    for (int i = 0; i < 8; i += 1) {
        game->flower [i] = 0;
        game->test_tube [i] = 0;
    }
    game->dialog.orchid = 0;
    game->dialog.skunk = 0;
    game->dialog.elephant = 0;
    game->dialog.gnome = 0;
    a->time = 0;
    save_info();
}
bool save_is_saved(int i) {
    return g_save_info [i].saved;
}
int save_get_level_n(int i) {
    return g_save_info [i].n;
}
float* save_get_level_xy(int i) {
    return g_save_info [i].xy;
}
