/* usage: to_d88 <type> [-r rpm] [outfile] */
/* AKIKAWA, Hisashi 2017.7.26 */

#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 SECT_MAX 32
#define HEADER_SIZE (32 + 2 * 82 * 4)

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

int read_track(unsigned char *raw, int bitsize, unsigned char *d88, int d88adr, int d88size);
enum adrmark convert(unsigned char *outdata, int bytesize, unsigned char *indata, int *inadr, int bitsize);
int sync(unsigned char *data, int start, int bitsize);
int calc_crc(unsigned int crc, unsigned char data);
int test_bit(unsigned char *data, int bit);
void set_bit(unsigned char *data, int bit);
void reset_bit(unsigned char *data, int bit);
void usage_exit(void);

int main(int argc, char *argv[])
{
  int i;
  int iarg;
  int type;
  int cylinder, head;
  int datalen, bitsize, time, time_old;
  int d88size, d88adr, d88adr_old;
  double rpm, tdiff;
  unsigned char *timedata, *raw, *d88, *track_table;
  char fname[PATH_MAX];
  FILE *fp;

  char typename[][4] = {"1D", "1DD", "2D", "2DD", "2HD"};
  int nside[] = {1, 1, 2, 2, 2};
  int ncylinder[] = {41, 82, 41, 82, 82};
  int track_density[] = {1, 2, 1, 2, 2};
  int d88type[] = {0x00, 0x10, 0x00, 0x10, 0x20};
  double tick[] = {2, 2, 2, 2, 1};      /* us */

  /* set parameters */
  if (argc < 2) {
    usage_exit();
  }

  type = -1;
  for (i = 0; i < sizeof(typename) / sizeof(typename[0]); i++) {
    if (strcasecmp(argv[1], typename[i]) == 0) {
      type = i;
      break;
    }
  }
  if (type == -1) {
    usage_exit();
  }

  iarg = 2;
  rpm = 300;
  if (argc >= 3 && strcmp(argv[2], "-r") == 0) {
    if (argc == 3) {
      usage_exit();
    }
    sscanf(argv[3], "%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);
  if (timedata == NULL || raw == NULL ) {
    printf("cannot allocate memory\n");
    exit(1);
  }

  d88size = HEADER_SIZE + ncylinder[type] * nside[type] * 10000;
  d88 = calloc(d88size, 1);
  if (d88 == NULL) {
    printf("cannot allocate memory\n");
    exit(1);
  }
  d88adr = HEADER_SIZE;

  /* d88 track/sector part */
  for (cylinder = 0; cylinder < ncylinder[type]; cylinder++) {
    for (head = 0; head < nside[type]; head++) {
      snprintf(fname, sizeof(fname), "c%02dh%d.dat",
	       cylinder * 2 / track_density[type], head);
      fp = fopen(fname, "rb");  /* b for Windows */
      if (fp) {
	if (DEBUG) printf("read %s\n", fname);
	datalen = fread(timedata, 1, TIME_SIZE, fp) & 0xfffffffc;
	fclose(fp);

	memset(raw, 0, RAW_SIZE);
	bitsize = 0;
	time_old = 0;
        for (i = 0; i < datalen; i += 4) {
	  time = timedata[i] + (timedata[i + 1] << 8) +
	    (timedata[i + 2] << 16) + (timedata[i + 3] << 24);	/* [ns] */
	  tdiff = (time - time_old) * 300. / rpm / 1000;	/* [us] */
	  bitsize += (tdiff + tick[type] / 2) / tick[type];
	  if (bitsize >= BIT_MAX) {
	  printf("too much size\n");
	    exit(1);
	  }
	  raw[bitsize / 8] |= (0x80 >> (bitsize % 8));
	  time_old = time;
	}
	if (bitsize) {
	  bitsize--;	/* INDEX signal */
	}

	d88adr_old = d88adr;
	d88adr = read_track(raw, bitsize, d88, d88adr, d88size);

	/* track table */
	if (d88adr != d88adr_old) {
	  track_table = &d88[0x20 + cylinder * 8 + head * 4];
	  track_table[0] = d88adr_old & 0xff;
	  track_table[1] = (d88adr_old >> 8) & 0xff;
	  track_table[2] = (d88adr_old >> 16) & 0xff;
	  track_table[3] = (d88adr_old >> 24) & 0xff;
	}
      }
    }
  }

  /* D88 header */
  memset(&d88[0x00], 0, 17);  /* disk name */
  memset(&d88[0x11], 0, 9);  /* reserved */
  d88[0x1a] = 0;             /* write protect = none */
  d88[0x1b] = d88type[type];
  d88[0x1c] = d88adr & 0xff;          /* disk size */
  d88[0x1d] = (d88adr >> 8) & 0xff;   /* disk size */
  d88[0x1e] = (d88adr >> 16) & 0xff;  /* disk size */
  d88[0x1f] = (d88adr >> 24) & 0xff;  /* disk size */

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

/* read track data */
int read_track(unsigned char *raw, int bitsize, unsigned char *d88, int d88adr, int d88size)
{
  int i;
  int sector;
  int sectsize;
  int rawadr, rawadrbak;
  unsigned int crc;
  unsigned char n;
  unsigned char *tmpdata;
  unsigned char *sectdata[SECT_MAX];
  enum adrmark adrmark;

  /* temporary data */
  tmpdata = malloc(d88size);
  if (tmpdata == NULL) {
    printf("cannot allocate memory\n");
    exit(1);
  }

  /* preamble part */
  rawadr = 0;
  adrmark = convert(tmpdata, 4, raw, &rawadr, bitsize);
  if (adrmark!= IAM) {
    rawadr = 0;
    if (DEBUG) printf("IAM not found\n");
  }

  /* sector part */
  for (sector = 0; sector < SECT_MAX; sector++) {
    /* ID field */
    do {
      rawadrbak = rawadr;
      adrmark = convert(tmpdata, 10, raw, &rawadr, bitsize);
    } while (adrmark != IDAM && rawadr > rawadrbak);
    if (rawadr <= rawadrbak) {
      break;
    }

    n = tmpdata[7];
    sectsize = 128 << n;
    /*
    if (n > 7) {
      sectsize = 128 << 7;
    }
    */
    sectdata[sector] = malloc(16 + sectsize);
    memset(sectdata[sector], 0, 16 + sectsize);

    sectdata[sector][0x00] = tmpdata[4];  /* c */
    sectdata[sector][0x01] = tmpdata[5];  /* h */
    sectdata[sector][0x02] = tmpdata[6];  /* r */
    sectdata[sector][0x03] = n;           /* n */
    sectdata[sector][0x06] = 0;           /* MFM */

    if (DEBUG) printf("CHRN=%d %d %d %d\n", tmpdata[0x04], tmpdata[0x05], tmpdata[0x06], n);
    crc = 0xffff;
    for (i = 0; i < 8; i++) {
      crc = calc_crc(crc, tmpdata[i]);
    }
    if (crc != (tmpdata[0x08] << 8) + tmpdata[0x09]) {
      sectdata[sector][0x08] = 0xa0;  /* data error (ID) */
      if (DEBUG) printf("error: ID CRC\n");
    }

    /* data field */
    crc = 0xffff;
    rawadrbak = rawadr;
    adrmark = convert(tmpdata, 6 + sectsize, raw, &rawadr, bitsize);
    for (i = 0; i < 4 + sectsize; i++) {
      crc = calc_crc(crc, tmpdata[i]);
    }

    /* D88 sector header */
    if (tmpdata[3] == 0xf8) {  /* deleted data */
      sectdata[sector][0x07] = 0x10;
      sectdata[sector][0x08] = 0x10;
    }
    if (adrmark != DAM) {
      sectdata[sector][0x08] = 0xf0;  /* status = missing DAM */
      sectsize = 0;
      if (DEBUG) printf("error: missing DAM\n");
    } else if (crc != (tmpdata[4 + sectsize] << 8) + tmpdata[4 + sectsize + 1]
	      || rawadr <= rawadrbak) {
      sectdata[sector][0x08] = 0xb0;  /* data error (data) */
      if (DEBUG) printf("error: data CRC\n");
    }

    sectdata[sector][0x09] = 0;  /* reserved */
    sectdata[sector][0x0a] = 0;  /* reserved */
    sectdata[sector][0x0b] = 0;  /* reserved */
    sectdata[sector][0x0c] = 0;  /* reserved */
    sectdata[sector][0x0d] = 0;  /* reserved */
    sectdata[sector][0x0e] = sectsize & 0xff;  /* sector size */
    sectdata[sector][0x0f] = sectsize / 256;  /* sector size */

    memcpy(&sectdata[sector][0x10], &tmpdata[4], sectsize);
    rawadr = rawadrbak;
  }

  /* copy to d88[] */
  for (i= 0; i < sector; i++) {
    sectdata[i][0x04] = sector & 0xff;  /* total number of sectors */
    sectdata[i][0x05] = sector / 256;   /* total number of sectors */
    memcpy(&d88[d88adr], sectdata[i], 16 + sectdata[i][0x0e] + (sectdata[i][0x0f] << 8));
    d88adr +=  16 + sectdata[i][0x0e] + (sectdata[i][0x0f] << 8);
    free(sectdata[i]);
  }

  return d88adr;
}

/* convert data */
enum adrmark convert(unsigned char *outdata, int bytesize, unsigned char *indata, int *inadr, int bitsize)
{
  int instart;
  int outadr;
  char iamdata[4] = {0xc2, 0xc2, 0xc2, 0xfc};
  char idamdata[4] = {0xa1, 0xa1, 0xa1, 0xfe};
  char idamdata2[4] = {0xa0, 0xa1, 0xa1, 0xfe};
  char damdata[4] = {0xa1, 0xa1, 0xa1, 0xfb};
  char ddamdata[4] = {0xa1, 0xa1, 0xa1, 0xf8};

  memset(outdata, 0, bytesize);
  if (bitsize < 2) {
    return NORMAL;
  }
  *inadr = sync(indata, *inadr, bitsize);
  instart = *inadr;

  for (outadr = 0; outadr < bytesize * 8; outadr++) {
    if (*inadr >= bitsize) {
      *inadr -= bitsize;
    }
    if (test_bit(indata, *inadr)) {
      set_bit(outdata, outadr);
    } else {
      reset_bit(outdata, outadr);
    }
    *inadr += 2;
  }

  /* check missing clock and address mark */
  if (!test_bit(indata, instart + 7)
      && !test_bit(indata, instart + 23)
      && !test_bit(indata, instart + 39)
      && memcmp(outdata, iamdata, 4) == 0) {
    return IAM;
  }
  if (!test_bit(indata, instart + 9)
      && !test_bit(indata, instart + 25)
      && !test_bit(indata, instart + 41)
      && (memcmp(outdata, idamdata, 4) == 0 || memcmp(outdata, idamdata2, 4) == 0)) {
    return IDAM;
  }
  if (!test_bit(indata, instart + 9)
      && !test_bit(indata, instart + 25)
      && !test_bit(indata, instart + 41)
      && (memcmp(outdata, damdata, 4) == 0 || memcmp(outdata, ddamdata, 4) == 0)) {
    return DAM;
  }
  return NORMAL;
}

/* skip SYNC and search first data bit */
int sync(unsigned char *data, int start, int bitsize)
{
  int i, j;
  int count = 0;

  for (i = start; i < bitsize; i++) {
    for (j = 1; i < bitsize; j++) {
      if (test_bit(data, i)) {
	break;
      }
      i++;
    }
    if (i >= bitsize) {
      break;
    }

    if (j == 2) {
      count++;
    } else {
      if (count > 40) {  /* about 5 byte 0x00 */
	break;
      } else {
	count = 0;
      }
    }
  }

  return i;
}

/* 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;
}

/* bit=1 or 0 */
int test_bit(unsigned char *data, int bit)
{
  return data[bit / 8] & (0x80 >> (bit % 8));
}

/* set bit */
void set_bit(unsigned char *data, int bit)
{
  data[bit / 8] |= (0x80 >> (bit % 8));
}

/* reset bit */
void reset_bit(unsigned char *data, int bit)
{
  data[bit / 8] &= ~(0x80 >> (bit % 8));
}

/* pritn usage and exit */
void usage_exit() {
  printf("usage: to_d88 <type> [-r rpm] [outfile]\n");
  printf(" type: 1d, 1dd, 2d, 2dd, 2hd\n");
  exit(1);
}
