/*
 * mii_disk2.c
 *
 * Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
 *
 * SPDX-License-Identifier: MIT
 */

#define _GNU_SOURCE // for asprintf
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "mii.h"
#include "mii_bank.h"

#include "mii_rom_disk2.h"
#include "mii_woz.h"
#include "mii_floppy.h"

enum {
	// bits used to address the LSS ROM using lss_mode
	Q7_WRITE_BIT	= 3,
	Q6_LOAD_BIT		= 2,
	QA_BIT			= 1,
	RP_BIT			= 0,
};

enum {
	SIG_DR,			// data register
	SIG_WR,			// write register
	SIG_DRIVE,		// selected drive
	SIG_MOTOR,		// motor on/off
	SIG_READ_DR,
	SIG_LSS_CLK,
	SIG_LSS_SEQ,
	SIG_LSS_CMD,
	SIG_LSS_WRITE,
	SIG_LSS_LOAD,
	SIG_LSS_QA,
	SIG_LSS_RP,		// read pulse
	SIG_LSS_WB,		// write bit
	SIG_LSS_RANDOM,
	SIG_WRITE_CYCLE,
	SIG_COUNT
};
const char *sig_names[] = {
	"D2_DR", "D2_WR", "DRIVE", "MOTOR", "READ_DR",
	"LSS_CLK", "LSS_SEQ", "LSS_CMD", "LSS_WRITE", "LSS_LOAD",
	"LSS_QA", "LSS_RP", "LSS_WB", "LSS_RANDOM", "WRITE_CYCLE",
};

typedef struct mii_card_disk2_t {
	mii_t *			mii;
	mii_dd_t 		drive[2];
	mii_floppy_t	floppy[2];
	uint8_t 		selected;

	uint8_t 		timer_off;
	uint8_t 		timer_lss;

	uint8_t 		write_register;
	uint8_t 		head : 4;		// bits are shifted in there
	uint16_t 		clock;			// LSS clock cycles, read a bit when 0
	uint8_t 		lss_state : 4,	// Sequence state
					lss_mode : 4;	// WRITE/LOAD/SHIFT/QA/RP etc
	uint8_t 		lss_prev_state;	// for write bit
	uint8_t 		lss_skip;
	uint8_t 		data_register;

	uint64_t 		debug_last_write, debug_last_duration;
	mii_vcd_t 		*vcd;
	mii_signal_t 	*sig;
} mii_card_disk2_t;

static void
_mii_disk2_lss_tick(
	mii_card_disk2_t *c );
static void
_mii_disk2_vcd_debug(
	mii_card_disk2_t *c,
	int on);

// debug, used for mish, only supports one card tho (yet)
static mii_card_disk2_t *_mish_d2 = NULL;

/*
 * This timer is used to turn off the motor after a second
 */
static uint64_t
_mii_floppy_motor_off_cb(
		mii_t * mii,
		void * param )
{
	mii_card_disk2_t *c = param;
	mii_floppy_t *f 	= &c->floppy[c->selected];
//	printf("%s drive %d off\n", __func__, c->selected);
	if (c->drive[c->selected].file && f->seed_dirty != f->seed_saved)
		mii_floppy_update_tracks(f, c->drive[c->selected].file);
	f->motor = 0;
	mii_raise_signal(c->sig + SIG_MOTOR, 0);
	return 0;
}

/*
 * This (tries) to be called every cycle, it never happends in practice,
 * as all the instructions can use multiple cycles by CPU runs.
 * But I can retreive the overshoot cycles and call the LSS as many times
 * as needed to 'catch up'
 */
static uint64_t
_mii_floppy_lss_cb(
		mii_t * mii,
		void * param )
{
	mii_card_disk2_t *c = param;
	mii_floppy_t *f 	= &c->floppy[c->selected];
	if (!f->motor)
		return 0;	// stop timer, motor is now off
	// delta is ALWAYS negative, or zero here
	int32_t delta = mii_timer_get(mii, c->timer_lss);
	uint64_t ret = -delta + 1;
	do {
		_mii_disk2_lss_tick(c);
		_mii_disk2_lss_tick(c);
	} while (delta++ < 0);
	return ret;
}

static uint8_t
_mii_disk2_switch_track(
		mii_t *mii,
		mii_card_disk2_t *c,
		int delta)
{
	mii_floppy_t *f = &c->floppy[c->selected];
	int qtrack = f->qtrack + delta;
	if (qtrack < 0) qtrack = 0;
	if (qtrack >= MII_FLOPPY_TRACK_COUNT * 4)
			qtrack = (MII_FLOPPY_TRACK_COUNT * 4) -1;

	if (qtrack == f->qtrack)
		return f->qtrack;

	uint8_t track_id = f->track_id[f->qtrack];
//	if (track_id != 0xff)
//		printf("NEW TRACK D%d: %d\n", c->selected, track_id);
	uint8_t track_id_new = f->track_id[qtrack];
	if (track_id != track_id_new && track_id != MII_FLOPPY_NOISE_TRACK) {
		if (track_id == 0 && c->vcd)
			_mii_disk2_vcd_debug(c, 0);
		if (f->seed_dirty != f->seed_saved) {
		//	mii_floppy_resync_track(f, track_id, 0);
		}
	}
	if (track_id_new >= MII_FLOPPY_TRACK_COUNT)
		track_id_new = MII_FLOPPY_NOISE_TRACK;
	/* adapt the bit position from one track to the others, from WOZ specs */
	if (track_id_new != MII_FLOPPY_NOISE_TRACK) {
		uint32_t track_size = f->tracks[track_id].bit_count;
		uint32_t new_size = f->tracks[track_id_new].bit_count;
		uint32_t new_pos = f->bit_position * new_size / track_size;
		f->bit_position = new_pos;
	}
	f->qtrack = qtrack;
	return f->qtrack;
}


static int
_mii_disk2_init(
		mii_t * mii,
		struct mii_slot_t *slot )
{
	mii_card_disk2_t *c = calloc(1, sizeof(*c));
	slot->drv_priv = c;
	c->mii = mii;
	printf("%s loading in slot %d\n", __func__, slot->id + 1);
	uint16_t addr = 0xc100 + (slot->id * 0x100);
	mii_bank_write(
			&mii->bank[MII_BANK_CARD_ROM],
			addr, mii_rom_disk2, 256);

	c->sig = mii_alloc_signal(&mii->sig_pool, 0, SIG_COUNT, sig_names);
	for (int i = 0; i < SIG_COUNT; i++)
		c->sig[i].flags |= SIG_FLAG_FILTERED;
	for (int i = 0; i < 2; i++) {
		mii_dd_t *dd = &c->drive[i];
		dd->slot_id = slot->id + 1;
		dd->drive = i + 1;
		dd->slot = slot;
		asprintf((char **)&dd->name, "Disk ][ S:%d D:%d",
				dd->slot_id, dd->drive);
		mii_floppy_init(&c->floppy[i]);
		c->floppy[i].id = i;
		dd->floppy = &c->floppy[i];
	}
	mii_dd_register_drives(&mii->dd, c->drive, 2);
	char *n;
	asprintf(&n, "Disk ][ S:%d motor off", slot->id + 1);
	c->timer_off 	= mii_timer_register(mii, _mii_floppy_motor_off_cb, c, 0, n);
	asprintf(&n, "Disk ][ S:%d LSS", slot->id + 1);
	c->timer_lss 	= mii_timer_register(mii, _mii_floppy_lss_cb, c, 0, n);
	_mish_d2 = c;
	return 0;
}

static void
_mii_disk2_reset(
		mii_t * mii,
		struct mii_slot_t *slot )
{
	mii_card_disk2_t *c = slot->drv_priv;
//	printf("%s\n", __func__);
	c->selected = 1;
	_mii_floppy_motor_off_cb(mii, c);
	c->selected = 0;
	_mii_floppy_motor_off_cb(mii, c);
	mii_raise_signal(c->sig + SIG_DRIVE, 0);
}

static uint8_t
_mii_disk2_access(
	mii_t * mii, struct mii_slot_t *slot,
	uint16_t addr, uint8_t byte, bool write)
{
	mii_card_disk2_t *c = slot->drv_priv;
	mii_floppy_t * f = &c->floppy[c->selected];
	uint8_t ret = 0;

	if (write) {
//		if (!(byte &0x80))
//			printf("WRITE PC:%04x %4.4x: %2.2x\n", mii->cpu.PC, addr, byte);
		c->write_register = byte;
		mii_raise_signal(c->sig + SIG_WR, byte);
		if (c->vcd) {
			uint8_t delta = c->vcd->cycle - c->debug_last_write;

			mii_raise_signal_float(
					c->sig + SIG_WRITE_CYCLE, c->debug_last_duration != delta, 0);
			c->debug_last_duration = delta;
			c->debug_last_write = c->vcd->cycle;
		}
	}
	int psw = addr & 0x0F;
	int p = psw >> 1, on = psw & 1;
	int read = 0;
	switch (psw) {
		case 0x00 ... 0x07: {
			static const int8_t delta[4][4] = {
				{0, 1, 2, -1}, {-1, 0, 1, 2}, {-2, -1, 0, 1}, {1, -2, -1, 0},
			};
			if (on) {
				_mii_disk2_switch_track(mii, c, delta[f->stepper][p] * 2);
				f->stepper = p;
			}
		}	break;
		case 0x08:
		case 0x09: {
			// motor on/off
			if (on) {
				mii_timer_set(mii, c->timer_off, 0);
				mii_timer_set(mii, c->timer_lss, 1);
				f->motor = 1;
				mii_raise_signal(c->sig + SIG_MOTOR, 1);
			} else {
				int32_t timer = mii_timer_get(mii, c->timer_off);
				mii_timer_set(mii, c->timer_off,
						timer + (1000000 * mii->speed)); // one second
			}
		}	break;
		case 0x0A:
		case 0x0B: {
			if (on != c->selected) {
				c->selected = on;
			//	printf("SELECTED DRIVE: %d\n", c->selected);
				c->floppy[on].motor = f->motor;
				f->motor = 0;
				mii_raise_signal(c->sig + SIG_MOTOR, 0);
			}
		}	break;
		case 0x0C:
		case 0x0D:
			c->lss_mode = (c->lss_mode & ~(1 << Q6_LOAD_BIT)) | (!!on << Q6_LOAD_BIT);
			if (!(c->lss_mode & (1 << Q7_WRITE_BIT)) && f->heat) {
				uint8_t 	track_id = f->track_id[f->qtrack];
				uint32_t 	byte_index 	= f->bit_position >> 3;
				unsigned int dstb = byte_index / MII_FLOPPY_HM_HIT_SIZE;
				f->heat->read.map[track_id][dstb] = 255;
				f->heat->read.seed++;
				read++;
			}
			break;
		case 0x0E:
		case 0x0F:
			c->lss_mode = (c->lss_mode & ~(1 << Q7_WRITE_BIT)) | (!!on << Q7_WRITE_BIT);
			break;
	}
	/*
	 * Here we run one LSS cycle ahead of 'schedule', just because it allows
	 * the write protect bit to be loaded if needed, it *has* to run before
	 * we return this value, so we marked a skip and run it here.
	 */
//	_mii_disk2_lss_tick(c);
//	c->lss_skip++;
	if (read)
		mii_raise_signal(c->sig + SIG_READ_DR, c->data_register);
	ret = on ? byte : c->data_register;

	return ret;
}

static int
_mii_disk2_command(
		mii_t * mii,
		struct mii_slot_t *slot,
		uint32_t cmd,
		void * param)
{
	mii_card_disk2_t *c = slot->drv_priv;
	int res = -1;
	switch (cmd) {
		case MII_SLOT_DRIVE_COUNT:
			if (param) {
				*(int *)param = 2;
				res = 0;
			}
			break;
		case MII_SLOT_DRIVE_WP ... MII_SLOT_DRIVE_WP + 2 - 1: {
			int drive = cmd - MII_SLOT_DRIVE_WP;
			int *wp = param;
			if (wp) {
				res = 0;
				printf("Drive %d WP: 0x%x set %s\n", drive,
							c->floppy[drive].write_protected,
							*wp ? "ON" : "OFF");
				c->floppy[drive].write_protected =
						(c->floppy[drive].write_protected &
											~(MII_FLOPPY_WP_MANUAL))|
						(*wp ? MII_FLOPPY_WP_MANUAL : 0);
			}
		}	break;
		case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + 2 - 1: {
			int drive = cmd - MII_SLOT_DRIVE_LOAD;
			const char *pathname = param;
			mii_dd_file_t *file = NULL;
			if (pathname && *pathname) {
				if (c->drive[drive].file &&
						!strcmp(c->drive[drive].file->pathname, pathname)) {
					printf("%s D%d Same file, not reloading\n",
							__func__, drive);
					return 0;
				}
				file = mii_dd_file_load(&mii->dd, pathname, O_RDWR);
				if (!file)
					return -1;
			}
			// reinit all tracks, bits, maps etc
			mii_floppy_init(&c->floppy[drive]);
			mii_dd_drive_load(&c->drive[drive], file);
			mii_floppy_load(&c->floppy[drive], file);
			res = 0;
		}	break;
		case MII_SLOT_D2_GET_FLOPPY: {
			if (param) {
				mii_floppy_t ** fp = param;
				fp[0] = &c->floppy[0];
				fp[1] = &c->floppy[1];
				res = 0;
			}
		}	break;
	}
	return res;
}

static mii_slot_drv_t _driver = {
	.name = "disk2",
	.desc = "Apple Disk ][",
	.init = _mii_disk2_init,
	.reset = _mii_disk2_reset,
	.access = _mii_disk2_access,
	.command = _mii_disk2_command,
};
MI_DRIVER_REGISTER(_driver);

static void
_mii_disk2_vcd_debug(
	mii_card_disk2_t *c,
	int on)
{
	if (c->vcd) {
		mii_vcd_close(c->vcd);
		c->vcd = NULL;
		printf("VCD OFF\n");
	} else {
		c->vcd = calloc(1, sizeof(*c->vcd));
		// 2Mhz clock
		mii_vcd_init(c->mii, "/tmp/disk2.vcd", c->vcd, 500);
		printf("VCD ON\n");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_DR, 8, "DR");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_WR, 8, "WR");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_DRIVE, 1, "DRIVE");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_MOTOR, 1, "MOTOR");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_READ_DR, 8, "READ");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_CLK, 1, "LSS_CLK");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_SEQ, 4, "LSS_SEQ");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_CMD, 4, "LSS_CMD");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_WRITE, 1, "LSS_W/R");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_LOAD, 1, "LSS_L/S");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_QA, 1, "LSS_QA");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_RP, 1, "LSS_RP");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_WB, 1, "LSS_WB");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_RANDOM, 1, "LSS_RANDOM");
		mii_vcd_add_signal(c->vcd, c->sig + SIG_WRITE_CYCLE, 1, "WRITE_CYCLE");
		mii_vcd_start(c->vcd);
		// put the LSS state in the VCD to start with
		mii_raise_signal(c->sig + SIG_LSS_QA,
					!!(c->lss_mode & (1 << QA_BIT)));
		mii_raise_signal(c->sig + SIG_LSS_WRITE,
					!!(c->lss_mode & (1 << Q7_WRITE_BIT)));
		mii_raise_signal(c->sig + SIG_LSS_LOAD,
					!!(c->lss_mode & (1 << Q6_LOAD_BIT)));
	}
}

/* Sather Infamous Table, pretty much verbatim
									WRITE
	-----------READ PULSE---------		-—------NO READ PULSE--------
	----SHIFT-----	-----LOAD-----		----SHIFT-----	-----LOAD-----
SEQ --QA'-	--QA--	--QA'-	--QA--		 --QA'-	--QA--	--QA'-	--QA--
0-	18-NOP	18-NOP	18-NOP	18-NOP		18-NOP	18-NOP	18-NOP	18-NOP
1-	28-NOP	28-NOP	28-NOP	28-NOP		28-NOP	28-NOP	28-NOP	28-NOP
2-	39-SL0	39-SL0	3B-LD	3B-LD		39-SL0	39-SL0	3B-LD	3B-LD
3-	48-NOP	48-NOP	48-NOP	48-NOP		48-NOP	48-NOP	48-NOP	48-NOP
4-	58-NOP	58-NOP	58-NOP	58-NOP		58-NOP	58-NOP	58-NOP	58-NOP
5-	68-NOP	68-NOP	68-NOP	68-NOP		68-NOP	68-NOP	68-NOP	68-NOP
6-	78-NOP	78-NOP	78-NOP	78-NOP		78-NOP	78-NOP	78-NOP	78-NOP
7-	08-NOP	88-NOP	08-NOP	88-NOP		08-NOP	88-NOP	08-NOP	88-NOP
8-	98-NOP	98-NOP	98-NOP	98-NOP		98-NOP	98-NOP	98-NOP	98-NOP
9-	A8-NOP	A8-NOP	A8-NOP	A8-NOP		A8-NOP	A8-NOP	A8-NOP	A8-NOP
A-	B9-SL0	B9-SL0	BB-LD	BB-LD		B9-SL0	B9-SL0	BB-LD	BB-LD
B-	C8-NOP	C8-NOP	C8-NOP	C8-NOP		C8-NOP	C8-NOP	C8-NOP	C8-NOP
C-	D8-NOP	D8-NOP	D8-NOP	D8-NOP		D8-NOP	D8-NOP	D8-NOP	D8-NOP
D-	E8-NOP	E8-NOP	E8-NOP	E8-NOP		E8-NOP	E8-NOP	E8-NOP	E8-NOP
E-	F8-NOP	F8-NOP	F8-NOP	F8-NOP		F8-NOP	F8-NOP	F8-NOP	F8-NOP
F-	88-NOP	08-NOP	88-NOP	08-NOP		88-NOP	08-NOP	88-NOP	08-NOP
									READ
	------------SHIFT------------		-------------LOAD------------
	-----QA'------	------QA------		-----QA'------	-----QA------
	--RP--	-NORP-	--RP--	-NORP-		--RP--	-NORP-	--RP--	-NORP-
0-	18-NOP	18-NOP	18-NOP	18-NOP		0A-SR	0A-SR	0A-SR	0A-SR
1-	2D-SL1	2D-SL1	38-NOP	38-NOP		0A-SR	0A-SR	0A-SR	0A-SR
2-	D8-NOP	38-NOP	08-NOP	28-NOP		0A-SR	0A-SR	0A-SR	0A-SR
3-	D8-NOP	48-NOP	48-NOP	48-NOP		0A-SR	0A-SR	0A-SR	0A-SR
4-	D8-NOP	58-NOP	D8-NOP	58-NOP		0A-SR	0A-SR	0A-SR	0A-SR
5-	D8-NOP	68-NOP	D8-NOP	68-NOP		0A-SR	0A-SR	0A-SR	0A-SR
6-	D8-NOP	78-NOP	D8-NOP	78-NOP		0A-SR	0A-SR	0A-SR	0A-SR
7-	D8-NOP	88-NOP	D8-NOP	88-NOP		0A-SR	0A-SR	0A-SR	0A-SR
8-	D8-NOP	98-NOP	D8-NOP	98-NOP		0A-SR	0A-SR	0A-SR	0A-SR
9-	D8-NOP	29-SL0	D8-NOP	A8-NOP		0A-SR	0A-SR	0A-SR	0A-SR
A-	CD-SL1	BD-SL1	D8-NOP	B8-NOP		0A-SR	0A-SR	0A-SR	0A-SR
B-	D9-SL0	59-SL0	D8-NOP	C8-NOP		0A-SR	0A-SR	0A-SR	0A-SR
C-	D9-SL0	D9-SL0	D8-NOP	A0-CLR		0A-SR	0A-SR	0A-SR	0A-SR
D-	D8-NOP	08-NOP	E8-NOP	E8-NOP		0A-SR	0A-SR	0A-SR	0A-SR
E-	FD-SL1	FD-SL1	F8-NOP	F8-NOP		0A-SR	0A-SR	0A-SR	0A-SR
F-	DD-SL1	4D-SL1	E0-CLR	E0-CLR		0A-SR	0A-SR	0A-SR	0A-SR
*/
enum {
	WRITE 	= (1 << Q7_WRITE_BIT),
	LOAD 	= (1 << Q6_LOAD_BIT),
	QA1 	= (1 << QA_BIT),
	RP1 	= (1 << RP_BIT),
	/* This keeps the transposed table more readable, as it looks like the book */
	READ = 0, SHIFT = 0, QA0 = 0, RP0 = 0,
};
// And this is the same Sather table (Figure 9.11, page 9-20) but transposed
static const uint8_t lss_rom16s[16][16] = {
[WRITE|RP0|SHIFT|QA0]={	0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x88 },
[WRITE|RP0|SHIFT|QA1]={	0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x08 },
[WRITE|RP0|LOAD|QA0]={	0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x88 },
[WRITE|RP0|LOAD|QA1]={	0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x08 },
[WRITE|RP1|SHIFT|QA0]={	0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x88 },
[WRITE|RP1|SHIFT|QA1]={	0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x08 },
[WRITE|RP1|LOAD|QA0]={	0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x88 },
[WRITE|RP1|LOAD|QA1]={	0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x08 },
[READ|SHIFT|QA0|RP1]={	0x18,0x2D,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xCD,0xD9,0xD9,0xD8,0xFD,0xDD },
[READ|SHIFT|QA0|RP0]={	0x18,0x2D,0x38,0x48,0x58,0x68,0x78,0x88,0x98,0x29,0xBD,0x59,0xD9,0x08,0xFD,0x4D },
[READ|SHIFT|QA1|RP1]={	0x18,0x38,0x08,0x48,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xE8,0xF8,0xE0 },
[READ|SHIFT|QA1|RP0]={	0x18,0x38,0x28,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xB8,0xC8,0xA0,0xE8,0xF8,0xE0 },
[READ|LOAD|QA0|RP1]={	0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A },
[READ|LOAD|QA0|RP0]={	0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A },
[READ|LOAD|QA1|RP1]={	0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A },
[READ|LOAD|QA1|RP0]={	0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A },
};

static const uint8_t SEQUENCER_ROM_16[256] __attribute__((unused)) = {
    // See Understanding the Apple IIe, Figure 9.11 The DOS 3.3 Logic State Sequencer
    // Note that the column order here is NOT the same as in Figure 9.11 for Q7 H (Write).
    //
    //                Q7 L (Read)                                     Q7 H (Write)
    //    Q6 L (Shift)            Q6 H (Load)             Q6 L (Shift)             Q6 H (Load)
    //  QA L        QA H        QA L        QA H        QA L        QA H        QA L        QA H
    // 1     0     1     0     1     0     1     0     1     0     1     0     1     0     1     0
    0x18, 0x18, 0x18, 0x18, 0x0A, 0x0A, 0x0A, 0x0A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0
    0x2D, 0x2D, 0x38, 0x38, 0x0A, 0x0A, 0x0A, 0x0A, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, // 1
    0xD8, 0x38, 0x08, 0x28, 0x0A, 0x0A, 0x0A, 0x0A, 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B, // 2
    0xD8, 0x48, 0x48, 0x48, 0x0A, 0x0A, 0x0A, 0x0A, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, // 3
    0xD8, 0x58, 0xD8, 0x58, 0x0A, 0x0A, 0x0A, 0x0A, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, // 4
    0xD8, 0x68, 0xD8, 0x68, 0x0A, 0x0A, 0x0A, 0x0A, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, // 5
    0xD8, 0x78, 0xD8, 0x78, 0x0A, 0x0A, 0x0A, 0x0A, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, // 6
    0xD8, 0x88, 0xD8, 0x88, 0x0A, 0x0A, 0x0A, 0x0A, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, // 7
    0xD8, 0x98, 0xD8, 0x98, 0x0A, 0x0A, 0x0A, 0x0A, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, // 8
    0xD8, 0x29, 0xD8, 0xA8, 0x0A, 0x0A, 0x0A, 0x0A, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, // 9
    0xCD, 0xBD, 0xD8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A, 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, // A
    0xD9, 0x59, 0xD8, 0xC8, 0x0A, 0x0A, 0x0A, 0x0A, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, // B
    0xD9, 0xD9, 0xD8, 0xA0, 0x0A, 0x0A, 0x0A, 0x0A, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, // C
    0xD8, 0x08, 0xE8, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, // D
    0xFD, 0xFD, 0xF8, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, // E
    0xDD, 0x4D, 0xE0, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08  // F
	// 0     1     1     3     4	 5     6     7     8     9     A     B     C     D     E     F
};

FILE *debug = NULL;

static void
_mii_disk2_lss_tick(
	mii_card_disk2_t *c )
{
	if (c->lss_skip) {
		c->lss_skip--;
		return;
	}
	mii_floppy_t *f = &c->floppy[c->selected];

	if (c->vcd)
		c->vcd->cycle += 1;
	c->clock += 4;	// 4 is 0.5us.. we run at 2MHz

	uint8_t 	track_id 	= f->track_id[f->qtrack];
	uint8_t * 	track 		= f->track_data[track_id];
	uint32_t 	byte_index 	= f->bit_position >> 3;
	uint8_t 	bit_index 	= 7 - (f->bit_position & 7);
	uint8_t 	rp 			= 0;

	mii_raise_signal(c->sig + SIG_LSS_CLK, c->clock >= f->bit_timing);
	if (c->clock >= f->bit_timing) {
		uint8_t 	bit 	= track[byte_index];
		bit = (bit >> bit_index) & 1;
		c->head = (c->head << 1) | bit;
		// see WOZ spec for how we do this here
		if ((c->head & 0xf) == 0) {
			bit = f->track_data[MII_FLOPPY_NOISE_TRACK][byte_index];
			rp = (bit >> bit_index) & 1;
//				printf("RANDOM TRACK %2d %2d %2d : %d\n",
//						track_id, byte_index, bit_index, bit);
			mii_raise_signal_float(c->sig + SIG_LSS_RANDOM, rp, 0);
		} else {
			rp = (c->head >> 1) & 1;
			mii_raise_signal_float(c->sig + SIG_LSS_RANDOM, rp, 1);
		}
	}
	c->lss_mode = (c->lss_mode & ~(1 << RP_BIT)) | (rp << RP_BIT);
	c->lss_mode = (c->lss_mode & ~(1 << QA_BIT)) |
						(!!(c->data_register & 0x80) << QA_BIT);

	mii_raise_signal(c->sig + SIG_LSS_RP, rp);
	mii_raise_signal(c->sig + SIG_LSS_QA,
				!!(c->lss_mode & (1 << QA_BIT)));
	mii_raise_signal(c->sig + SIG_LSS_WRITE,
				!!(c->lss_mode & (1 << Q7_WRITE_BIT)));
	mii_raise_signal(c->sig + SIG_LSS_LOAD,
				!!(c->lss_mode & (1 << Q6_LOAD_BIT)));

	const uint8_t *rom 	= lss_rom16s[c->lss_mode];
	uint8_t cmd 	= rom[c->lss_state];
	uint8_t next 	= cmd >> 4;
	uint8_t action 	= cmd & 0xF;

	mii_raise_signal(c->sig + SIG_LSS_SEQ, c->lss_state);
	mii_raise_signal(c->sig + SIG_LSS_CMD, action);

	if (action & 0b1000) {	// Table 9.3 in Sather's book
		switch (action & 0b0011) {
			case 1:	// SL0/1
				c->data_register <<= 1;
				c->data_register |= !!(action & 0b0100);
				mii_raise_signal(c->sig + SIG_DR, c->data_register);
				break;
			case 2:	// SR
				c->data_register = (c->data_register >> 1) |
									(!!f->write_protected << 7);
				mii_raise_signal(c->sig + SIG_DR, c->data_register);
				break;
			case 3:	{// LD
				uint8_t 	track_id = f->track_id[f->qtrack];
				c->data_register = c->write_register;
				mii_raise_signal(c->sig + SIG_DR, c->data_register);
				f->seed_dirty++;
				if (f->heat) {
					uint32_t 	byte_index 	= f->bit_position >> 3;
					unsigned int dstb = byte_index/MII_FLOPPY_HM_HIT_SIZE;
					f->heat->write.map[track_id][dstb] = 255;
					f->heat->write.seed++;
				}
			}	break;
		}
	} else {	// CLR
		c->data_register = 0;
		mii_raise_signal(c->sig + SIG_DR, c->data_register);
	}
	if ((c->lss_mode & (1 << Q7_WRITE_BIT)) &&
					track_id < MII_FLOPPY_TRACK_COUNT) {
		// on state 0 and 8 we write a bit...
		if ((c->lss_state & 0b0111) == 0) {
			uint8_t bit = c->data_register >> 7;
//			uint8_t bit = !!(c->lss_state & 0x8);
			mii_raise_signal_float(c->sig + SIG_LSS_WB, 1, 0);
			if (!f->tracks[track_id].dirty) {
			//	printf("DIRTY TRACK %2d \n", track_id);
				f->tracks[track_id].dirty = 1;
				/*
				* This little trick allows to have all the track neatly aligned
				* on bit zero when formatting a floppy or doing a copy, this helps
				* debug quite a bit.
				*/
				if (f->tracks[track_id].virgin) {
					f->tracks[track_id].virgin = 0;
					f->bit_position = 0;
				//	if (track_id == 0)
				//		_mii_disk2_vcd_debug(c, 1);
				}
				f->seed_dirty++;
			}
			f->track_data[track_id][byte_index] &= ~(1 << bit_index);
			f->track_data[track_id][byte_index] |= (bit << bit_index);
		} else {
			mii_raise_signal_float(c->sig + SIG_LSS_WB, 0, 0);
		}
	}

	c->lss_state = next;
	if (c->clock >= f->bit_timing) {
		c->clock -= f->bit_timing;
		f->bit_position = (f->bit_position + 1) % f->tracks[track_id].bit_count;
	}
}

#include <fcntl.h>
#include <unistd.h>

static void
_mii_mish_d2(
		void * param,
		int argc,
		const char * argv[])
{
//	mii_t * mii = param;
	if (!_mish_d2) {
		printf("No Disk ][ card installed\n");
		return;
	}
	static int sel = 0;
	if (!argv[1] || !strcmp(argv[1], "list")) {
		mii_card_disk2_t *c = _mish_d2;
		for (int i = 0; i < 2; i++) {
			mii_floppy_t *f = &c->floppy[i];
			printf("Drive %d %s\n", f->id, f->write_protected ? "WP" : "RW");
			printf("\tMotor: %3s qtrack:%3d Bit %6d/%6d\n",
					f->motor ? "ON" : "OFF", f->qtrack,
					f->bit_position, f->tracks[0].bit_count);
		}
		return;
	}
	if (!strcmp(argv[1], "sel")) {
		if (argv[2]) {
			sel = atoi(argv[2]);
		}
		printf("Selected drive: %d\n", sel);
		return;
	}
	if (!strcmp(argv[1], "wp")) {
		if (argv[2]) {
			int wp = atoi(argv[2]);
			mii_card_disk2_t *c = _mish_d2;
			mii_floppy_t *f = &c->floppy[sel];
			f->write_protected = wp;
		}
		printf("Drive %d Write protected: %d\n", sel,
				_mish_d2->floppy[sel].write_protected);
		return;
	}
	// dump a track, specify track number and number of bytes
	if (!strcmp(argv[1], "track")) {
		mii_card_disk2_t *c = _mish_d2;
		mii_floppy_t *f = &c->floppy[sel];
		if (argv[2]) {
			int track = atoi(argv[2]);
			if (track < 0 || track >= MII_FLOPPY_TRACK_COUNT) {
				printf("Invalid track %d\n", track);
				return;
			}
			int count = 256;
			if (argv[3]) {
				if (!strcmp(argv[3], "save")) {
					// save one binary file in tmp with just that track
					uint8_t *data = f->track_data[track];
					char *filename;
					asprintf(&filename, "/tmp/track_%02d.bin", track);
					int fd = open(filename, O_CREAT | O_WRONLY, 0666);
					write(fd, data, MII_FLOPPY_DEFAULT_TRACK_SIZE);
					close(fd);
					printf("Saved track %d to %s\n", track, filename);
					free(filename);
					return;
				}
				count = atoi(argv[3]);
			}
			uint8_t *data = f->track_data[track];

			for (int i = 0; i < count; i += 8) {
				uint8_t *line = data + i;
			#if 1
				for (int bi = 0; bi < 8; bi++) {
					uint8_t b = line[bi];
					for (int bbi = 0; bbi < 8; bbi++) {
						printf("%c", (b & 0x80) ? '1' : '0');
						b <<= 1;
					}
				}
				printf("\n");
			#endif
				for (int bi = 0; bi < 8; bi++)
					printf("%8x", line[bi]);
				printf("\n");
			}
		} else {
			printf("track <track 0-34> [count]\n");
		}
		return;
	}
	if (!strcmp(argv[1], "dirty")) {
		mii_card_disk2_t *c = _mish_d2;
		mii_floppy_t *f = &c->floppy[sel];
		f->seed_dirty = f->seed_saved = rand();
		return;
	}
	if (!strcmp(argv[1], "resync")) {
		mii_card_disk2_t *c = _mish_d2;
		mii_floppy_t *f = &c->floppy[sel];
		printf("Resyncing tracks\n");
		for (int i = 0; i < MII_FLOPPY_TRACK_COUNT; i++)
			mii_floppy_resync_track(f, i, 0);
		return;
	}
	if (!strcmp(argv[1], "map")) {
		mii_card_disk2_t *c = _mish_d2;
		mii_floppy_t *f = &c->floppy[sel];

		printf("Disk map:\n");
		for (int i = 0; i < MII_FLOPPY_TRACK_COUNT; i++) {
			mii_floppy_track_map_t map = {};
			int r = mii_floppy_map_track(f, i, &map, 0);
			printf("Track %2d: %7s\n",
					i, r == 0 ? "OK" : "Invalid");
			if (r != 0) {
				printf("Track %d has %5d bits\n", i, f->tracks[i].bit_count);
				for (int si = 0; si < 16; si++)
					printf("[%2d hs:%4d h:%5d ds:%3d d:%5d]%s",
							si, map.sector[si].hsync,
							map.sector[si].header,
							map.sector[si].dsync,
							map.sector[si].data,
							si & 1 ? "\n" : " ");
			}
		}
		return;
	}
	if (!strcmp(argv[1], "trace")) {
		if (debug == NULL)
			debug = fopen("/tmp/disk2.log", "w");
		else {
			fclose(debug);
			debug = NULL;
		}
		printf("Debug trace %s\n", debug ? "ON" : "OFF");
		return;
	}
	if (!strcmp(argv[1], "vcd")) {
		mii_card_disk2_t *c = _mish_d2;
		_mii_disk2_vcd_debug(c, !c->vcd);
		return;
	}
}

#include "mish.h"

MISH_CMD_NAMES(d2, "d2");
MISH_CMD_HELP(d2,
		"d2: disk ][ internals",
		" <default>: dump status",
		" list: list drives",
		" sel [0-1]: select drive",
		" wp [0-1]: write protect",
		" track <track 0-34> [count]: dump track",
		" track <track 0-34> save: save in /tmp/trackXX.bin",
		" dirty: mark track as dirty",
		" resync: resync all tracks",
		" map: show track map",
		" trace: toggle debug trace",
		" vcd: toggle VCD debug"
		);
MII_MISH(d2, _mii_mish_d2);
