/*
 *
 * All Input is assumed to be going to RAM (no longer, ROM works, too.)
 * All Output is assumed to be coming from either RAM or ROM
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.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.8 1997/09/30 thor,david Exp $";

#define FALSE   0
#define TRUE    1

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

#define	MAGIC1	0x96
#define	MAGIC2	0x02

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_filename[MAX_DRIVES][FILENAME_LEN];

/* Serial I/O emulation support */
UBYTE CommandFrame[6];
int CommandIndex=0;
UBYTE DataBuffer[256+3];
char sio_status[256];
int DataIndex=0;
int TransferStatus=0;
int ExpectedBytes=0;


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

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

  TransferStatus = SIO_NoFrame;
}

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)
    {
      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
	{
	  format[diskno-1] = XFD;
	  sectorcount[diskno-1]=lseek(fd,0,SEEK_END)/128;
	  lseek(fd,0,SEEK_SET);
	  sectorsize[diskno-1]=128;
	}
    }
  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 SizeOfSector(UBYTE unit,int sector,int *sz,ULONG *ofs)
{
int size=128;
ULONG offset=0;

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

    if (sz)
      *sz = size;

    if (ofs)
      *ofs = offset;
}

int SeekSector(int unit,int sector)
{
ULONG offset;
int size;

    sprintf (sio_status, "%d: %d", unit+1, sector);
    SizeOfSector(unit,sector,&size,&offset); 
    /* printf("Sector %x,Offset: %x\n",sector,offset); */
    lseek(disk[unit], offset, SEEK_SET);

    return size;
}


/* Unit counts from zero up */
int ReadSector(int unit,int sector,UBYTE *buffer)
{
int size;

    if (drive_status[unit] != Off) {
      if (disk[unit] != -1) {
	if (sector<=sectorcount[unit]) {
	  size = SeekSector (unit, sector);
	  read (disk[unit], buffer, size);
	  return 'C';
	} else return 'E';
      } else return 'N';
    } else return 0;
}

int WriteSector(int unit,int sector,UBYTE *buffer)
{
int size;

    if (drive_status[unit] != Off) {
      if (disk[unit] != -1) {
	if (drive_status[unit] == ReadWrite) {
	  if (sector<=sectorcount[unit]) {
	    size = SeekSector (unit, sector);
	    write (disk[unit], buffer, size);
	    return 'C';
	  } else return 'E';
	} else return 'E';
      } else return 'N';
    } else return 0;
}

int FormatSingle(int unit,UBYTE *buffer)
{
int i;

  if (drive_status[unit] != Off) {
    if (disk[unit] != -1) {
      if (drive_status[unit] == ReadWrite) {
	sectorcount[unit] = 720;
	sectorsize[unit] = 128;
	format[unit] = XFD;
	SeekSector(unit,1);
	memset(buffer,0,128);
	for(i=1;i<=720;i++)
	  write (disk[unit],buffer,128);
	memset(buffer,0xff,128);
	return 'C';
      } else return 'E';
    } else return 'N';
  } else return 0;
}

int FormatEnhanced(int unit,UBYTE *buffer)
{
int i;

  if (drive_status[unit] != Off) {
    if (disk[unit] != -1) {
      if (drive_status[unit] == ReadWrite) {
	sectorcount[unit] = 1040;
	sectorsize[unit] = 128;
	format[unit] = XFD;
	SeekSector(unit,1);
	memset(buffer,0,128);
	for(i=1;i<=1040;i++)
	  write (disk[unit],buffer,128);
	memset(buffer,0xff,128);
	return 'C';
      } else return 'E';
    } else return 'N';
  } else return 0;
}

int WriteStatusBlock(int unit,UBYTE *buffer)
{

  if (drive_status[unit] != Off) {
    /* We only care about the density right here. Setting everything else
       right here seems to be non-sense */
    if (format[unit] == ATR) { 
      if (buffer[5]==8) {
	sectorsize[unit] = 256;
      } else {
	sectorsize[unit] = 128;
      }
      sectorcount[unit] = 720;
      return 'C';
    } else return 'E';
  } else return 0;
}

int ReadStatusBlock(int unit,UBYTE *buffer)
{
int size;

  if (drive_status[unit] != Off) {
    SizeOfSector(unit,0x168,&size,NULL);
    buffer[0] = 40;     /* # of tracks */
    buffer[1] = 1;      /* step rate. No idea what this means */
    buffer[2] = 0;      /* sectors per track. HI byte */
    buffer[3] = 18;     /* sectors per track. LO byte */
    buffer[4] = 1;      /* # of heads */
    if (size==128) {
      buffer[5] = 4;    /* density */
      buffer[6] = 0;    /* HI bytes per sector */
      buffer[7] = 128;  /* LO bytes per sector */
    } else {
      buffer[5] = 8;    /* double density */
      buffer[6] = 1;    /* HI bytes per sector */
      buffer[7] = 0;    /* LO bytes per sector */
    }
    buffer[8] = 1;    /* drive is online */
    buffer[9] = 192;  /* transfer speed. Whatever this means */
    return 'C';
  } else return 0;
}
    
/*
   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)
*/
int DriveStatus(int unit,UBYTE *buffer)
{
    if (drive_status[unit] != Off) {
      if (drive_status[unit] == ReadWrite) {
	buffer[0] = (sectorsize[unit] == 256)?(32+16):(16);
	buffer[1] = (disk[unit] != -1)?(128):(0);
      } else {
	buffer[0] = (sectorsize[unit] == 256)?(32):(0);
	buffer[1] = (disk[unit] != -1)?(192):(64);
      }		
      if (sectorcount[unit]==1040) buffer[0] |= 128;
      buffer[2] = 1;
      buffer[3] = 0;
      return 'C';
    } else return 0;
}


void SIO (void)
{
int sector = DPeek(0x30a);
UBYTE unit = Peek(0x301) - 1;
UBYTE result=0x00;
ATPtr data = DPeek(0x304);
int length = DPeek(0x308);
int realsize;
int cmd = Peek(0x302);

#ifdef MOTIF
	Atari_Set_LED(1);
#endif

/*
    printf("SIO: Unit %x,Sector %x,Data %x,Length %x,CMD %x\n",unit,sector,data,length,cmd);
*/

    if (Peek(0x300) == 0x31) switch (cmd) {
    case 0x4e :  /* Read Status Block */
      if (12 == length) {
	result = ReadStatusBlock(unit,DataBuffer);
	if (result == 'C') 
	  CopyToMem(DataBuffer,data,12);
      } else result = 'E';
      break;
    case 0x4f :  /* Write Status Block */
      if (12 == length) {
	CopyFromMem(data,DataBuffer,12);
	result = WriteStatusBlock(unit,DataBuffer);
      } else result = 'E';
      break;
    case 0x50 :  /* Write */
    case 0x57 :
      SizeOfSector(unit,sector,&realsize,NULL);
      if (realsize == length) {
	CopyFromMem(data,DataBuffer,realsize);
	result = WriteSector(unit,sector,DataBuffer);
      } else result = 'E';
      break;
    case 0x52:   /* Read */
      SizeOfSector(unit,sector,&realsize,NULL);
      if (realsize == length) {
	result = ReadSector(unit,sector,DataBuffer);
	if (result == 'C') 
	  CopyToMem(DataBuffer,data,realsize);
      }
      else result = 'E';
      break;
    case 0x53:  /* Status */
      if (4 == length) {
	result = DriveStatus(unit,DataBuffer);
	CopyToMem(DataBuffer,data,4);
      } else result = 'E';
      break;
    case 0x21 : /* Single Density Format */
      if (length == 128) {
	result = FormatSingle(unit,DataBuffer);
	if (result == 'C')
	  CopyToMem(DataBuffer,data,realsize);
      } else result = 'E';
      break;
    case 0x22 : /* Enhanced Density Format */
      if (length == 128) {
	result = FormatEnhanced(unit,DataBuffer);
	if (result == 'C')
	  CopyToMem(DataBuffer,data,realsize);
      } else result = 'E';
      break;
    case 0x66 : /* US Doubler Format - I think! */
      result = 'A';  /* Not yet supported... to be done later... */
      break;
    default:
      result = 'N';
    }

   switch(result) {
   case 0x00:  /* Device disabled, generate timeout */
      regY = 138;
      SetN;
      break;
    case 'A':  /* Device acknoledge */
    case 'C':  /* Operation complete */
      regY = 1;
      ClrN;
      break;
    case 'N': /* Device NAK */
      regY = 144;
      SetN;
      break;
    case 'E': /* Device error */
    default:
      regY = 146;
      SetN;
      break;
    }

    Poke(0x0303,regY);

#ifdef MOTIF
	Atari_Set_LED(0);
#endif

}

void SIO_Initialize(void)
{
  TransferStatus = SIO_NoFrame;
}


UBYTE ChkSum(UBYTE *buffer,UWORD length)
{
int i;
int checksum=0;

  for (i=0;i<length;i++,buffer++) {
    checksum += *buffer;
    while (checksum > 255)
      checksum -= 255;
  }
  
  return checksum;
}
   
void Command_Frame (void)
{
int unit;
int result='A';
int sector;
int realsize;


   sector = CommandFrame[2] | (((UWORD)(CommandFrame[3]))<<8);
   unit = CommandFrame[0]-'1';
   if (unit>8) {   /* UBYTE - range ! */
     printf ("Unknown command frame: %02x %02x %02x %02x %02x\n",
	     CommandFrame[0], CommandFrame[1], CommandFrame[2],
	     CommandFrame[3], CommandFrame[4]);
     result = 0;
   } else switch (CommandFrame[1]) {
   case 0x4e :  /* Read Status */
      DataBuffer[0] = ReadStatusBlock(unit,DataBuffer+1);
      DataBuffer[13] = ChkSum(DataBuffer+1,12);
      DataIndex = 0;
      ExpectedBytes = 14;
      TransferStatus = SIO_ReadFrame;
      DELAYED_SERIN_IRQ += SERIN_INTERVAL;
      break;
    case 0x4f :
      ExpectedBytes = 13;
      DataIndex = 0;
      TransferStatus = SIO_WriteFrame;
      break;
    case 0x50 :  /* Write */
    case 0x57 :
      SizeOfSector(unit,sector,&realsize,NULL);
      ExpectedBytes = realsize+1;
      DataIndex = 0;
      TransferStatus = SIO_WriteFrame;
      break;
    case 0x52:   /* Read */     
      SizeOfSector(unit,sector,&realsize,NULL);
      DataBuffer[0] = ReadSector(unit,sector,DataBuffer+1);
      DataBuffer[1+realsize] = ChkSum(DataBuffer+1,realsize);
      DataIndex = 0;
      ExpectedBytes = 2+realsize;
      TransferStatus = SIO_ReadFrame;      
      DELAYED_SERIN_IRQ += SERIN_INTERVAL;
      break;
    case 0x53:  /* Status */
      DataBuffer[0] = DriveStatus(unit,DataBuffer+1);
      DataBuffer[1+4] = ChkSum(DataBuffer+1,4);
      DataIndex = 0;
      ExpectedBytes = 6;
      TransferStatus = SIO_ReadFrame;
      DELAYED_SERIN_IRQ += SERIN_INTERVAL;
      break;
    case 0x21 : /* Single Density Format */
      DataBuffer[0] = FormatSingle(unit,DataBuffer+1);
      DataBuffer[1+128] = ChkSum(DataBuffer+1,128);
      DataIndex = 0;
      ExpectedBytes = 2+128;
      TransferStatus = SIO_FormatFrame;
      DELAYED_SERIN_IRQ += SERIN_INTERVAL;
      break;
    case 0x22 : /* Duel Density Format */     
      DataBuffer[0] = FormatEnhanced(unit,DataBuffer+1);
      DataBuffer[1+128] = ChkSum(DataBuffer+1,128);
      DataIndex = 0;
      ExpectedBytes = 2+128;
      TransferStatus = SIO_FormatFrame;
      DELAYED_SERIN_IRQ += SERIN_INTERVAL;
      break;
    case 0x66 : /* US Doubler Format - I think! */
      result = 'A';  /* Not yet supported... to be done later... */
      break;
    default :
      printf ("Command frame: %02x %02x %02x %02x %02x\n",
	      CommandFrame[0], CommandFrame[1], CommandFrame[2],
	      CommandFrame[3], CommandFrame[4]);
      break;
      result = 0;
    }

    if (result == 0)
      TransferStatus = SIO_NoFrame;
}


/* Enable/disable the command frame */
void SwitchCommandFrame(int onoff)
{

  if (onoff) {    /* Enabled */
    if (TransferStatus != SIO_NoFrame)
      printf("Unexpected command frame %x.\n",TransferStatus);
    CommandIndex = 0;
    DataIndex = 0;
    ExpectedBytes = 5;
    TransferStatus = SIO_CommandFrame;
#ifdef MOTIF
    Atari_Set_LED(1);
#endif
    /* printf("Command frame expecting.\n"); */
  } else {
    if (TransferStatus!=SIO_StatusRead && TransferStatus!=SIO_NoFrame &&
	TransferStatus!=SIO_ReadFrame) {
      if (!(TransferStatus==SIO_CommandFrame && CommandIndex==0))
	printf("Command frame %02x unfinished.\n",TransferStatus);
      TransferStatus = SIO_NoFrame;
    }
    CommandIndex = 0;
  }
}

UBYTE WriteSectorBack(void)
{
UWORD sector;
UBYTE unit;
UBYTE result;

   sector = CommandFrame[2] | (((UWORD)(CommandFrame[3]))<<8);
   unit = CommandFrame[0]-'1';
   if (unit>8) {  /* UBYTE range ! */
     result = 0;
   } else switch (CommandFrame[1]) {
   case 0x4f :  /* Write Status Block */
     result=WriteStatusBlock(unit,DataBuffer);
     break;
   case 0x50 :  /* Write */
   case 0x57 :
     result=WriteSector(unit,sector,DataBuffer);
     break;
   default:
     result = 'E';
   }

   return result;
}

/* Put a byte that comes out of POKEY. So get it here... */
void SIO_PutByte(int byte)
{
UBYTE sum,result;

  switch(TransferStatus) {
  case SIO_CommandFrame:
    if (CommandIndex < ExpectedBytes) {
      CommandFrame[CommandIndex++] = byte;
      if (CommandIndex >= ExpectedBytes) {
	/* printf("%x\n",CommandFrame[0]); */
	if (((CommandFrame[0]>=0x31) && (CommandFrame[0]<=0x38))) { 
	  TransferStatus = SIO_StatusRead;
	  /* printf("Command frame done.\n"); */
	  DELAYED_SERIN_IRQ += SERIN_INTERVAL + ACK_INTERVAL;
	} else TransferStatus = SIO_NoFrame;
      } 
    } else {
      printf("Invalid command frame!\n");
      TransferStatus = SIO_NoFrame;
    }
    break;
  case SIO_WriteFrame:  /* Expect data */
    if (DataIndex < ExpectedBytes) {
      DataBuffer[DataIndex++] = byte;
      if (DataIndex >= ExpectedBytes) {
	sum = ChkSum(DataBuffer,ExpectedBytes - 1);
	if (sum == DataBuffer[ExpectedBytes - 1]) {
	  result = WriteSectorBack();
	  if (result) {
	    DataBuffer[0] = 'A';
	    DataBuffer[1] = result;
	    DataIndex = 0;
	    ExpectedBytes = 2;
	    DELAYED_SERIN_IRQ += SERIN_INTERVAL + ACK_INTERVAL;
	    TransferStatus = SIO_FinalStatus;
	    /* printf("Sector written, result= %x.\n",result); */
	  } else TransferStatus = SIO_NoFrame;
	} else {
	  DataBuffer[0] = 'E';
	  DataIndex = 0;
	  ExpectedBytes = 1;
	  DELAYED_SERIN_IRQ += SERIN_INTERVAL + ACK_INTERVAL;
	  TransferStatus = SIO_FinalStatus;
	}
      }
    } else {
      printf("Invalid data frame!\n");
    }
    break;
  default:
    printf("Unexpected data output :%x\n",byte);
  }
  DELAYED_SEROUT_IRQ += SEROUT_INTERVAL;

}

/* Get a byte from the floppy to the pokey. */

int SIO_GetByte(void)
{
int byte = 0;

  switch(TransferStatus) {
  case SIO_StatusRead:
    byte = 'A';   /* Command acknoledged */
    /* printf("Command status read\n"); */
    Command_Frame();  /* Handle now the command */      
    break;
  case SIO_FormatFrame:
    TransferStatus = SIO_ReadFrame;
    DELAYED_SERIN_IRQ += SERIN_INTERVAL<<3;
  case SIO_ReadFrame:
    if (DataIndex < ExpectedBytes) {
      byte=DataBuffer[DataIndex++];
      if (DataIndex >= ExpectedBytes) {
	TransferStatus = SIO_NoFrame;
#ifdef MOTIF
	Atari_Set_LED(0);
#endif
	/* printf("Transfer complete.\n"); */
      } else {
	  DELAYED_SERIN_IRQ += SERIN_INTERVAL;
      }
    } else {
      printf("Invalid read frame!\n");
      TransferStatus = SIO_NoFrame;
    }
    break;
  case SIO_FinalStatus:  
    if (DataIndex < ExpectedBytes) {
      byte=DataBuffer[DataIndex++];
      if (DataIndex >= ExpectedBytes) {
	TransferStatus = SIO_NoFrame;
#ifndef BASIC
	Atari_Set_LED(0);
#endif
	/* printf("Write complete.\n"); */
      } else {
	if (DataIndex == 0)
	  DELAYED_SERIN_IRQ += SERIN_INTERVAL+ACK_INTERVAL;
	else DELAYED_SERIN_IRQ += SERIN_INTERVAL;
      }
    } else {
      printf("Invalid read frame!\n");
#ifndef BASIC
	Atari_Set_LED(0);
#endif
      TransferStatus = SIO_NoFrame;
    }
    break;
  default:
    printf("Unexpected data reading!\n");
    break;
  }
  
  return byte;
}
