#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <scsi/sg.h>
#include <stdint.h>
#include <time.h>


#define PLAY_AUDIO_12_CMD_CODE 0xa5
#define PLAY_AUDIO_12_CMD_LEN 12

#define READ_SUB_CHANNEL_CMD_CODE 0x42
#define READ_SUB_CHANNEL_CMD_LEN 10
#define READ_SUB_CHANNEL_REPLY_LEN 16

static char buf[32];

char *readsubchannel_audiostatus (uint8_t status) {
  switch (status) {
  case 0x00:
    return "Audio status byte not supported or not valid";
  case 0x11:
    return "Audio play operation in progress";
  case 0x12:
    return "Audio play operation paused";
  case 0x13:
    return "Audio play operation successfully completed";
  case 0x14:
    return "Audio play operation stopped due to error";
  case 0x15:
    return "No current audio status to return";
  default:
    sprintf(buf, "Reserved 0x%02x", status);
    return (buf);
  }
}

char *readsubchannel_adrq (uint8_t adrq) {
  static char buf[]="Reserved 0x00";
  switch (adrq) {
  case 0x0:
    return "Q Sub-channel mode information not supplied";
  case 0x1:
    return "Q Sub-channel encodes current position data";
  case 0x2:
    return "Q Sub-channel encodes media catalog number";
  case 0x3:
    return "Q Sub-channel encodes ISRC";
  default:
    sprintf(buf, "Reserved 0x%1x", adrq);
    return (buf);
  }
}

/* begin of part of cddisasm */
#define SUBCH_Q_TRACK_TYPE_AUDIO 0
#define SUBCH_Q_TRACK_TYPE_DATA 1

#define SUBCH_Q_DATA_RECORD_UNINTERRUPTED 0
#define SUBCH_Q_DATA_RECORD_INCREMENT 1

struct subch_q_control {
  int track_type;
  int channels;
  int pre_emphasis;
  int record_type;
  int copy_permitted;
};

void readsubchannel_control (struct subch_q_control *out, uint8_t control) {
  /* CONTROL */
  out->track_type=-1;
  out->channels=-1;
  out->pre_emphasis=-1;
  out->record_type=-1;
  switch (control&0xd) {
  case 0x0:
    out->track_type=SUBCH_Q_TRACK_TYPE_AUDIO;
    out->channels=2;
    out->pre_emphasis=0;
    break;
  case 0x1:
    out->track_type=SUBCH_Q_TRACK_TYPE_AUDIO;
    out->channels=2;
    out->pre_emphasis=1;
    break;
  case 0x8:
    out->track_type=SUBCH_Q_TRACK_TYPE_AUDIO;
    out->channels=4;
    out->pre_emphasis=0;
    break;
  case 0x9:
    out->track_type=SUBCH_Q_TRACK_TYPE_AUDIO;
    out->channels=4;
    out->pre_emphasis=1;
    break;
  case 0x4:
    out->track_type=SUBCH_Q_TRACK_TYPE_DATA;
    out->record_type=SUBCH_Q_DATA_RECORD_UNINTERRUPTED;
    break;
  case 0x5:
    out->track_type=SUBCH_Q_TRACK_TYPE_DATA;
    out->record_type=SUBCH_Q_DATA_RECORD_INCREMENT;
    break;
  default:
    out->track_type=-1;
    break;
  }
  if (control&0x2)
    out->copy_permitted=1;
  else
    out->copy_permitted=0;
}
/* end of part of cddisasm */


int main(int argc, char * argv[])
{
  int sg_fd, k;
  const struct timespec time_requested = {.tv_sec=0, .tv_nsec=13333333}; /* 1/75sec */
  struct timespec time_remaining;
  uint8_t playaudio12CmdBlk[PLAY_AUDIO_12_CMD_LEN] =
    {PLAY_AUDIO_12_CMD_CODE,
     0, /* Reserved, RelADR */
     0, 0, 0, 0, /* LBA */
     0xff, 0xff, 0xff, 0xff, /* Play Length */
     0, /* Reserved */
     0, /* Control */
    };
  uint8_t readsubchannelCmdBlk[READ_SUB_CHANNEL_CMD_LEN] =
    {READ_SUB_CHANNEL_CMD_CODE,
     0x02, /* Reserved, TIME=MSF, Reserved */
     0x40, /* Reserved, SUBQ=1, Reserved */
     0x01, /* Sub-channel Parameter List = CD Current Position */
     0, /* Reserved */
     0, /* Reserved */
     0, /* Track Number (only for ISRC Sub-channel Parameter List) */
     0, READ_SUB_CHANNEL_REPLY_LEN, /* Allocation Length (<256) */
     0, /* Control */
    };
  uint8_t readsubchannelBuff[READ_SUB_CHANNEL_REPLY_LEN];
  uint8_t sense_buffer[32];
  sg_io_hdr_t io_hdr;

  if (2 != argc) {
    printf("Usage: '%s <sg_device>'\n", argv[0]);
    return 1;
  }
  if ((sg_fd = open(argv[1], O_RDWR)) < 0) {
    perror("error opening given file name");
    return 1;
  }
  /* It is prudent to check we have a sg device by trying an ioctl */
  if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
    printf("%s is not an sg device, or old sg driver\n", argv[1]);
    return 1;
  }
  /* Prepare PLAY AUDIO (12) command */
  memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
  io_hdr.interface_id = 'S';
  io_hdr.cmd_len = sizeof(playaudio12CmdBlk);
  /* io_hdr.iovec_count = 0; */  /* memset takes care of this */
  io_hdr.mx_sb_len = sizeof(sense_buffer);
  io_hdr.dxfer_direction = SG_DXFER_NONE;
  io_hdr.cmdp = playaudio12CmdBlk;
  io_hdr.sbp = sense_buffer;
  io_hdr.timeout = 20000;     /* 20000 millisecs == 20 seconds */
  /* io_hdr.flags = 0; */     /* take defaults: indirect IO, etc */
  /* io_hdr.pack_id = 0; */
  /* io_hdr.usr_ptr = NULL; */

  if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
    perror("sg_simple0: PLAY AUDIO (12) SG_IO ioctl error");
    return 1;
  }

  /* now for the error processing */
  if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
    if (io_hdr.sb_len_wr > 0) {
      printf("PLAY AUDIO (12) sense data: ");
      for (k = 0; k < io_hdr.sb_len_wr; ++k) {
	if ((k > 0) && (0 == (k % 10)))
	  printf("\n  ");
	printf("0x%02x ", sense_buffer[k]);
      }
      printf("\n");
    }
    if (io_hdr.masked_status)
      printf("PLAY AUDIO (12) SCSI status=0x%x\n", io_hdr.status);
    if (io_hdr.host_status)
      printf("PLAY AUDIO (12) host_status=0x%x\n", io_hdr.host_status);
    if (io_hdr.driver_status)
      printf("PLAY AUDIO (12) driver_status=0x%x\n", io_hdr.driver_status);
  }

  while (1) {
    /* Prepare READ SUB-CHANNEL command */
    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.cmd_len = sizeof(playaudio12CmdBlk);
    /* io_hdr.iovec_count = 0; */  /* memset takes care of this */
    io_hdr.mx_sb_len = sizeof(sense_buffer);
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
    io_hdr.dxfer_len = READ_SUB_CHANNEL_REPLY_LEN;
    io_hdr.dxferp = readsubchannelBuff;
    io_hdr.cmdp = readsubchannelCmdBlk;
    io_hdr.sbp = sense_buffer;
    io_hdr.timeout = 20000;     /* 20000 millisecs == 20 seconds */
    /* io_hdr.flags = 0; */     /* take defaults: indirect IO, etc */
    /* io_hdr.pack_id = 0; */
    /* io_hdr.usr_ptr = NULL; */

    if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
      perror("sg_simple0: READ SUB-CHANNEL SG_IO ioctl error");
      return 1;
    }

    /* now for the error processing */
    if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
      if (io_hdr.sb_len_wr > 0) {
	printf("READ SUB-CHANNEL sense data: ");
	for (k = 0; k < io_hdr.sb_len_wr; ++k) {
	  if ((k > 0) && (0 == (k % 10)))
	    printf("\n  ");
	  printf("0x%02x ", sense_buffer[k]);
	}
	printf("\n");
      }
      if (io_hdr.masked_status)
	printf("READ SUB-CHANNEL SCSI status=0x%x\n", io_hdr.status);
      if (io_hdr.host_status)
	printf("READ SUB-CHANNEL host_status=0x%x\n", io_hdr.host_status);
      if (io_hdr.driver_status)
	printf("READ SUB-CHANNEL driver_status=0x%x\n", io_hdr.driver_status);
    }
    else {
      struct subch_q_control q;
      char *str;
      /* assume READ SUB-CHANNEL response is present */
      printf("\nREAD SUB-CHANNEL command's response:\n");

      printf("Audio Status: %s\n",
	     readsubchannel_audiostatus((readsubchannelBuff[1]))
	     );
      printf("ADR Q Sub-channel field: %s\n",
	     readsubchannel_adrq((readsubchannelBuff[4+1]&0xf0)>>4)
	     );
/* begin of part of cddisasm */
      printf("Q Sub-channel control field: 0x%1x\n",
	     readsubchannelBuff[4+1]&0x0f
	     );
    readsubchannel_control(&q, readsubchannelBuff[4+1]&0x0f);
    switch (q.track_type) {
    case SUBCH_Q_TRACK_TYPE_AUDIO: str="audio"; break;
    case SUBCH_Q_TRACK_TYPE_DATA: str="data"; break;
    default: str="*** RESERVED CD TYPE ***"; break;
    }
    printf("  type: %s\n", str);
    if (q.channels!=-1) {
      printf("  channels: %d\n", q.channels);
    }
    if (q.pre_emphasis!=-1) {
      printf("  pre-emphasis: %s\n", (q.pre_emphasis?"50/15 us":"none"));
    }
    if (q.record_type!=-1) {
      printf("  record type: recorded %s\n", (q.pre_emphasis?"increment":"uninterrupted"));
    }
    printf("  copy permitted: %s\n", (q.copy_permitted?"yes":"no"));

/* end of part of cddisasm */
      printf("Track number: %02d\n",
	     (readsubchannelBuff[4+2])
	     );
      printf("Index number: %02d\n",
	     (readsubchannelBuff[4+3])
	     );
      /* For LBA mode (FIXME: endian)
      printf("Absolute CD address: 0x%08x\n",
	     *(uint32_t*)(&(readsubchannelBuff[4+4]))
	     );
      printf("Track Relative CD address: 0x%08x\n",
	     *(uint32_t*)(&(readsubchannelBuff[4+8]))
	     );
      */
      /* For MSF mode */
      printf("Absolute CD address: %02d:%02d:%02d\n",
	     readsubchannelBuff[4+ 5],
	     readsubchannelBuff[4+ 6],
	     readsubchannelBuff[4+ 7]
	     );
      printf("Track Relative CD address: %02d:%02d:%02d\n",
	     readsubchannelBuff[4+ 9],
	     readsubchannelBuff[4+10],
	     readsubchannelBuff[4+11]
	     );
    }
    nanosleep(&time_requested, &time_remaining);
  }
  close(sg_fd);
  return 0;
}

