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

#include <exec/types.h>
#include <exec/memory.h>
#include <devices/trackdisk.h>
#include <devices/scsidisk.h>

#include <powerup/ppcproto/intuition.h>
#include <powerup/ppclib/interface.h>
#include <powerup/ppclib/time.h>
#include <powerup/gcclib/powerup_protos.h>
#include <powerup/ppcproto/exec.h>
#include <powerup/ppcproto/dos.h>
#include <powerup/ppcinline/alib.h>

#include "fpse.h"
#include "scsidefs.h"

#define READ_CD 0xbe

#define SENSELEN 32
unsigned char sensebuf[SENSELEN];

int amiga_cd_unit = 2;
char *amiga_cd_device = NULL;

struct MsgPort *CDMP; /* Pointer for message port */
struct IOExtTD *CDIO; /* Pointer for IORequest */
int cd_error;

typedef struct {
  unsigned char reserved;
  unsigned char adr;
  unsigned char trackno;
  unsigned char reserved1;
  unsigned char addr[4];
} TOCENTRY;

typedef struct {
  unsigned char size[2];
  unsigned char first;
  unsigned char last;
  TOCENTRY track[100];
} TOC;

typedef struct {
  UINT8 minute;
  UINT8 second;
  UINT8 frame;
} LOC;

#define INT2BCD(a) (((a)/10)*16+((a)%10))
#define BCD2INT(a) (((a)>>4)*10+((a)&0xf))

#define SECTOR_SIZE 2352
static UINT8 readbuf[SECTOR_SIZE*2];

static int loc2int(LOC *loc);
static void ExecReadTOC(void *buf,int size,int first);
static void ExecRead(unsigned long frame,void *buf,int bufsize);
static void ExecPlay(unsigned char *start,unsigned char *end);
static void ExecStop(void);

int CD_Open(UINT32 *par)
{
  CDMP = CreateMsgPort();
  if (CDMP != NULL) {
    CDIO = (struct IOExtTD *)CreateIORequest(CDMP, sizeof(struct IOExtTD));
    if (CDIO != NULL) {
      cd_error = OpenDevice(amiga_cd_device, amiga_cd_unit, (struct IORequest *)CDIO, 0);
      if (cd_error != 0) {
        printf("CD_Open: could not open device %s unit %d\n", amiga_cd_device, amiga_cd_unit);
        return FPSE_ERR;
      }
    }
  }

  return FPSE_OK;
}

void CD_Close(void)
{
  if (cd_error == 0) {
    CloseDevice(CDIO);
  }

  if (CDIO != NULL) {
    DeleteIORequest(CDIO);
  }

  if (CDMP != NULL) {
    DeleteMsgPort(CDMP);
  }
}

static TOC toc;

static struct {
  unsigned char size[2];
  unsigned char first,last;
  TOCENTRY track[1];
} readout;

int CD_GetTN(UINT8 *result)
{
  ExecReadTOC(&toc,sizeof(toc),0);

  result[1] = toc.first;
  result[2] = toc.last;

  return 0;
}

int CD_GetTD(UINT8 *result,int track)
{
  int n;

  ExecReadTOC(&toc,sizeof(toc),0);
  n = track-toc.first;

  result[1] = toc.track[n].addr[1]; /* minute */
  result[2] = toc.track[n].addr[2]; /* second */
  result[3] = toc.track[n].addr[3]; /* frame */

  return 0;
}

int CD_Play(UINT8 *param)
{
  /* serch next track */
  ExecPlay(param,&readout.track[0].addr[2]);
  return 0;
}

int CD_Stop(void)
{
  /* serch next track */
  ExecStop();
  return 0;
}

UINT8 *CD_Read(UINT8 *param)
{
  ExecRead(loc2int((LOC *)param),readbuf+12,SECTOR_SIZE);

  /* emulate header */
  readbuf[0] = INT2BCD(param[0]);
  readbuf[1] = INT2BCD(param[1]);
  readbuf[2] = INT2BCD(param[2]);

  return readbuf;
}

void CD_GetSeek(UINT8 *par)
{
}

int ExecSCSICmd(UINT8 *cmd,int cmdsize,UINT8 *data,int datasize, UINT8 direction)
{
  struct SCSICmd scmd;

  CDIO->iotd_Req.io_Command = HD_SCSICMD;
  CDIO->iotd_Req.io_Data    = &scmd;
  CDIO->iotd_Req.io_Length  = sizeof(scmd);

  scmd.scsi_Data        = (APTR)data;
  scmd.scsi_Length      = datasize;
  scmd.scsi_SenseActual = 0;
  scmd.scsi_SenseData   = sensebuf;
  scmd.scsi_SenseLength = SENSELEN;
  scmd.scsi_Command     = cmd;
  scmd.scsi_CmdLength   = cmdsize;
  scmd.scsi_Flags       = SCSIF_AUTOSENSE | direction;

  DoIO((struct IORequest *)CDIO);

  if (CDIO->iotd_Req.io_Error != 0) {
    printf("ExecSCSICmd: error (%d)\n", CDIO->iotd_Req.io_Error);
    printf("SCSI Status: %d\n", scmd.scsi_Status);
  }

  return CDIO->iotd_Req.io_Error;
}

int CD_Wait()
{
  /* What is this supposed to do ? */
  return FPSE_OK;
}

static int loc2int(LOC *loc)
{
  return ((loc->minute)*60+(loc->second)-2)*75+(loc->frame);
}

static void ExecReadTOC(void *buf,int size,int first)
{
  UINT8 cdb[10];

  memset(cdb,0,sizeof(cdb));

  cdb[0] = SCSI_READ_TOC;
  cdb[1] = 2; /* MSF format */
  cdb[6] = first;
  cdb[7] = size>>8;
  cdb[8] = size&255;

  ExecSCSICmd(cdb,sizeof(cdb),buf,size,SCSIF_READ);
}

static void ExecRead(unsigned long frame,void *buf,int bufsize)
{
  UINT8 cdb[12];

  memset(cdb,0,sizeof(cdb));

  cdb[0] = READ_CD;
  cdb[2] = frame>>24;
  cdb[3] = frame>>16;
  cdb[4] = frame>>8;
  cdb[5] = frame;
  cdb[8] = 1; /* read 1 frame */
  cdb[9] = 0x18; /* User Data + EDC/ECC */

  ExecSCSICmd(cdb,sizeof(cdb),buf,bufsize,SCSIF_READ);
}

static void ExecPlay(unsigned char *start,unsigned char *end)
{
  UINT8 cdb[10];

  memset(cdb,0,sizeof(cdb));

  cdb[0] = SCSI_PLAYAUDMSF;
  cdb[3] = start[0];
  cdb[4] = start[1];
  cdb[5] = start[2];
  cdb[6] = end[0];
  cdb[7] = end[1];
  cdb[8] = end[2];

  ExecSCSICmd(cdb,sizeof(cdb),NULL,0,SCSIF_READ);
}

static void ExecStop(void)
{
  UINT8 cdb[10];

  memset(cdb,0,sizeof(cdb));

  cdb[0] = 0x4B; /* Pause command */
  cdb[8] = 0; /* 0:pause 1:resume */

  ExecSCSICmd(cdb,sizeof(cdb),NULL,0,SCSIF_READ);
}