#ifndef _MC161_STREAMFILE_H_
#define _MC161_STREAMFILE_H_
#include "../streamfile.h"


typedef struct {
    int32_t base_key;
    int32_t curr_key;
    uint32_t curr_offset;
} mc161_io_data;


static void decrypt_chunk(uint8_t* buf, int buf_size, mc161_io_data* data) {
    int i;
    int32_t hash = data->curr_key;


    for (i = 0; i < buf_size; i++) {
    	buf[i] = (uint8_t)(buf[i] ^ ((hash >> 8) & 0xFF));
	    hash = (int32_t)(hash * 498729871) + (85731 * (int8_t)buf[i]); /* signed */
    }

    data->curr_key = hash;
    data->curr_offset += buf_size;
}

static void update_key(STREAMFILE* sf, off_t offset, mc161_io_data* data) {
    uint8_t buf[0x800];
    size_t bytes;
    size_t to_skip;

    if (offset < data->curr_offset || offset == 0x00) {
        data->curr_key = data->base_key;
        data->curr_offset = 0x00;
        to_skip = offset;
    }
    else {
        to_skip = offset - data->curr_offset;
    }

    /* update key by reading and decrypt all data between current offset + last known key to requested offset */
    while (to_skip > 0) {
        size_t read_size = sizeof(buf);
        if (read_size > to_skip)
            read_size = to_skip;

        bytes = read_streamfile(buf, data->curr_offset, read_size, sf);
        if (!bytes) /* ??? */
            break;

        decrypt_chunk(buf, bytes, data); /* updates curr_offset and key */
        to_skip -= bytes;
    }
}

/* XOR depends on decrypted data, meanings having to decrypt linearly to reach some offset. */
static size_t mc161_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, mc161_io_data* data) {
    size_t bytes;

    /* read and decrypt unneded data */
    update_key(sf, offset, data);

    /* read and decrypt current data */
    bytes = read_streamfile(dest, offset, length, sf);
    decrypt_chunk(dest, bytes, data);

    return bytes;
}

/* String.hashCode() should be equivalent to this on Windows (though implementation-defined), note Java has no unsigned */
static int32_t mc161_get_java_hashcode(STREAMFILE* sf) {
    char filename[1024];
    int i = 0;
    int32_t hash = 0;

    get_streamfile_filename(sf, filename, sizeof(filename));

    while (filename[i] != '\0') {
        hash = 31 * hash + (uint8_t)filename[i];
        i++;
    }

    return hash;
}

/* decrypts Minecraft old streams (some info from: https://github.com/ata4/muscode) */
static STREAMFILE* setup_mc161_streamfile(STREAMFILE* sf) {
    STREAMFILE* new_sf = NULL;
    mc161_io_data io_data = {0};

    io_data.base_key = mc161_get_java_hashcode(sf);
    io_data.curr_key = io_data.base_key;
    io_data.curr_offset = 0x00;

    new_sf = open_wrap_streamfile(sf);
    new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(mc161_io_data), mc161_io_read, NULL);
    new_sf = open_fakename_streamfile_f(new_sf, NULL, "ogg");
    return new_sf;
}

#endif /* _MC161_STREAMFILE_H_ */
