/*

 * All Input is assumed to be going to RAM
 * All Output is assumed to be coming from either RAM or ROM
 *
 */

#include <stdio.h>
#include <stdlib.h>

#ifdef VMS
#include <unixio.h>
#include <file.h>
#else
#include <fcntl.h>
#ifndef AMIGA
#include <unistd.h>
#endif
#endif

#ifdef DJGPP
#include "djgpp.h"
#endif

static char *rcsid = "$Id: sio.c,v 1.9 1998/02/21 14:55:21 david Exp $";

#define FALSE   0
#define TRUE    1

#include "atari.h"
#include "cpu.h"
#include "sio.h"

#define	MAGIC1	0x96
#define	MAGIC2	0x02

#undef ORIGINAL_XFD_CODE		/* new code should support DD XFD images - PS */

struct ATR_Header {
	unsigned char magic1;
	unsigned char magic2;
	unsigned char seccountlo;
	unsigned char seccounthi;
	unsigned char secsizelo;
	unsigned char secsizehi;
	unsigned char hiseccountlo;
	unsigned char hiseccounthi;
	unsigned char gash[8];
};

typedef enum Format {
	XFD, ATR
} Format;

static Format format[MAX_DRIVES];
static int disk[MAX_DRIVES] =
{-1, -1, -1, -1, -1, -1, -1, -1};
static int sectorcount[MAX_DRIVES];
static int sectorsize[MAX_DRIVES];

static enum DriveStatus {
	Off,
	NoDisk,
	ReadOnly,
	ReadWrite
} drive_status[MAX_DRIVES];

char sio_status[256];
char sio_filename[MAX_DRIVES][FILENAME_LEN];

void SIO_Initialise(int *argc, char *argv[])
{
	int i;

	for (i = 0; i < MAX_DRIVES; i++)
		strcpy(sio_filename[i], "Empty");
}

int SIO_Mount(int diskno, char *filename)
{
	struct ATR_Header header;

	int fd;

	drive_status[diskno - 1] = ReadWrite;
	strcpy(sio_filename[diskno - 1], "Empty");

	fd = open(filename, O_RDWR | O_BINARY);
	if (fd == -1) {
		fd = open(filename, O_RDONLY | O_BINARY);
		drive_status[diskno - 1] = ReadOnly;
	}

	if (fd >= 0) {
		int status;

		status = read(fd, &header, sizeof(struct ATR_Header));
		if (status == -1) {
			close(fd);
			disk[diskno - 1] = -1;
			return FALSE;
		}

		strcpy(sio_filename[diskno - 1], filename);

		if ((header.magic1 == MAGIC1) && (header.magic2 == MAGIC2)) {
			sectorcount[diskno - 1] = header.hiseccounthi << 24 |
				header.hiseccountlo << 16 |
				header.seccounthi << 8 |
				header.seccountlo;

			sectorsize[diskno - 1] = header.secsizehi << 8 |
				header.secsizelo;

			sectorcount[diskno - 1] /= 8;
			if (sectorsize[diskno - 1] == 256) {
				sectorcount[diskno - 1] += 3;	/* Compensate for first 3 sectors */
				sectorcount[diskno - 1] /= 2;
			}

#ifdef DEBUG
			printf("ATR: sectorcount = %d, sectorsize = %d\n",
				   sectorcount[diskno - 1],
				   sectorsize[diskno - 1]);
#endif

			format[diskno - 1] = ATR;
		}
		else {
			ULONG file_length = lseek(fd, 0L, SEEK_END);
			format[diskno - 1] = XFD;
#ifndef ORIGINAL_XFD_CODE
			/* XFD might be of double density ! (PS) */
			sectorsize[diskno - 1] = (file_length > (1040 * 128)) ? 256 : 128;
			sectorcount[diskno - 1] = file_length / sectorsize[diskno - 1];
#endif
		}
	}
	else {
		drive_status[diskno - 1] = NoDisk;
	}

	disk[diskno - 1] = fd;

	return (disk[diskno - 1] != -1) ? TRUE : FALSE;
}

void SIO_Dismount(int diskno)
{
	if (disk[diskno - 1] != -1) {
		close(disk[diskno - 1]);
		disk[diskno - 1] = -1;
		drive_status[diskno - 1] = NoDisk;
		strcpy(sio_filename[diskno - 1], "Empty");
	}
}

void SIO_DisableDrive(int diskno)
{
	drive_status[diskno - 1] = Off;
	strcpy(sio_filename[diskno - 1], "Off");
}

void SeekSector(int dskno, int sector)
{
	int offset;

	sprintf(sio_status, "%d: %d", dskno + 1, sector);

	switch (format[dskno]) {
	case XFD:
#ifdef ORIGINAL_XFD_CODE		/* does not support double density XFD disks */
		offset = (sector - 1) * 128;
#else
		offset = (sector - 1) * sectorsize[dskno] + 0;
#endif
		break;

	case ATR:
		if (sector < 4)
			offset = (sector - 1) * 128 + 16;
		else
			offset = (sector - 1) * sectorsize[dskno] + 16;
/*
   offset = 3*128 + (sector-4) * sectorsize[dskno] + 16;
 */
		break;
	default:
		printf("Fatal Error in atari_sio.c\n");
		Atari800_Exit(FALSE);
		exit(1);
	}

	if (offset > lseek(disk[dskno], 0L, SEEK_END)) {
/*
   printf("Seek after end of file\n");
   Atari800_Exit(FALSE);
   exit(1);
 */
	}
	else
		lseek(disk[dskno], offset, SEEK_SET);
}

void SIO(void)
{
	/* UBYTE DDEVIC = memory[0x0300]; */
	UBYTE DUNIT = memory[0x0301];
	UBYTE DCOMND = memory[0x0302];
	/* UBYTE DSTATS = memory[0x0303]; */
	UBYTE DBUFLO = memory[0x0304];
	UBYTE DBUFHI = memory[0x0305];
	/* UBYTE DTIMLO = memory[0x0306]; */
	UBYTE DBYTLO = memory[0x0308];
	UBYTE DBYTHI = memory[0x0309];
	UBYTE DAUX1 = memory[0x030a];
	UBYTE DAUX2 = memory[0x030b];

	int sector;
	int buffer;
	int count;
	int i;

	if (drive_status[DUNIT - 1] != Off) {
		if (disk[DUNIT - 1] != -1) {
			int offset;

			sector = DAUX1 + DAUX2 * 256;
			buffer = DBUFLO + DBUFHI * 256;
			count = DBYTLO + DBYTHI * 256;

			switch (format[DUNIT - 1]) {
			case XFD:
#ifdef ORIGINAL_XFD_CODE
				offset = (sector - 1) * 128 + 0;
#else
				offset = (sector - 1) * sectorsize[DUNIT - 1] + 0;
#endif
				break;
			case ATR:
				if (sector < 4)
					offset = (sector - 1) * 128 + 16;
				else
					offset = (sector - 1) * sectorsize[DUNIT - 1] + 16;
/*
   offset = 3*128 + (sector-4) * sectorsize[DUNIT-1] + 16;
 */
				break;
			default:
				printf("Fatal Error in atari_sio.c\n");
				Atari800_Exit(FALSE);
				exit(1);
			}

			if (offset > lseek(disk[DUNIT - 1], 0L, SEEK_END)) {
/*
   printf("Seek after end of file\n");
   Atari800_Exit(FALSE);
   exit(1);
 */
			}
			else
				lseek(disk[DUNIT - 1], offset, SEEK_SET);

#ifdef DEBUG
			printf("SIO: DCOMND = %x, SECTOR = %d, BUFADR = %x, BUFLEN = %d\n",
				   DCOMND, sector, buffer, count);
#endif

			switch (DCOMND) {
			case 0x50:
			case 0x57:
				if (drive_status[DUNIT - 1] == ReadWrite) {
					write(disk[DUNIT - 1], &memory[buffer], count);
					regY = 1;
					ClrN;
				}
				else {
					regY = 146;
					SetN;
				}
				break;
			case 0x52:
				read(disk[DUNIT - 1], &memory[buffer], count);
				regY = 1;
				ClrN;
				break;
			case 0x21:			/* Single Density Format */
			case 0x22:			/* Duel Density Format */
			case 0x66:			/* US Doubler Format - I think! */
				regY = 1;
				ClrN;
				break;
/*
   Status Request from Atari 400/800 Technical Reference Notes

   DVSTAT + 0   Command Status
   DVSTAT + 1   Hardware Status
   DVSTAT + 2   Timeout
   DVSTAT + 3   Unused

   Command Status Bits

   Bit 0 = 1 indicates an invalid command frame was received
   Bit 1 = 1 indicates an invalid data frame was received
   Bit 2 = 1 indicates that a PUT operation was unsuccessful
   Bit 3 = 1 indicates that the diskete is write protected
   Bit 4 = 1 indicates active/standby

   plus

   Bit 5 = 1 indicates double density
   Bit 7 = 1 indicates duel density disk (1050 format)
 */
			case 0x53:			/* Get Status */
				for (i = 0; i < count; i++) {
					if (sectorsize[DUNIT - 1] == 256)
						memory[buffer + i] = 32 + 16;
					else
						memory[buffer + i] = 16;
				}
				regY = 1;
				ClrN;
				break;
			default:
#ifdef DEBUG
				printf("SIO: DCOMND = %0x\n", DCOMND);
#endif
				regY = 146;
				SetN;
				break;
			}
		}
		else {
			regY = 146;
			SetN;
		}
	}
	else {
		regY = 138;
		SetN;
	}

	memory[0x0303] = regY;
}

static unsigned char cmd_frame[5];
static int ncmd = 0;
static int checksum = 0;

static unsigned char data[256];
static int offst;

static int buffer_offset;
static int buffer_size;

extern int DELAYED_SERIN_IRQ;
extern int DELAYED_SEROUT_IRQ;
extern int DELAYED_XMTDONE_IRQ;

typedef enum {
	SIO_Normal,
	SIO_Put
} SIO_State;

static SIO_State sio_state = SIO_Normal;

void Command_Frame(void)
{
	sio_state = SIO_Normal;

	switch (cmd_frame[1]) {
	case 'R':					/* Read */
#ifdef DEBUG
		printf("Read command\n");
#endif
		{
			int sector;
			int dskno;
			int i;

			dskno = cmd_frame[0] - 0x31;
			sector = cmd_frame[2] + cmd_frame[3] * 256;
#ifdef DEBUG
			printf("Sector: %d(%x)\n", sector, sector);
#endif
			SeekSector(dskno, sector);

			data[0] = 0x41;		/* ACK */
			data[1] = 0x43;		/* OPERATION COMPLETE */

			read(disk[dskno], &data[2], 128);
			checksum = 0;
			for (i = 2; i < 130; i++) {
				checksum += (unsigned char) data[i];
				while (checksum > 255)
					checksum = checksum - 255;
			}
			data[130] = checksum;

			buffer_offset = 0;
			buffer_size = 131;

			DELAYED_SEROUT_IRQ = 1;
			DELAYED_XMTDONE_IRQ = 3;
			DELAYED_SERIN_IRQ = 150;	/* BEFORE 7 */
		}
		break;
	case 'S':					/* Status */
#ifdef DEBUG
		printf("Status command\n");
#endif
		data[0] = 0x41;			/* ACK */
		data[1] = 0x43;			/* OPERATION COMPLETE */
		data[2] = 0x10;			/* 2ea */
		data[3] = 0x00;			/* 2eb */
		data[4] = 0x01;			/* 2ec */
		data[5] = 0x00;			/* 2ed */
		data[6] = 0x11;			/* Checksum */
		buffer_offset = 0;
		buffer_size = 7;

		DELAYED_SEROUT_IRQ = 1;
		DELAYED_XMTDONE_IRQ = 5;
		DELAYED_SERIN_IRQ = 150;
		break;
	case 'W':					/* Write with verify */
	case 'P':					/* Put without verify */
#ifdef DEBUG
		printf("Put or Write command\n");
#endif
		data[0] = 0x41;			/* ACK */
		buffer_offset = 0;
		buffer_size = 1;
		DELAYED_SEROUT_IRQ = 1;
		DELAYED_XMTDONE_IRQ = 3;
		DELAYED_SERIN_IRQ = 150;	/* BEFORE 7 */
		sio_state = SIO_Put;
		break;
	case '!':					/* Format */
#ifdef DEBUG
		printf("Format command\n");
#endif
		break;
	case 'T':					/* Read Address */
#ifdef DEBUG
		printf("Read Address command\n");
#endif
		break;
	case 'Q':					/* Read Spin */
#ifdef DEBUG
		printf("Read Spin command\n");
#endif
		break;
	case 'U':					/* Motor On */
#ifdef DEBUG
		printf("Motor On command\n");
#endif
		break;
	case 'V':					/* Verify Sector */
#ifdef DEBUG
		printf("Verify Sector\n");
#endif
		break;
	default:
#ifdef DEBUG
		printf("Unknown command: %02x\n", cmd_frame[1]);
		printf("Command frame: %02x %02x %02x %02x %02x\n",
			   cmd_frame[0], cmd_frame[1], cmd_frame[2],
			   cmd_frame[3], cmd_frame[4]);
#endif
		buffer_offset = 0;
		buffer_size = 0;
		DELAYED_XMTDONE_IRQ = 3;
		break;
	}
}

void SIO_SEROUT(unsigned char byte, int cmd)
{
	checksum += (unsigned char) byte;
	while (checksum > 255)
		checksum = checksum - 255;

#ifdef DEBUG
	printf("SIO_SEROUT: byte = %x, checksum = %x, cmd = %d\n",
		   byte, checksum, cmd);
#endif

	if (cmd) {
		cmd_frame[ncmd++] = byte;
		if (ncmd == 5) {
			Command_Frame();

			offst = 0;
			checksum = 0;
			ncmd = 0;
		}
		else {
			DELAYED_SEROUT_IRQ = 1;
		}

		if (cmd_frame[0] == 0)
			ncmd = 0;
	}
	else if (sio_state == SIO_Put) {
		data[buffer_offset++] = byte;
		if (buffer_offset == 130) {
			int sector;
			int dskno;
			int i;

			checksum = 0;

			for (i = 1; i < 129; i++) {
				checksum += (unsigned char) data[i];
				while (checksum > 255)
					checksum = checksum - 255;
			}

			if (checksum != data[129]) {
				printf("Direct SIO Write Error\n");
				printf("Calculated Checksum = %x\n", checksum);
				printf("Actual Checksum = %x\n", data[129]);
				exit(1);
			}

			dskno = cmd_frame[0] - 0x31;
			sector = cmd_frame[2] + cmd_frame[3] * 256;

#ifdef DEBUG
			printf("Sector: %d(%x)\n", sector, sector);
#endif

			SeekSector(dskno, sector);

			write(disk[dskno], &data[1], 128);
			data[buffer_offset] = 0x41;		/* ACK */
			data[buffer_offset + 1] = 0x43;		/* OPERATION COMPLETE */
			buffer_size = buffer_offset + 2;
			DELAYED_SEROUT_IRQ = 1;
			DELAYED_XMTDONE_IRQ = 3;
			DELAYED_SERIN_IRQ = 7;
			DELAYED_XMTDONE_IRQ = 5;
			DELAYED_SERIN_IRQ = 150;
		}
		else {
			DELAYED_SEROUT_IRQ = 4;
		}
	}
	else {
		DELAYED_SEROUT_IRQ = 1;
		ncmd = 0;
	}
}

int SIO_SERIN(void)
{
	int byte = 0;				/* initialise the value, just for sure */

	if (buffer_offset < buffer_size) {
		byte = (int) data[buffer_offset++];

#ifdef DEBUG
		printf("SERIN: byte = %x\n", byte);
#endif

		if (buffer_offset < buffer_size) {
#ifdef DEBUG
			printf("Setting SERIN Interrupt again\n");
#endif
			DELAYED_SERIN_IRQ = 3;
			DELAYED_SERIN_IRQ = 4;
		}
	}

	return byte;
}
