#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
	};
