#include "progress.hh"
#include <stdio.h>
#include <string.h>
#include <zlib.h>

#include <cstdlib>

#ifdef _NO_TMS
#define tms_infof(...)
#define tms_errorf(...)
#else
#include <tms/cpp.hh>
#endif

#define WARNING_STR "Warning: if you edit this file manually, you risk losing all your game data."
#define ID_XOR    0xfa5f0e6f
#define O_XOR     0xaab27f1a
#define X_XOR     0x0f81fea1
#define A_XOR     0xdeadbeef
#define TYPE_SEP  0x3d

std::map<uint32_t, lvl_progress*> progress::levels[3];

bool progress::initialized = false;
char progress::path[1024] = {0};

lvl_progress *progress::get_level_progress(int level_type, uint32_t level_id) {
    if (level_type < 0 || level_type >= 3)
        return 0;

    std::map<uint32_t, lvl_progress*>::iterator i;

    i = levels[level_type].find(level_id);

    if (i != levels[level_type].end())
        return i->second;

    lvl_progress *ret = new lvl_progress();

    levels[level_type].insert(std::pair<uint32_t, lvl_progress*>(level_id, ret));
    return ret;
}

void progress::init(char *data_path) {
    char tmp[1024];
    memcpy(path, data_path, 1024);

    long crc_pos[4] = {0,0,0,0};
    uint32_t crc_set = 0;

    uint32_t crc_v = 0;
    uint32_t crc_base = 0;

    FILE *fp = fopen(path, "rb");

    if (!fp) {
        tms_infof("data.bin doesn't exist, creating one");
        initialized = true;
        return;
    }

    // well...
    goto load;

err:
    levels[0].clear();
    levels[1].clear();
    levels[2].clear();
    tms_errorf("ERROR: invalid data in data.bin");
    if (fp)
        fclose(fp);
    initialized = true;
    return;

load:
    fread(tmp, 1, strlen(WARNING_STR), fp);

    for (int x = 0; x < 3; x++) {
        uint32_t nlevels;
        fread(&nlevels, 1, sizeof(uint32_t), fp);

        nlevels ^= A_XOR;

        for (uint32_t l = 0; l < nlevels; l++) {
            lvl_progress p;
            uint32_t r_nn = 0;
            uint32_t id;
            size_t l_size;
            switch (l % 5) {
                case 0: l_size = 1; break;
                case 1: l_size = 3; break;
                case 2: l_size = 4; break;
                case 3: l_size = 2; break;
                case 4: l_size = 5; break;
            }
            fread(tmp, l_size, sizeof(uint8_t), fp);

            fread(&id, 1, sizeof(uint32_t), fp);
            id ^= ID_XOR;
            id -= l;

            if (crc_set < 4u) {
                crc_pos[crc_set] = ftell(fp);
                uint8_t t;
                fread(&t, 1, sizeof(uint8_t), fp);

                crc_v |= t << crc_set*8u;

                crc_set ++;
            }

            fread(&p.num_plays, 1, sizeof(uint32_t), fp);
            r_nn = p.num_plays;
            p.completed = p.num_plays >> 31;
            p.num_plays &= ~(1<<31);

            fread(&p.top_score, 1, sizeof(uint32_t), fp);
            r_nn += p.top_score;
            p.top_score ^= O_XOR;

            //tms_infof("reading: %u, %u", id, p.top_score);

            fread(&p.last_score, 1, sizeof(uint32_t), fp);
            r_nn += p.last_score;
            p.last_score ^= O_XOR;

            fread(&p.time, 1, sizeof(uint32_t), fp);
            r_nn += p.time;
            p.time ^= O_XOR;

            uint32_t n;
            fread(&n, 1, sizeof(uint32_t), fp);
            n ^= X_XOR;

            /* verify lower 16 bits of top score */
            uint32_t t = p.top_score;

            if ((t & 0xffff) != (n & 0xffff)) {
                tms_infof("invalid score 1");
                goto err;
            }

            if ( (((t < 1000000000) << 31) != (n & (1<<31)))
                || ((t < 100000) << 30) != (n & (1<<30))
                || ((t < 10000000) << 29) != (n & (1<<29))
                || ((t < 100) << 28) != (n & (1<<28))
                || ((t < 100000000) << 27) != (n & (1<<27))
                || ((t < 10000) << 26) != (n & (1<<26))
                || ((t < 1000000) << 25) != (n & (1<<25))
                || ((t < 1000) << 24) != (n & (1<<24))
                || ((t < 10) << 23) != (n & (1<<23))) {
                tms_infof("invalid score 2");
                goto err;
            }

            uint32_t nn,nnn;
            fread(&nn, 1, sizeof(uint32_t), fp);
            switch (l % 9) {
                case 0: l_size = 9; break;
                case 1: l_size = 1; break;
                case 2: l_size = 2; break;
                case 3: l_size = 5; break;
                case 4: l_size = 7; break;
                case 5: l_size = 8; break;
                case 6: l_size = 3; break;
                case 7: l_size = 4; break;
                case 8: l_size = 6; break;
            }
            fread(tmp, l_size, sizeof(uint8_t), fp);

            fread(&nnn, 1, sizeof(uint32_t), fp);
            if (nn != r_nn) {
                tms_errorf("invalid r_nn");
                goto err;
            }
            if (nnn != (p.num_plays + p.top_score + p.last_score + p.time)) {
                tms_errorf("invalid nnn");
                goto err;
            }
            crc_base += p.top_score;

            lvl_progress *pp = new lvl_progress();
            *pp = p;
            levels[x].insert(std::pair<uint32_t, lvl_progress*>(id, pp));
        }
    }

    while (crc_set < 4u) {
        crc_pos[crc_set] = ftell(fp);
        uint8_t t;
        fread(&t, 1, sizeof(uint8_t), fp);

        crc_v |= t << crc_set*8u;

        crc_set ++;
    }

    // verify crc32
    long size = ftell(fp);
    if (size < 0) {
        goto err;
    }
    char *tcrc = (char*)malloc(size);

    fseek(fp, 0, SEEK_SET);
    fread(tcrc, 1, size, fp);
    fclose(fp);
    fp = 0;

    tms_infof("crc base read: %x", crc_base);
    tms_infof("crc sum read: %x", crc_v);

    for (uint32_t x = 0; x < 4; x++) {
        tcrc[crc_pos[x]] = (uint8_t)((crc_base >> (x*8u)) & 0xffu);
    }

    uint32_t crc = crc32(0L, (const Bytef*)tcrc, size);
    free(tcrc);

    if (crc != crc_v) {
        tms_infof("crc mismatch");
        //goto err;
    }
    initialized = true;
}

void progress::commit() {
    if (!initialized)
        return;

    long crc_pos[4]={0,0,0,0};
    uint32_t crc_set=0;

    FILE *fp = fopen(path, "w+b");

    if (!fp) {
        tms_errorf("Error: could not save progress %s", path);
        return;
    }

    uint32_t crc_val = 0;
    fwrite(WARNING_STR, 1, strlen(WARNING_STR), fp);

    for (int x=0; x<3; x++) {
        uint32_t t = levels[x].size() ^ A_XOR;
        fwrite(&t, 1, sizeof(uint32_t), fp);
        uint32_t c = 0;

        for (std::map<uint32_t, lvl_progress*>::iterator i = levels[x].begin();
                i != levels[x].end(); i++) {
            lvl_progress p = *i->second;

            uint32_t id = (i->first+c) ^ ID_XOR;
            size_t l_size;
            switch (c%5) {
                case 0: l_size = 1; break;
                case 1: l_size = 3; break;
                case 2: l_size = 4; break;
                case 3: l_size = 2; break;
                case 4: l_size = 5; break;
            }
            fwrite(i->second, l_size, sizeof(uint8_t), fp);
            fwrite(&id, 1, sizeof(uint32_t), fp);

            if (crc_set < 4) {
                crc_pos[crc_set] = ftell(fp);
                uint8_t xx = (uint8_t)levels[1].size();
                fwrite(&xx, 1, sizeof(uint8_t), fp);
                crc_set ++;
            }
            uint32_t n = p.num_plays | (p.completed << 31), nn=0,nnn=0;
            nn += n;
            nnn += p.num_plays;
            fwrite(&n, 1, sizeof(uint32_t), fp);
            n = p.top_score ^ O_XOR;
            nn += n;
            nnn += p.top_score;
            fwrite(&n, 1, sizeof(uint32_t), fp);
            n = p.last_score ^ O_XOR;
            nn += n;
            nnn += p.last_score;
            fwrite(&n, 1, sizeof(uint32_t), fp);
            n = p.time ^ O_XOR;
            nn += n;
            nnn += p.time;
            fwrite(&n, 1, sizeof(uint32_t), fp);
            crc_val += p.top_score;

            n =(
                ((uint32_t)(p.top_score < 1000000000) << 31)
                | ((uint32_t)(p.top_score < 100000) << 30)
                | ((uint32_t)(p.top_score < 10000000) << 29)
                | ((uint32_t)(p.top_score < 100) << 28)
                | ((uint32_t)(p.top_score < 100000000) << 27)
                | ((uint32_t)(p.top_score < 10000) << 26)
                | ((uint32_t)(p.top_score < 1000000) << 25)
                | ((uint32_t)(p.top_score < 1000) << 24)
                | ((uint32_t)(p.top_score < 10) << 23)
                | (p.top_score & 0xffff)
                ) ^ X_XOR;
            fwrite(&n, 1, sizeof(uint32_t), fp);
            fwrite(&nn, 1, sizeof(uint32_t), fp);
            switch (c % 9) {
                case 0: l_size = 9; break;
                case 1: l_size = 1; break;
                case 2: l_size = 2; break;
                case 3: l_size = 5; break;
                case 4: l_size = 7; break;
                case 5: l_size = 8; break;
                case 6: l_size = 3; break;
                case 7: l_size = 4; break;
                case 8: l_size = 6; break;
            }
            fwrite(i->second, l_size, sizeof(uint8_t), fp);
            fwrite(&nnn, 1, sizeof(uint32_t), fp);

            c++;
        }
    }

    for (; crc_set < 4; crc_set++) {
        crc_pos[crc_set] = ftell(fp);
        uint8_t xx = (uint8_t)levels[1].size();
        fwrite(&xx, 1, sizeof(uint8_t), fp);
    }
    long size = ftell(fp);

    for (uint32_t x = 0; x < 4; x++) {
        fseek(fp, crc_pos[x], SEEK_SET);
        uint8_t v = (uint8_t)((crc_val >> (x*8u)) & 0xffu);
        fwrite(&v, 1, sizeof(uint8_t), fp);
    }

    fseek(fp, 0, SEEK_SET);

    size_t bl;
    unsigned char b[1024];
    uint32_t crc = crc32(0L, Z_NULL, 0);
    while ((bl = fread(b, 1, 1024, fp)) > 0) {
        crc = crc32(crc, b, bl);
    }

    for (uint32_t x=0; x<4; x++) {
        fseek(fp, crc_pos[x], SEEK_SET);
        uint8_t v = (uint8_t)((crc >> (x*8u)) & 0xffu);
        fwrite(&v, 1, sizeof(uint8_t), fp);
    }

    fclose(fp);
}
