/*

 * 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, 0777);
        if (fd == -1) {
                fd = open(filename, O_RDONLY, 0777);
                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:
                                printf("SIO: DCOMND = %0x\n", DCOMND);
                                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 */
                printf("Format command\n");
                break;
        case 'T':                                       /* Read Address */
                printf("Read Address command\n");
                break;
        case 'Q':                                       /* Read Spin */
                printf("Read Spin command\n");
                break;
        case 'U':                                       /* Motor On */
                printf("Motor On command\n");
                break;
        case 'V':                                       /* Verify Sector */
                printf("Verify Sector\n");
                break;
        default:
                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]);
                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;
}
