/*
 * abcdisk.c
 *
 * Emulate an ABC80/800 disk controller
 */

#include <string.h>
#include <stdio.h>

#include "fw.h"
#include "io.h"
#include "abcio.h"
#include "console.h"
#include "sdcard.h"
#include "ff.h"

/* Option flags, mostly for debugging */
#define NOTTHERE	0
#define READONLY	0
#define INTERLEAVE	0
#define FORMAT_SUPPORT	0

enum drive_flags {
    DF_MOUNTED   = 1,		/* Host file open, drive exported */
    DF_READONLY  = 2,		/* Drive is readonly */
    DF_DISABLED  = 4,		/* Drive is disabled */
    DF_DIRTY     = 8		/* Disk has been written */
};

enum pending {
    PEND_IO       = 1,		/* I/O transfer complete */
    PEND_STARTCMD = 2,		/* C1# = go to command state */
    PEND_RESET    = 4		/* C3# or RST# = local/global reset */
};

/*
 * Status bits in INP(1); bits 5 & 2 are unknown, bit 4 is apparently
 * always 0 on at least card 66 81046-02 (55 21046-x1)...
 * "fast DMA controller".
 */
enum abc_status {
    AS_CMD     = 0x80,		/* Start of command */
    AS_WRITE   = 0x40,		/* Data direction OUT(0) (to controller) */
    AS_ERROR   = 0x08,		/* INP(0) contains error info */
    AS_READING = 0x04,		/* Waiting for sector load */
    AS_READY   = 0x01		/* Ready for input */
};

/* Number of fragments (extents) we can memoize */
#define MAX_FAST_FRAGMENTS 16
#define CLTBL_SIZE ((MAX_FAST_FRAGMENTS+1)*2)

/* Per-drive state */
struct drive_state {
    FIL file;			/* Host file */
    char name[4];               /* Drive name */
    uint16_t sectors;		/* Total size in 256-byte sectors */
#if INTERLEAVE
    uint8_t ilmsk, ilfac;       /* Software interleaving parameters */
#endif
    enum drive_flags flags;
    enum drive_flags force;	/* Option to force flags */
    DWORD cltbl[CLTBL_SIZE];	/* Extent map */
};

/* Fixed parameters for each controller */
struct ctl_params {
    uint8_t clustshift;		/* log2(clustersize/256) */
    uint8_t devsel;		/* I/O device select */
    uint16_t maxsectors;	/* Maximum sectors for this controller */
    uint8_t c, h, s;		/* Disk geometry */
    bool newaddr;		/* "New addressing" */
    const char name[4];		/* Name of controller (disk type) */
#if INTERLEAVE
    uint8_t ilmsk, ilfac;	/* Software interleaving parea */
#endif
#if FORMAT_SUPPORT
    bool fmtdata_i_buf;	/* Use user-provided formatting data */
#endif
} __attribute__((aligned(4)));

/* Per-controller state */
struct ctl_state {
    struct abc_dev iodev;
    const struct ctl_params *params;
    uint8_t k[4];		/* Command bytes */
    uint8_t drives;		/* Total drives present */
    uint8_t error;		/* Error status */
    bool initialized;	        /* Controller initialized */
    volatile enum pending pending;	/* Need to do I/O */
    struct drive_state drv[8];	/* Per-drive state */
    uint8_t buf[4][256];	/* 4 host buffers @ 256 bytes */
};

/*
 * Error codes for INP(0), derived from FD1791 status codes
 *
 * DOSGEN depends on OUT_OF_RANGE having this value... and different
 * DOSGEN expect different values. If this value is wrong, DOSGEN will
 * spin forever on "testing sector..."
 */
#define OUT_OF_RANGE   0x21	/* Status code for an invalid sector */
#define DISK_NOT_READY 0x80
#define WRITE_PROTECT  0x40
#define WRITE_FAULT    0x20
#define CRC_ERROR      0x08

enum controller_types {
    MOx,
    MFx,
    SFx,
    HDx,
    CONTROLLER_TYPES
};

#if INTERLEAVE
# define IL(mask, fac) .ilmsk = (mask), .ilfac = (fac),
#else
# define IL(mask, fac)
#endif

static const struct ctl_params parameters[CONTROLLER_TYPES] = {
    /*
     * MOx: covers all of these formats:
     * SSSD = 40×8×1 (80K, FD2/DD80),
     * SSDD = 40×16×1 (160K, FD2D/DD82/ABC830) and
     * DSDD = 40×16×2 (320K, FD4D/DD84/DD52)
     */
    [MOx] = {
	.devsel = 45,
	.clustshift = 0,
	.maxsectors = 40 * 2 * 16,
	.c = 40, .h = 2, .s = 16,
	.name = "mo",
#if FORMAT_SUPPORT
	.fmtdata_in_buf = true,
#endif
	IL(15, 7)
    },

    /* MFx: DSQD = 80×16x2 (640K, ABC832/834) */
    [MFx] = {
	.devsel = 44,
	.clustshift = 2,
	.maxsectors = 80 * 2 * 16,
	.c = 80, .h = 2, .s = 16,
	.name = "mf"
    },

    /* SFx: 8" floppy (DD88, ABC838) */
    [SFx] = {
	.devsel = 46,
	.clustshift = 2,
	.maxsectors = (77 * 2 - 1) * 26, /* Track 0, side 0 not used */
	.c = 77, .h = 2, .s = 26,
	.name = "sf"
    },

    [HDx] = {
	.devsel = 36,
	.clustshift = 5,
	.newaddr = true,            /* Actually irrelevant for clustshift = 5 */
	.maxsectors = (239 * 8 - 1) * 32,     /* Maximum supported by UFD-DOS */
	.c = 238, .h = 16, .s = 64,
	.name = "hd"
    }
};

static struct ctl_state __dram_bss controllers[CONTROLLER_TYPES];

static inline bool mounted(const struct drive_state *drv)
{
    return !!(drv->flags & DF_MOUNTED);
}

static inline struct drive_state *cur_drv_mutable(struct ctl_state *state)
{
    return &state->drv[state->k[1] & 7];
}

static inline const struct drive_state *cur_drv(const struct ctl_state *state)
{
    return &state->drv[state->k[1] & 7];
}

static inline unsigned int cur_sector(const struct ctl_state *state)
{
    uint8_t k2 = state->k[2], k3 = state->k[3];

    if (state->params->newaddr)
        return (k2 << 8) + k3;
    else
        return (((k2 << 3) + (k3 >> 5)) << state->params->clustshift)
	    + (k3 & 31);
}

/* Get physical sector number, after interleaving */
static inline unsigned int
virt2phys(const struct drive_state *drv, unsigned int sector)
{
#if INTERLEAVE
    unsigned int ilmsk = drv->ilmsk;
    unsigned int ilfac = drv->ilfac;

    sector = (sector & ~ilmsk) | ((sector * ilfac) & ilmsk);
#endif
    return sector;
}

static inline unsigned int phys_sector(const struct ctl_state *state)
{
    return virt2phys(cur_drv(state), cur_sector(state));
}

static inline unsigned int file_pos(const struct ctl_state *state)
{
    return phys_sector(state) << 8;
}

static inline bool file_pos_valid(const struct ctl_state *state)
{
    return phys_sector(state) < cur_drv(state)->sectors;
}

static inline bool cur_sector_valid(const struct ctl_state *state)
{
    uint8_t k3 = state->k[3];

    if (!state->params->newaddr && ((k3 & 31) >> state->params->clustshift))
	return false;

    return phys_sector(state) < state->params->maxsectors;
}

static inline uint8_t *cur_buf(struct ctl_state *state)
{
    return state->buf[state->k[1] >> 6];
}

static void disk_start_command(struct ctl_state *state)
{
#if 0
    con_printf("%-2s:  start command\n", state->params->name);
#endif

    memset(&state->k, 0xee, sizeof state->k);
    state->iodev.callback_mask |= 1 << 0;
    abc_setup_out_queue(&state->iodev, state->k, 4,
			AS_CMD|AS_READY|AS_WRITE|
			(state->error ? AS_ERROR : 0));
}

static void sync_drives(struct ctl_state *state)
{
    for (int i = 0; i < 8; i++) {
	struct drive_state *drv = &state->drv[i];
	if (drv->flags & DF_DIRTY) {
	    f_sync(&state->drv[i].file);
	    drv->flags &= ~DF_DIRTY;
	}
    }
}

static void disk_set_error(struct ctl_state *state, unsigned int error)
{
    abc_set_inp_default(&state->iodev, state->error = error);
    state->k[0] = 0;		/* Abort remaining command sequence */
}

static void disk_reset_state(struct ctl_state *state)
{
    abc_set_inp_status(&state->iodev, 0);
    disk_set_error(state, 0);
    disk_start_command(state);
}

static struct drive_state *
name_to_drive(const char *drive)
{
    struct ctl_state *state;
    unsigned int ndrive;

    /* All drive names are three letters long */
    if (strlen(drive) != 3)
	return NULL;

    ndrive = drive[2] - '0';
    if (ndrive > 7)
	return NULL;

    if (!memcmp("dr", drive, 2)) {
	/* DRx alias for MOx (matches "old DOS") */
	return &controllers[MOx].drv[ndrive];
    }

    for (int i = 0; i < CONTROLLER_TYPES; i++) {
	struct ctl_state *state = &controllers[i];

	if (!memcmp(state->params->name, drive, 2))
	    return &state->drv[ndrive];
    }

    return NULL;		/* No such disk */
}

bool valid_drive_name(const char *drive)
{
    return name_to_drive(drive) != NULL;
}

static int mount_drive(struct drive_state *drv, struct ctl_state *state,
		       const char *filename)
{
    FRESULT rv;

    if (mounted(drv)) {
	if (!(sdc.fsstatus & STA_NOINIT))
	    f_close(&drv->file);
	drv->flags &= ~DF_MOUNTED;
	state->drives--;
    }

    if (drv->force & DF_DISABLED)
	return -1;

    drv->flags = drv->force & ~DF_MOUNTED;

    if (!filename) {
	return 0;		/* Explicit unmount */
    } else {
	while (1) {
	    BYTE mode = FA_OPEN_EXISTING | FA_READ;

	    if (!(drv->flags & DF_READONLY))
		mode |= FA_WRITE;

	    rv = f_open(&drv->file, filename, mode);

	    if (rv == FR_WRITE_PROTECTED && (mode & FA_WRITE)) {
		drv->flags |= DF_READONLY;
		continue;
	    }

	    drv->flags |= (rv == FR_OK) ? DF_MOUNTED : DF_DISABLED;
	    break;
	}

	if (!(drv->flags & DF_MOUNTED))
	    return -1;
    }

    con_printf("abcdisk: %-3s = %s\n", drv->name, filename);

    /* Try to memoize extents for fast seek */
    drv->cltbl[0]   = CLTBL_SIZE;
    drv->file.cltbl = drv->cltbl;

    rv = f_lseek(&drv->file, CREATE_LINKMAP);
    if (rv != FR_OK) {
	con_printf("abcdisk: %-3s ! file too fragmented, will be slow\n",
		   drv->name);
    }

    /*
     * Smaller than the standard disk size?  Treat the sectors
     * beyond the end as bad.
     */
    unsigned int filesec = f_size(&drv->file) >> 8;
    drv->sectors = min(filesec, state->params->maxsectors);

    /* Interleaving parameters */
#if INTERLEAVE
    drv->ilfac = state->params->ilfac;
    drv->ilmsk = state->params->ilmsk;
#endif

    state->drives++;
    return 0;
}

static void unmount_drives(struct ctl_state *state)
{
    int i;

    for (int i = 0; i < 8; i++) {
	struct drive_state *drv = &state->drv[i];
	if (drv->flags & DF_MOUNTED)
	    mount_drive(drv, state, NULL);
    }
}

/* RST# = global reset, C3# = local reset, C1# = goto command start */
#define IDLE_CALLBACK_MASK	((1 << 7)|(1 << 4)|(1 << 2))

static ABC_CALLBACK(abcdisk_callback_out)
{
    struct ctl_state *state = container_of(dev, struct ctl_state, iodev);

    dev->callback_mask = IDLE_CALLBACK_MASK;
    ABC_INP1_DATA = dev->inp_data[1] = 0;
    state->error = 0;
    state->pending |= PEND_IO;
}

static ABC_CALLBACK(abcdisk_callback_inp)
{
    struct ctl_state *state = container_of(dev, struct ctl_state, iodev);

    dev->callback_mask = IDLE_CALLBACK_MASK;
    ABC_INP1_DATA = dev->inp_data[1] = 0;
    state->error = 0;
    state->pending |= PEND_IO;
}

static ABC_CALLBACK(abcdisk_callback_restart)
{
    struct ctl_state *state = container_of(dev, struct ctl_state, iodev);
    state->pending |= PEND_STARTCMD;
}

static ABC_CALLBACK(abcdisk_callback_rst)
{
    struct ctl_state *state = container_of(dev, struct ctl_state, iodev);
    state->pending |= PEND_RESET;
}

static char abcdisk_80_800[] = "/abcdisk.800/";

static const char * const disk_pfx[] = {
    abcdisk_80_800, "/abcdisk/", "/abcdisk.", "/", NULL
};

static void init_drives(struct ctl_state *state)
{
    uint32_t abc_status = ABC_STATUS;
    char *p80;
    int i;

    /* .80/ or .800/ for the first entry */
    p80 = abcdisk_80_800 + 10 + !!(abc_status & ABC_STATUS_800);
    p80[0] = '0';
    p80[1] = '/';
    p80[2] = '\0';

    for (i = 0; i < 8; i++) {
	struct drive_state *drv = &state->drv[i];
	unsigned int filesec;
	const char * const *pfx;

	snprintf(drv->name, sizeof drv->name, "%-.2s%c",
		 state->params->name, i + '0');

	for (pfx = disk_pfx; *pfx; pfx++) {
	    char filename_buf[64];
	    snprintf(filename_buf, sizeof filename_buf,
		     "%s%s", *pfx, drv->name);

	    if (!mount_drive(drv, state, filename_buf))
		break;
	}
    }

    state->initialized = true;
}

static void do_next_command(struct ctl_state *state)
{
    struct drive_state *drv = cur_drv_mutable(state);
    uint8_t *buf = cur_buf(state);

#if 0
    con_printf("%-3s: cmd %02x %02x %02x %02x\n",
	       drv->name, state->k[0], state->k[1],
	       state->k[2], state->k[3]);
#endif
    if (state->k[0] & 0x01) {
	/* READ SECTOR */
	if (!(drv->flags & DF_MOUNTED)) {
	    disk_set_error(state, DISK_NOT_READY);
	} else if (!cur_sector_valid(state)) {
	    disk_set_error(state, OUT_OF_RANGE);
	} else {
	    UINT rlen = 0;
	    FRESULT rv;

	    set_led(LED_DISKIO, true);

	    abc_set_inp_status(&state->iodev, AS_READING|AS_READY);

	    rv = f_lseek(&drv->file, file_pos(state));
	    if (rv == FR_OK)
		rv = f_read(&drv->file, buf, 256, &rlen);
	    if (rv != FR_OK || rlen != 256) {
		disk_set_error(state, CRC_ERROR);
	    }
	}
	state->k[0] &= ~0x01;   /* Command done */
    }
    if (state->k[0] & 0x02) {
	/* SECTOR TO HOST */
	state->k[0] &= ~0x02;   /* Command done */
	state->iodev.callback_mask |= 1 << 8;
	abc_setup_inp_queue(&state->iodev, buf, 256, AS_READY);
	return;
    }
    if (state->k[0] & 0x04) {
	/* SECTOR FROM HOST */
	state->k[0] &= ~0x04;   /* Command done */
	state->iodev.callback_mask |= 1 << 0;
	abc_setup_out_queue(&state->iodev, buf, 256, AS_WRITE|AS_READY);
	return;
    }
    if (state->k[0] & 0x08) {
	/* WRITE SECTOR */
	if (!(drv->flags & DF_MOUNTED)) {
	    disk_set_error(state, DISK_NOT_READY);
	} else if (drv->flags & DF_READONLY) {
	    disk_set_error(state, WRITE_PROTECT);
	} else if (!cur_sector_valid(state)) {
	    disk_set_error(state, OUT_OF_RANGE);
	} else {
	    UINT wlen = 0;
	    FRESULT rv;

	    set_led(LED_DISKIO, true);

	    rv = f_lseek(&drv->file, file_pos(state));
	    if (rv == FR_OK) {
		drv->flags |= DF_DIRTY;
		rv = f_write(&drv->file, buf, 256, &wlen);
	    }
	    if (rv != FR_OK || wlen != 256)
		disk_set_error(state, WRITE_FAULT);
	}
	state->k[0] &= ~0x08;   /* Command done */
    }

#if FORMAT_SUPPORT
    /* This code needs additional work */

    if (state->k[0] & 0x10 && state->k[1] & 0x08) {
	state->out_ptr = 0;
	/* FORMAT */
	if (!drv->hf) {
	    state->error = 0x80;	/* Not ready */
	} else if (!file_wrok(hf)) {
	    state->error = 0x40;	/* Write protect */
	} else {
	    unsigned int s, c0, c1, s0, s1;
	    unsigned int cylsec = state->s * state->h;
	    uint8_t data[256];
	    unsigned int fmtsec, filesec;

	    /* Sector count produced by format */
	    fmtsec = state->params->maxsectors;

	    /* For non-MO-drives, this seems to be internally generated */
	    memset(data, 0x40, 256);

	    if (state->fmtdata_in_buf) {
		/*
		 * MO drives put the sector image in the buffers, for
		 * backwards compatibility and to support single density.
		 *
		 * Right before the F7 header CRC opcode is a density byte;
		 * 00 for single, and 01 for double.  The data begins after
		 * a byte of FB.
		 */
		bool single = false;
		const uint8_t *p, *ep;

		ep = state->buf[1];
		for (p = state->buf[0]+1; p < ep; p++) {
		    if (*p == 0xf7)
			single = (p[-1] == 0);
		    if (*p == 0xfb)
			break;
		}

		fmtsec >>= single;

		if (*p++ == 0xfb) { /* Data block found */
		    if (single) {
			/* Really two 128-byte sectors! */
			memcpy(data, p, 128);
			memcpy(data+128, p, 128);
		    } else {
			memcpy(data, p, 256);
		    }
		}
	    }

	    /*
	     * Adjust the size of the accessible device to the smallest
	     * of the physical file and the formatted size
	     */
	    filesec = drv->hf->filesize >> 8;
	    drv->sectors = (filesec && filesec < fmtsec) ? filesec : fmtsec;

	    /*
	     * k2 and k3 contain the first and last cylinder numbers to
	     * format, inclusively.  The last cylinder may be partial due
	     * to virtual remapping, e.g. for sf floppies.
	     */

	    c0 = state->k[2];
	    s0 = c0 * cylsec;
	    c1 = state->k[3] + 1;
	    s1 = c1 * cylsec;

	    if (tracing(TRACE_DISK)) {
		fprintf(tracef, "%s: formatting cyl %u..%u, sectors %u..%u\n",
			drv->name, c0, c1-1, s0, s1-1);
	    }

	    clearerr(hf->f);
	    state->error = 0;

	    for (s = s0; s < s1; s++) {
		unsigned int ps = virt2phys(drv, s);
		if (ps >= drv->sectors) {
		    state->error |= 0x02; /* Track 0/Lost data? */
		    break;
		} else if (hf->map) {
		    memcpy(hf->map + (ps << 8), data, 256);
		} else {
		    fseek(hf->f, ps << 8, SEEK_SET);
		    fwrite(data, 1, 256, hf->f);
		}
	    }
	    if (ferror(hf->f))
		state->error |= 0x20; /* Write fault */
	}
	state->k[1] &= ~0x08;
    }

    if (!(state->k[1] & 0x38))
	state->k[0] &= ~0x10;
#else
    /* No FORMAT support */
    state->k[0] &= ~0x10;
#endif
    state->k[0] &= ~0xe0;	/* Unimplemented commands */

    disk_start_command(state);
}

static FATFS sd_fs;

static int mount_disk(void)
{
    FRESULT rv;
    char label[128];
    uint32_t volid, freeclust;
    FATFS *fs;

    set_led(LED_DISKIO, true);

    rv = f_mount(&sd_fs, "", 1);
    if (rv != FR_OK) {
	con_printf("sdcard: no volume found\n");
	set_led(LED_DISKIO, false);
	return -1;
    }

    label[0] = '\0';
    volid = 0;

    f_getlabel("", label, &volid);
    con_printf("sdcard: volume found, label \"%s\", volid %08x\n", label, volid);

    freeclust = 0;
    f_getfree("", &freeclust, &fs);

    con_printf("sdcard: %u/%u clusters free, clusters = %u bytes\n",
	       freeclust, fs->n_fatent - 2, fs->csize << 9);

    return 0;
}

#define SYNC_TIME (1*TIMER_HZ)	/* 1 s */

/* Called from the main loop */
void abcdisk_io_poll(void)
{
    static uint32_t last_sync;
    static uint32_t last_timer = -1U;
    static uint32_t prev_abc_status = -1U;
    uint32_t abc_status_change;

    uint32_t now = timer_count();
    bool need_sync = false;
    uint32_t abc_status = ABC_STATUS & (ABC_STATUS_LIVE | ABC_STATUS_800);
    bool unmount_all = false;
    bool reset_all = false;

    if (now != last_timer) {
	/*
	 * Periodically try to initialize the SD card if we are waiting
	 * for it. The disk cache, however, will be initialized when fatfs
	 * tries to mount the device.
	 */
	if (sdc.status & STA_NOINIT) {
	    if (!(sdcard_init() & STA_NOINIT))
		mount_disk();
	    unmount_all = true;
	} else if (sdcard_present_poll() & STA_NOINIT) {
	    unmount_all = true;
	} else {
	    if ((now - last_sync) >= SYNC_TIME) {
		need_sync = true;
		last_sync = now;
	    }
	}

	last_timer = now;
    }

    abc_status_change = abc_status ^ prev_abc_status;
    if (unlikely(abc_status_change)) {
	const char *host;

	prev_abc_status = abc_status;

	set_led(LED_ABCBUS, !!(abc_status & ABC_STATUS_LIVE));

	con_puts("ABC-bus host: ");
	con_puts(abc_status & ABC_STATUS_800 ? "ABC800" : "ABC80");
	con_puts(abc_status & ABC_STATUS_LIVE ? " (online)\n" : " (offline)\n");

	unmount_all = !!(abc_status_change & ABC_STATUS_800);
	reset_all   = true;
	need_sync   = true;
    }

    for (int i = 0; i < CONTROLLER_TYPES; i++) {
	struct ctl_state *state = &controllers[i];
	enum pending pending = 0;

	if (unmount_all && state->initialized) {
	    unmount_drives(state);
	    state->initialized = false;
	} else if (!state->initialized && !(sdc.status & STA_NOINIT)) {
	    init_drives(state);
	}

	if (reset_all)
	    disk_reset_state(state);

	if (abc_status & ABC_STATUS_LIVE) {
	    mask_irq(ABC_IRQ);
	    pending = state->pending;
	    state->pending = 0;
	    unmask_irq(ABC_IRQ);

	    if (pending & (PEND_RESET|PEND_STARTCMD))
		disk_reset_state(state);
	    else if (pending & PEND_IO)
		do_next_command(state);
	}

	if (state->initialized && (need_sync || (pending & PEND_RESET)))
	    sync_drives(state);
    }

    if (need_sync) {
	/* Did we sync drives? Clear LED. */
	set_led(LED_DISKIO, false);
    }
}

/*
 * Called during initialization. Don't initialize the SD card here;
 * it will take too long and ABC will time out claiming no drives present.
 */
void abcdisk_init(void)
{
    static const struct abc_dev iodev_template = {
	.callback_mask = IDLE_CALLBACK_MASK,
	.inp_en = 3,
	.status_first_out_mask = (uint8_t)~0x80,
	.status_first_inp_mask = (uint8_t)~0x80,
	.callback_out[0] = abcdisk_callback_out,
	.callback_inp[0] = abcdisk_callback_inp,
	.callback_out[2] = abcdisk_callback_restart,
	.callback_out[4] = abcdisk_callback_rst, /* C3# = local reset */
	.callback_rst    = abcdisk_callback_rst
    };

    for (int i = 0; i < CONTROLLER_TYPES; i++) {
	struct ctl_state *state = &controllers[i];

	state->params = &parameters[i];
	state->iodev  = iodev_template;

	disk_reset_state(state);
	abc_register(&state->iodev, state->params->devsel);
    }
}
