/* usage: from_d88 [-r rpm] [infile] */
/* AKIKAWA, Hisashi 2017.3.4 */

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

#define DEBUG 1

/* data size */
#define BIT_MAX (200000 + 50000)	/* 1/300rpm/1us + more */
#define TIME_SIZE (BIT_MAX / 2 * 4)
#define RAW_SIZE (BIT_MAX / 8)

/* disk parameter */
#define NSIDE 2
#define NCYLINDER 82
#define HEADER_SIZE (32 + 2 * 82 * 4)
#define D88_SIZE (HEADER_SIZE + NCYLINDER * NSIDE * 10000)
#define TYPE_2HD 0x20

enum adrmark {
  NORMAL, IAM, IDAM, DAM, DDAM
};

int write_track(unsigned char *raw, unsigned char *d88, int *d88adr, int dtype, double rpm);
int iam(unsigned char *outdata, int adr);
int idam(unsigned char *outdata, int adr);
int dam(unsigned char *outdata, int adr);
int ddam(unsigned char *outdata, int adr);
int gap(unsigned char *outdata, int adr, int num);
int sync(unsigned char *outdata, int adr);
int set_byte(unsigned char *outdata, int adr, unsigned char data, enum adrmark type);
int set_bit(unsigned char *outdata, int adr, int bit);
int calc_crc(unsigned int crc, unsigned char data);
int calc_gap3(int nsector, int n, int dtype, double rpm);
void usage_exit(void);

int main(int argc, char *argv[])
{
  int i, j;
  int iarg;
  int cylinder, head;
  int dtype;
  int d88adr;
  int bitsize;
  int time;
  double rpm;
  unsigned char *timedata, *raw, *d88, *track_table;
  char fname[PATH_MAX];
  FILE *fp;

  /* set parameters */
  iarg = 1;
  rpm = 300;
  if (argc >= 2 && strcmp(argv[1], "-r") == 0) {
    if (argc == 2) {
      usage_exit();
    }
    sscanf(argv[2], "%lf", &rpm);
    if (rpm <= 0 || rpm >=1000) {
      printf("illegal rpm value\n");
      exit(1);
    }
    iarg += 2;
  }

  /* allocate memory */
  timedata = malloc(TIME_SIZE);
  raw = malloc(RAW_SIZE);
  d88 = malloc(D88_SIZE);
  if (timedata == NULL || raw == NULL || d88 == NULL) {
    printf("cannot allocate memory\n");
    exit(1);
  }

  /* input */
  if (iarg < argc) {
    strncpy(fname, argv[iarg], sizeof(fname));
  } else {
    strncpy(fname, "fd.d88", sizeof(fname));
  }
  fp = fopen(fname, "rb");  /* b for Windows */
  if (fp == NULL) {
    printf("cannot open %s\n", fname);
    exit(1);
  }
  fread(d88, 1, D88_SIZE, fp);
  fclose(fp);

  d88adr = HEADER_SIZE;
  dtype = d88[27];

  /* d88 track/sector part */
  for (cylinder = 0; cylinder < NCYLINDER; cylinder++) {
    for (head = 0; head < NSIDE; head++) {
      /* track table */
      track_table = &d88[32 + cylinder * 8 + head * 4];
      d88adr = track_table[0]
	+ (track_table[1] << 8)
	+ (track_table[2] << 16)
	+ (track_table[3] << 16);

      if (d88adr) {
	memset(raw, 0, RAW_SIZE);
	bitsize = write_track(raw, d88, &d88adr, dtype, rpm);

	j = 0;
	for (i = 0; i < bitsize; i++) {
	  if (raw[i / 8] & (0x80 >> (i % 8))) {
	    time = i * 2000. * rpm / 300 + 0.5;
	    if (dtype == TYPE_2HD) {
	      time /= 2;
	    }
	    timedata[j++] = time & 0xff;
	    timedata[j++] = (time >> 8) & 0xff;
	    timedata[j++] = (time >> 16) & 0xff;
	    timedata[j++] = (time >> 24) & 0xff;
	  }
	}

	/* output data */
	if (dtype == 0) {
	  snprintf(fname, sizeof(fname), "c%02dh%d.dat", cylinder * 2 , head);
	} else {
	  snprintf(fname, sizeof(fname), "c%02dh%d.dat", cylinder, head);
	}
	if (DEBUG) printf("write %s\n", fname);
	fp = fopen(fname, "wb");  /* b for Windows */
	if (fp == NULL) {
	  printf("cannot open %s\n", fname);
	  exit(1);
	}
	fwrite(timedata, 1, j, fp);
	fclose(fp);
      }
    }
  }

  return 0;
}

/* write track data */
int write_track(unsigned char *raw, unsigned char *d88, int *d88adr, int dtype, double rpm)
{
  int i;
  int sector;
  int rawadr;
  int sectsize;
  int nsector;
  int status;
  unsigned int crc;
  unsigned char c, h, r, n;

  nsector = d88[*d88adr + 4] + (d88[*d88adr + 5] << 8);
  rawadr = 1;

  /* preamble part = gap0 + sync + iam + gap1 */
  rawadr = gap(raw, rawadr, 80);
  rawadr = sync(raw, rawadr);
  rawadr = iam(raw, rawadr);
  rawadr = gap(raw, rawadr, 50);

  /* sector part */
  for (sector = 0; sector < nsector; sector++) {
    c = d88[*d88adr];
    h = d88[*d88adr + 1];
    r = d88[*d88adr + 2];
    n = d88[*d88adr + 3];
    status = d88[*d88adr + 8];

    sectsize = d88[*d88adr + 14] + (d88[*d88adr + 15] << 8);
    if (sectsize == 0) {	/* for Ditt! */
      sectsize = 128 << n;
      for (i = 0; i < 0x100; i++) {
	if (d88[*d88adr + 0x10 + i]) {
	  sectsize = 0;
	  break;
	}
      }
    }

    if (DEBUG) printf("CHRN=%d %d %d %d\n", c, h, r, n);

    crc = 0xffff;
    crc = calc_crc(crc, 0xa1);
    crc = calc_crc(crc, 0xa1);
    crc = calc_crc(crc, 0xa1);
    crc = calc_crc(crc, 0xfe);
    crc = calc_crc(crc, c);
    crc = calc_crc(crc, h);
    crc = calc_crc(crc, r);
    crc = calc_crc(crc, n);
    if (status == 0xa0) {  /* data error (ID) */
      crc++;
    }

    /* ID part = sync + idam + chrn + crc */
    rawadr = sync(raw, rawadr);
    rawadr = idam(raw, rawadr);
    rawadr = set_byte(raw, rawadr, c, NORMAL);
    rawadr = set_byte(raw, rawadr, h, NORMAL);
    rawadr = set_byte(raw, rawadr, r, NORMAL);
    rawadr = set_byte(raw, rawadr, n, NORMAL);
    rawadr = set_byte(raw, rawadr, (crc >> 8), NORMAL);
    rawadr = set_byte(raw, rawadr, crc & 0xff, NORMAL);

    /* data part = gap2 + sync + dam/ddam + data + crc */
    if (status == 0xf0) {  /* f0=missing DAM */
      *d88adr += 16 + sectsize;
    } else {

      crc = 0xffff;
      crc = calc_crc(crc, 0xa1);
      crc = calc_crc(crc, 0xa1);
      crc = calc_crc(crc, 0xa1);
  
      rawadr = gap(raw, rawadr, 22);
      rawadr = sync(raw, rawadr);

      if (status == 0x10) {
	rawadr = ddam(raw, rawadr);
	crc = calc_crc(crc, 0xf8);
      } else {
	rawadr = dam(raw, rawadr);
	crc = calc_crc(crc, 0xfb);
      }

      *d88adr += 16;
      for (i = 0; i < sectsize; i++) {
	rawadr = set_byte(raw, rawadr, d88[*d88adr], NORMAL);
	crc = calc_crc(crc, d88[*d88adr]);
	(*d88adr)++;
      }
      if (status == 0xb0) {  /* data error (data) */
	crc++;
      }
      rawadr = set_byte(raw, rawadr, (crc >> 8), NORMAL);
      rawadr = set_byte(raw, rawadr, crc & 0xff, NORMAL);
    }

    /* gap3 */
    rawadr = gap(raw, rawadr, calc_gap3(nsector, n, dtype, rpm));
  }

  /* postamble = gap4 */
  rawadr = gap(raw, rawadr, (BIT_MAX - rawadr) / 16);

  return rawadr;
}

/* index address mark */
int iam(unsigned char *outdata, int adr)
{
  adr = set_byte(outdata, adr, 0xc2, IAM);
  adr = set_byte(outdata, adr, 0xc2, IAM);
  adr = set_byte(outdata, adr, 0xc2, IAM);
  adr = set_byte(outdata, adr, 0xfc, IAM);
  return adr;
}

/* ID address mark */
int idam(unsigned char *outdata, int adr)
{
  adr = set_byte(outdata, adr, 0xa1, IDAM);
  adr = set_byte(outdata, adr, 0xa1, IDAM);
  adr = set_byte(outdata, adr, 0xa1, IDAM);
  adr = set_byte(outdata, adr, 0xfe, IDAM);
  return adr;
}

/* data address mark */
int dam(unsigned char *outdata, int adr)
{
  adr = set_byte(outdata, adr, 0xa1, DAM);
  adr = set_byte(outdata, adr, 0xa1, DAM);
  adr = set_byte(outdata, adr, 0xa1, DAM);
  adr = set_byte(outdata, adr, 0xfb, DAM);
  return adr;
}

/* deleted data address mark */
int ddam(unsigned char *outdata, int adr)
{
  adr = set_byte(outdata, adr, 0xa1, DDAM);
  adr = set_byte(outdata, adr, 0xa1, DDAM);
  adr = set_byte(outdata, adr, 0xa1, DDAM);
  adr = set_byte(outdata, adr, 0xf8, DDAM);
  return adr;
}

/* GAP */
int gap(unsigned char *outdata, int adr, int num)
{
  int i;

  for (i = 0; i < num; i++) {
    adr = set_byte(outdata, adr, 0x4e, NORMAL);
  }
  return adr;
}

/* SYNC */
int sync(unsigned char *outdata, int adr)
{
  int i;

  for (i = 0; i < 12; i++) {
    adr = set_byte(outdata, adr, 0, NORMAL);
  }
  return adr;
}

/* set 1-byte data */
int set_byte(unsigned char *outdata, int adr, unsigned char data, enum adrmark type)
{
  int i;
  int bit;
  static int bit_before;

  for (i = 0; i < 8; i++) {
    bit = (data >> (7 - i)) & 1;
    if (bit_before == 0 && bit == 0) {
      if ((type == IAM && i == 4) ||
	  ((type == IDAM || type == DAM || type == DDAM) && i == 5)) {
	adr = set_bit(outdata, adr, 0);
      } else {
	adr = set_bit(outdata, adr, 1);
      }
    } else {
      adr = set_bit(outdata, adr, 0);
    }
    adr = set_bit(outdata, adr, bit);
    bit_before = bit;
  }

  return adr;
}

/* set 1 bit */
int set_bit(unsigned char *outdata, int adr, int bit)
{
  if (adr < RAW_SIZE * 8) {
    if (bit) {
      outdata[adr / 8] |= 0x80 >> (adr % 8);
    } else {
      outdata[adr / 8] &= ~(0x80 >> (adr % 8));
    }
    adr++;
  }

  return adr;
}

/* calculate CRC */
int calc_crc(unsigned int crc, unsigned char data)
{
  int i, msb;

  for (i = 0; i < 8; i++) {
    msb = (crc >> 15) ^ (data >> 7);
    crc = (crc << 1) & 0xffff;
    data = (data << 1) & 0xff;
    if (msb) {
      crc ^= 0x1021;
    }
  }

  return crc;
}

/* calculate gap3 length */
int calc_gap3(int nsector, int n, int dtype, double rpm)
{
  int gap3max;
  int gap3len;
  int totallen;
  const int preamble = 146;	/* gap0(80)+sync(12)+iam(4)+gap1(50) */
  const int gap4min = 48;	/* for 1680KB format */
  const int id = 44;		/* sync(12)+idam(4)+chrn(4)+crc(2)+gap2(22) */
  const int syncdamcrc = 18;	/* sync(12)+dam/ddam(4)+crc(2) */
  const int gap3standard[] = {27, 54, 84, 116};

  if (n > 3) {
    gap3max = 255;
  } else {
    gap3max = gap3standard[n];
  }

  /* JIS X 6225 / ECMA-125 format */
  if (dtype == TYPE_2HD &&
      n == 2 &&
      nsector == 18 &&
      270 < rpm && rpm < 330) {
    gap3max = 101;
  }

  totallen = (dtype == TYPE_2HD) ? 12500 : 6250;

  gap3len = (totallen * 300 / rpm
	     - preamble
	     - gap4min
	     - nsector * (id + syncdamcrc + (128 >> n))) / nsector;
  if (gap3len > gap3max) {
    gap3len = gap3max;
  } else if (gap3len < 0) {
    gap3len = 0;
  }
  return gap3len;
}

/* print usage and exit */
void usage_exit()
{
  printf("usage: from_d88 [-r rpm] [infile]\n");
  exit(1);
}
