#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <conio.h>
#include "windows.h"

FILE *img = 0;
int imgsize = 0;

int doServer (HANDLE hSerial);

void slowsend (HANDLE hSerial, unsigned char *sbuf)
{
  unsigned int j, slen;
  DWORD numwritten;

  slen = strlen(sbuf);
  for (j = 0; j < slen; ++j)
    {
      if (!WriteFile(hSerial, sbuf+j, 1, &numwritten, NULL)) {
	fprintf(stderr, "WriteFile() error (in sendprog)\n");
      }
      Sleep(40);
    }
  Sleep(300);
}

int sendprog (HANDLE hSerial)
{
  FILE *boot = 0;

  unsigned int i, addr, progsize;
  static unsigned char sbuf[100];
  static unsigned char aprog[600];

  memset(aprog, 0, 600);

  boot = fopen("bootload.bin", "rb");
  if (!boot) {
    fprintf(stderr, "file not found: bootload.bin\n");
    exit(-1);
  }

  progsize = fread(aprog, 1, 512, boot);
  if (progsize == 0) {
    fprintf(stderr, "Can't read file: bootload.bin\n");
    exit(-1);
  }

  fclose(boot);

  addr = 0x800;

  printf("Turn on Apple, type IN#2 (for Serial card in Slot 2)\n"
	 "  then type <Ctrl-A>15B<enter> on the Apple.\n"
	 "Hit <enter> on the PC afterwards\n");
  fflush(stdout);
  getchar();

  slowsend(hSerial, "CALL -151\r");

  for (i = 0; i < progsize; i += 8)
    {
      sprintf(sbuf,
	      "%3.3X:%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\r",
	      addr, aprog[i], aprog[i+1], aprog[i+2], aprog[i+3],
	      aprog[i+4], aprog[i+5], aprog[i+6], aprog[i+7]);
      printf("%s\n", sbuf);
      fflush(stdout);

      addr += 8;
      slowsend(hSerial, sbuf);
    }

  //slowsend(hSerial, "0\x0b\r");

  return 1;
}

int main (int argc, char **argv)
{
  struct stat st;

  if (0 != stat(argv[1], &st)) {
    fprintf(stderr, "Can't stat file '%s'\n", argv[1]);
    return -1;
  }

  img = fopen(argv[1], "r+b");
  if (!img) {
    fprintf(stderr, "Can't open file '%s'\n", argv[1]);
    return -1;
  }

#if 1
  if (0 != fseek(img, 0, SEEK_END)) {
    fprintf(stderr, "Can't seek file '%s'\n", argv[1]);
    return -1;
  }
#endif

  imgsize = (int)st.st_size;
  imgsize /= 512;
  if (imgsize > 65536)
    imgsize = 65536;

  printf("opened file '%s' (%d blocks)\n", argv[1], imgsize);

  HANDLE hSerial;
  DCB dcbSerialParams = {0};

  hSerial = CreateFile("COM1",
		       GENERIC_READ | GENERIC_WRITE,
		       0,
		       0,
		       OPEN_EXISTING,
		       FILE_ATTRIBUTE_NORMAL,
		       0);

  if (hSerial == INVALID_HANDLE_VALUE) {
    if (GetLastError() == ERROR_FILE_NOT_FOUND)
      fprintf(stderr, "No Serial Port!\n");
    else
      fprintf(stderr, "Unknown Error\n");
    return -1;
  }

  dcbSerialParams.DCBlength = sizeof(dcbSerialParams);

  if (!GetCommState(hSerial, &dcbSerialParams)) {
    fprintf(stderr, "GetCommState() error\n");
    return -1;
  }

  dcbSerialParams.BaudRate = CBR_19200;
  dcbSerialParams.ByteSize = 8;
  dcbSerialParams.StopBits = ONESTOPBIT;
  dcbSerialParams.Parity   = NOPARITY;

  if (!SetCommState(hSerial, &dcbSerialParams)) {
    fprintf(stderr, "SetCommState() error\n");
    return -1;
  }

  COMMTIMEOUTS timeouts = {0};

  timeouts.ReadIntervalTimeout = 100;
  timeouts.ReadTotalTimeoutConstant = 50;
  timeouts.ReadTotalTimeoutMultiplier = 10;
  timeouts.WriteTotalTimeoutConstant = 50;
  timeouts.WriteTotalTimeoutMultiplier = 10;

  if (!SetCommTimeouts(hSerial, &timeouts)) {
    fprintf(stderr, "SetCommTimeouts() error\n");
    return -1;
  }

  FlushFileBuffers(hSerial);
  doServer(hSerial);

  CloseHandle(hSerial);
  fclose(img);

  return 0;
}

#define CMDHELLO 'H'
#define CMDGDBYE 'B'
#define CMDFORMT 'F'
#define CMDRDBLK 'R'
#define CMDWRBLK 'W'
#define CMDSTATS 'S'

#define ROK   '+'
#define RBAD  '-'

void doRespond(HANDLE hSerial, char response)
{
  char obuf[2];
  DWORD numwritten = 0;

  obuf[0] = response;

  while (numwritten < 1) {
    if (!WriteFile(hSerial, obuf, 1, &numwritten, NULL)) {
      fprintf(stderr, "WriteFile() error (in doRespond)\n");
    }
  }
}

void doStatus (HANDLE hSerial)
{
  char obuf[4];
  DWORD total = 0;
  DWORD numwritten = 0;

  doRespond(hSerial, ROK);

  obuf[1] = imgsize % 256;
  obuf[0] = imgsize / 256;

  total = 0;
  while (total < 2) {
    if (!WriteFile(hSerial, obuf+total, 2-total, &numwritten, NULL)) {
      fprintf(stderr, "WriteFile() error (in doStatus)\n");
    }
    total += numwritten;
  }
}

void doReadBlock (HANDLE hSerial, unsigned char * ibuf)
{
  unsigned int i, blk, total, cksum;
  DWORD numwritten;
  unsigned char buf[520];

  doRespond(hSerial, ROK);
  blk = ibuf[2] * 256 + ibuf[1];

  printf("Read block %d\n", blk);
  fflush(stdout);

  blk = blk * 512;

  memset(buf, 0, 512);
  fseek(img, blk, SEEK_SET);
  fread(buf, 1, 512, img);

  cksum = 0;
  for (i = 0; i < 512; ++i)
    cksum += (unsigned int)buf[i];

  buf[512] = cksum % 256;
  cksum /= 256;
  buf[513] = cksum % 256;

  // printf("$%2.2x%2.2x\n", buf[513], buf[512]);

  total = 0;
  while (total < 514) {
    if (!WriteFile(hSerial, buf+total, 514-total, &numwritten, NULL)) {
      fprintf(stderr, "WriteFile() error (in doReadBlock)\n");
    }
    total += (int)numwritten;
  }

  FlushFileBuffers(hSerial);
}

void doWriteBlock (HANDLE hSerial, unsigned char * ibuf)
{
  unsigned int i, blk, cksum;
  unsigned char buf[520];

  doRespond(hSerial, ROK);
  blk = ibuf[2] * 256 + ibuf[1];

  printf("Write block %d\n", blk);
  fflush(stdout);

  blk = blk * 512;

  DWORD numread = 0;
  DWORD total = 0;

  while (total < 514) {
    if (!ReadFile(hSerial, buf+total, 514-total, &numread, NULL)) {
      fprintf(stderr, "ReadFile() error (in doWriteBlock)\n");
    }
    total += numread;
  }

  cksum = 0;
  for (i = 0; i < 512; ++i)
    cksum += (unsigned int)buf[i];

  //printf("my cksum:%4.4x   received cksum: %2.2x%2.2x\n",
  // cksum & 0xffff, buf[513], buf[512]);

  if (cksum % 256 == buf[512] && (cksum / 256) % 256 == buf[513])
    {
      doRespond(hSerial, '+');
      fseek(img, blk, SEEK_SET);
      fwrite(buf, 1, 512, img);
    }
  else
    {
      doRespond(hSerial, '-');
    }
}

int doServer (HANDLE hSerial)
{
  unsigned char ibuf[4];
  DWORD numread;

  sendprog(hSerial);
  FlushFileBuffers(hSerial);

  printf("Waiting for connection...\n");
  fflush(stdout);

  slowsend(hSerial, "800G\r");

  while (1)
    {
      while (1)
	{
	  numread = 0;

	  if (!ReadFile(hSerial, ibuf, 1, &numread, NULL)) {
	    fprintf(stderr, "ReadFile() error\n");
	  }

	  if (numread)
	    {
	      ibuf[0] &= 0x7f;
	      //printf("Got <%c/%2.2x> [%2.2x/%d]\n", ibuf[0], ibuf[0],
	      //		     CMDHELLO, ibuf[0] == CMDHELLO);
	      fflush(stdout);
	      if (ibuf[0] == CMDHELLO || ibuf[0] == CMDGDBYE ||
		  ibuf[0] == CMDFORMT || ibuf[0] == CMDRDBLK ||
		  ibuf[0] == CMDWRBLK || ibuf[0] == CMDSTATS)
		break;
	    }


	  Sleep(1);
	}

      numread = 0;
      while (!numread) {
	if (!ReadFile(hSerial, ibuf+1, 1, &numread, NULL)) {
	  fprintf(stderr, "ReadFile() error\n");
	}
      }

      //printf("Got <%2.2x>\n", ibuf[1]);
      //fflush(stdout);

      numread = 0;
      while (!numread) {
	if (!ReadFile(hSerial, ibuf+2, 1, &numread, NULL)) {
	  fprintf(stderr, "ReadFile() error\n");
	}
      }

      //printf("Got <%2.2x>\n", ibuf[2]);
      //fflush(stdout);


      printf("Cmd: %c (%02x %02x %02x)\n",
	     ibuf[0], ibuf[0], ibuf[1], ibuf[2]);
      fflush(stdout);

      switch (ibuf[0]) {
      case CMDHELLO:
	doRespond(hSerial, ROK);
	printf("Connection Established\n");
	fflush(stdout);
	break;

      case CMDGDBYE:
	doRespond(hSerial, ROK);
	printf("Connection Ended\n");
	fflush(stdout);
	fclose(img);
	return 0;

      case CMDFORMT:
	doRespond(hSerial, ROK);
	printf("Format\n");
	fflush(stdout);
	break;

      case CMDSTATS:
	doStatus(hSerial);
	printf("Status\n");
	fflush(stdout);
	break;

      case CMDRDBLK:
	doReadBlock(hSerial, ibuf);
	break;

      case CMDWRBLK:
	doWriteBlock(hSerial, ibuf);
	break;

      default:
	doRespond(hSerial, RBAD);
	printf("Unknown Command\n");
	fflush(stdout);
	break;
      }
    }
}
