#include <stdlib.h>
#include "ibm.h"
#include "device.h"
#include "mem.h"
#include "sst39sf010.h"
#include "xi8088.h"

typedef struct sst_t {
        int command_state;
        int id_mode;
        int erase;
        int dirty;

        char flash_path[1024];
        uint8_t data[0x20000];
} sst_t;

#define SST_CHIP_ERASE 0x10
#define SST_SECTOR_ERASE 0x30
#define SST_ERASE 0x80
#define SST_SET_ID_MODE 0x90
#define SST_BYTE_PROGRAM 0xa0
#define SST_CLEAR_ID_MODE 0xf0

static void set_id_mode(sst_t *sst);
static void clear_id_mode(sst_t *sst);

static uint32_t sst_masked_rom_addr(uint32_t addr) {
        // The flipped MSB of the Xi 8088 is only present in the on-disk files,
        // in ram the mapping is as the software sees. This does not cause
        // problems with the programming commands because they only use the
        // lower 2 bytes.
        if (romset == ROM_XI8088 && !xi8088_bios_128kb())
                return addr & 0xffff;
        else
                return addr & 0x1ffff;
}

static void sst_new_command(sst_t *sst, uint8_t val) {
        switch (val) {
        case SST_CHIP_ERASE:
                if (sst->erase) {
                        memset(rom, 0xff, 0x20000);
                        memset(sst->data, 0xff, 0x20000);
                }
                sst->command_state = 0;
                sst->erase = 0;
                break;

        case SST_ERASE:
                sst->command_state = 0;
                sst->erase = 1;
                break;

        case SST_SET_ID_MODE:
                if (!sst->id_mode)
                        set_id_mode(sst);
                sst->command_state = 0;
                sst->erase = 0;
                break;

        case SST_BYTE_PROGRAM:
                sst->command_state = 3;
                sst->erase = 0;
                break;

        case SST_CLEAR_ID_MODE:
                if (sst->id_mode)
                        clear_id_mode(sst);
                sst->command_state = 0;
                sst->erase = 0;
                break;

        default:
                sst->command_state = 0;
                sst->erase = 0;
        }
}

static void sst_sector_erase(sst_t *sst, uint32_t sst_addr) {
        //        pclog("SST sector erase sst_addr %08x\n", sst_addr);
        memset(&rom[sst_addr & 0x1f000], 0xff, 4096);
        memset(&sst->data[sst_addr & 0x1f000], 0xff, 4096);
        sst->dirty = 1;
}

static uint8_t sst_read_id(uint32_t addr, void *p) {
        if ((addr & 0xffff) == 0)
                return 0xbf; /*SST*/
        else if ((addr & 0xffff) == 1)
                return 0xb5; /*39SF010*/
        else
                return 0xff;
        //                fatal("sst_read_id: addr=%08x\n", addr);
}

static void sst_write(uint32_t addr, uint8_t val, void *p) {
        sst_t *sst = (sst_t *)p;

        //        pclog("sst_write: addr=%08x val=%02x state=%i\n", addr, val, sst->command_state);
        switch (sst->command_state) {
        case 0:
                if (val == 0xf0) {
                        if (sst->id_mode)
                                clear_id_mode(sst);
                } else if ((addr & 0xffff) == 0x5555 && val == 0xaa)
                        sst->command_state = 1;
                else
                        sst->command_state = 0;
                break;
        case 1:
                if ((addr & 0xffff) == 0x2aaa && val == 0x55)
                        sst->command_state = 2;
                else
                        sst->command_state = 0;
                break;
        case 2:
                if ((addr & 0xffff) == 0x5555)
                        sst_new_command(sst, val);
                else if (val == SST_SECTOR_ERASE && sst->erase) {
                        sst_sector_erase(sst, sst_masked_rom_addr(addr));
                        sst->command_state = 0;
                } else
                        sst->command_state = 0;
                break;
        case 3:
                //                pclog("Byte program %08x %02x\n", addr, val);
                rom[sst_masked_rom_addr(addr)] = val;
                sst->data[sst_masked_rom_addr(addr)] = val;
                sst->command_state = 0;
                sst->dirty = 1;
                break;
        }
}

static void set_id_mode(sst_t *sst) {
        int c;

        for (c = 0; c < 8; c++) {
                mem_mapping_set_handler(&bios_mapping[c], sst_read_id, NULL, NULL, sst_write, NULL, NULL);
                mem_mapping_set_p(&bios_mapping[c], sst);
                mem_mapping_set_handler(&bios_high_mapping[c], sst_read_id, NULL, NULL, sst_write, NULL, NULL);
                mem_mapping_set_p(&bios_high_mapping[c], sst);
        }
        sst->id_mode = 1;
}

static void clear_id_mode(sst_t *sst) {
        int c;

        for (c = 0; c < 8; c++) {
                mem_mapping_set_handler(&bios_mapping[c], mem_read_bios, mem_read_biosw, mem_read_biosl, sst_write, NULL, NULL);
                mem_mapping_set_p(&bios_mapping[c], sst);
                mem_mapping_set_handler(&bios_high_mapping[c], mem_read_bios, mem_read_biosw, mem_read_biosl, sst_write, NULL,
                                        NULL);
                mem_mapping_set_p(&bios_high_mapping[c], sst);
        }
        sst->id_mode = 0;
}

static void *sst_39sf010_init() {
        FILE *f;
        sst_t *sst = malloc(sizeof(sst_t));
        memset(sst, 0, sizeof(sst_t));

        switch (romset) {
        case ROM_XI8088:
                strcpy(sst->flash_path, "xi8088/");
                break;

        case ROM_FIC_VA503P:
                strcpy(sst->flash_path, "fic_va503p/");
                break;

        default:
                fatal("sst_39sf010_init on unsupported ROM set %i\n", romset);
        }
        strcat(sst->flash_path, "flash.bin");
        f = romfopen(sst->flash_path, "rb");
        if (f) {
                switch (romset) {
                case ROM_XI8088:
                        if (xi8088_bios_128kb())
                                fread(rom + 0x10000, 0x10000, 1, f);
                        fread(rom, 0x10000, 1, f);
                        break;

                case ROM_FIC_VA503P:
                        fread(rom, 0x20000, 1, f);
                        break;

                default:
                        fatal("sst_39sf010_init on unsupported ROM set %i\n", romset);
                }
                fclose(f);
        }
        memcpy(sst->data, rom, 0x20000);

        clear_id_mode(sst);

        return sst;
}

static void sst_39sf010_close(void *p) {
        sst_t *sst = (sst_t *)p;

        if (sst->dirty) {
                FILE *f = romfopen(sst->flash_path, "wb");
                switch (romset) {
                case ROM_XI8088:
                        if (xi8088_bios_128kb())
                                fwrite(sst->data + 0x10000, 0x10000, 1, f);
                        fwrite(sst->data, 0x10000, 1, f);
                        break;

                case ROM_FIC_VA503P:
                        fwrite(sst->data, 0x20000, 1, f);
                        break;

                default:
                        fatal("sst_39sf010_init on unsupported ROM set %i\n", romset);
                }
                fclose(f);
        }

        free(sst);
}

device_t sst_39sf010_device = {"SST 39SF010 Flash BIOS", 0, sst_39sf010_init, sst_39sf010_close, NULL, NULL, NULL, NULL, NULL};
