#include <stdlib.h>
#include "ibm.h"
#include "io.h"
#include "device.h"
#include "tandy_eeprom.h"
#include "nvr.h"

typedef struct {
	int state;
	int count;
	int addr;
	int clock;
	uint16_t data;
	uint16_t store[64];

	int romset;
} tandy_eeprom_t;

enum {
	EEPROM_IDLE,
	EEPROM_GET_OPERATION,
	EEPROM_READ,
	EEPROM_WRITE
};

static int eeprom_data_out;

void tandy_eeprom_write(uint16_t addr, uint8_t val, void *p) {
	tandy_eeprom_t *eeprom = (tandy_eeprom_t *)p;

	if ((val & 4) && !eeprom->clock) {
//                pclog("eeprom_write %02x %i %i\n", val, eeprom->state, eeprom->count);
		switch (eeprom->state) {
		case EEPROM_IDLE:
			switch (eeprom->count) {
			case 0:
				if (!(val & 3))
					eeprom->count = 1;
				else
					eeprom->count = 0;
				break;
			case 1:
				if ((val & 3) == 2)
					eeprom->count = 2;
				else
					eeprom->count = 0;
				break;
			case 2:
				if ((val & 3) == 3)
					eeprom->state = EEPROM_GET_OPERATION;
				eeprom->count = 0;
				break;
			}
			break;
		case EEPROM_GET_OPERATION:eeprom->data = (eeprom->data << 1) | (val & 1);
			eeprom->count++;
			if (eeprom->count == 8) {
				eeprom->count = 0;
//                                pclog("EEPROM get operation %02x\n", eeprom->data);
				eeprom->addr = eeprom->data & 0x3f;
				switch (eeprom->data & 0xc0) {
				case 0x40:eeprom->state = EEPROM_WRITE;
					break;
				case 0x80:eeprom->state = EEPROM_READ;
					eeprom->data = eeprom->store[eeprom->addr];
//                                        pclog("EEPROM read data %02x %04x\n", eeprom->addr, eeprom->data);
					break;
				default:eeprom->state = EEPROM_IDLE;
					break;
				}
			}
			break;

		case EEPROM_READ:eeprom_data_out = eeprom->data & 0x8000;
			eeprom->data <<= 1;
			eeprom->count++;
			if (eeprom->count == 16) {
				eeprom->count = 0;
				eeprom->state = EEPROM_IDLE;
			}
			break;
		case EEPROM_WRITE:eeprom->data = (eeprom->data << 1) | (val & 1);
			eeprom->count++;
			if (eeprom->count == 16) {
				eeprom->count = 0;
				eeprom->state = EEPROM_IDLE;
//                                pclog("EEPROM write %04x to %02x\n", eeprom->data, eeprom->addr);
				eeprom->store[eeprom->addr] = eeprom->data;
			}
			break;
		}
	}

	eeprom->clock = val & 4;
}

int tandy_eeprom_read() {
//        pclog("tandy_eeprom_read: data_out=%x\n", eeprom_data_out);
	return eeprom_data_out;
}

void *tandy_eeprom_init() {
	tandy_eeprom_t *eeprom = malloc(sizeof(tandy_eeprom_t));
	FILE *f = NULL;

	memset(eeprom, 0, sizeof(tandy_eeprom_t));

	eeprom->romset = romset;
	switch (romset) {
	case ROM_TANDY1000HX:f = nvrfopen("tandy1000hx.bin", "rb");
		break;
	case ROM_TANDY1000SL2:f = nvrfopen("tandy1000sl2.bin", "rb");
		break;
	}
	if (f) {
		fread(eeprom->store, 128, 1, f);
		fclose(f);
	} else
		memset(eeprom->store, 0, 128);

	io_sethandler(0x037c, 0x0001, NULL, NULL, NULL, tandy_eeprom_write, NULL, NULL, eeprom);

	return eeprom;
}

void tandy_eeprom_close(void *p) {
	tandy_eeprom_t *eeprom = (tandy_eeprom_t *)p;
	FILE *f = NULL;

	switch (eeprom->romset) {
	case ROM_TANDY1000HX:f = nvrfopen("tandy1000hx.bin", "wb");
		break;
	case ROM_TANDY1000SL2:f = nvrfopen("tandy1000sl2.bin", "wb");
		break;
	}
	fwrite(eeprom->store, 128, 1, f);
	fclose(f);

	free(eeprom);
}

device_t tandy_eeprom_device =
	{
		"Tandy EEPROM",
		0,
		tandy_eeprom_init,
		tandy_eeprom_close,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL
	};
