/// \file main.c Buffering UART device driver.
// \author Henrik Herranen, VLSI Solution Oy

/* For free support for VSIDE, please visit www.vsdsp-forum.com */


#include <vo_stdio.h>
#include <volink.h>     // Linker directives like DLLENTRY
#include <apploader.h>  // RunLibraryFunction etc
#include <string.h>
#include <timers.h>
#include <vsrv.h>
#include <imem.h>
#include <ringbuf.h>
#include <kernel.h>
#include <consolestate.h>
#ifdef SUPPORT_UIMESSAGES
#include <uimessages.h>
#endif
#include <ctype.h>
#include <sysmemory.h>
#include <math.h>
#include <uartspeed.h>


/* If one below a power of two, memory usage efficiency is best */
#define UART_RX_BUFFER_DEFAULT_SIZE (256-1)
/* If one below a power of two, memory usage efficiency is best */
#define UART_TX_BUFFER_DEFAULT_SIZE (256-1)
__mem_y char *uartRxBuffer;
__mem_y char *uartTxBuffer;

struct Uart {
  FILE *origIn, *origOut;
  u_int16 origIntEna0Mask;
  u_int32 origRxVec, origTxVec;
  __mem_y char *rxRd, *rxWr, *txRd, *txWr;
  u_int16 rxSize, txSize;
} uart = {
  0, 0, 0, 0, 0, NULL, NULL, NULL, NULL,
  UART_RX_BUFFER_DEFAULT_SIZE, UART_TX_BUFFER_DEFAULT_SIZE
};


#if 0
void WatchdogReset(void) {
  //PERIP(UART_DATA) = 'R';
  PERIP(WDOG_CF) = 1;
  PERIP(WDOG_KEY) = 0x4ea9;
  while (1)
    ;
}
#endif

void IntUartTxC(void) {
  while (!(PERIP(UART0_STATUS) & UART_ST_TXFULL) && uart.txWr != uart.txRd) {
    PERIP(UART0_DATA) = *uart.txRd;
    uart.txRd = ringmodY(uart.txRd, MAKEMODF(uart.txSize));
  }
}

void IntUartRxC(void) {
#ifdef SUPPORT_UIMESSAGES
  static u_int16 inProtocol = 0;
#endif
  static u_int16 utf8Bytes = 0, utf8Res = 0;
  static u_int32 value;
  static u_int16 address;
  static u_int16 ctrlCCount = 0;
  char c = PERIP(UART0_DATA);

  if (!(appFlags & APP_FLAG_RAWTTY)) {
    /* Potential illegal UTF-8 code cancelled, handle new char as normal. */
    if ((c & 0xC0) != 0x80) utf8Bytes = 0;

    if (c==0x03) { // Ctrl-C
      if (appFlags & APP_FLAG_QUIT) {
	ctrlCCount++;
	//PERIP(UART_DATA) = '0'+count;
	if (ctrlCCount>=3) { // 3rd Ctrl-C -> force watchdog reset
	  PERIP(WDOG_CF) = 1;
	  PERIP(WDOG_KEY) = 0x4ea9;
	  while (1) {
	    //Wait for watchdog reset
	  }
	}
      }
      appFlags |= APP_FLAG_QUIT;
    } else {
      ctrlCCount = 0;
      if (utf8Bytes) {
	utf8Res |= (c&0x3f) << (6 * --utf8Bytes);
	/* UTF8 code not finished, no character to return. */
	if (utf8Bytes) return;
	/* UTF8 code finished, now return what we've collected. */
	c = (utf8Res > 0xFFFDU) ? 0xFFFDU : utf8Res;
#ifdef SUPPORT_UIMESSAGES
      } else if (c==0x02) { //Ctrl-B
	inProtocol = 1;
	value = 0;
	return;
#endif
      } else if ((c & 0xC0) == 0xC0) {
      /*
	UTF-8 encodings:
               0, 7 bits  = 00          = 00000000
	    0x7f, 7 bits  = 7f          = 01111111
	    0x80  11 bits = c2 80       = 11000010 10000000
	   0x7ff, 11 bits = df bf       = 11011111 10111111
	   0x800, 16 bits = b0 a0 80    = 11100000 10100000 10000000
	  0xffff, 16 bits = ef bf bf    = 11101111 10111111 10111111
	 0x10000, 21 bits = c0 90 80 80 = 11110000 10010000 10000000 10000000
	0x1fffff, 21 bits = f9 bf bf bf = 11110111 10111111 10111111 10111111
         ...
	0x1ffffffffff, 41 bits = ff bf bf bf bf bf bf bf

	If utf8Bytes > 2, then code is at least 0x10000, so overflow
      */
	utf8Bytes = 1;
	utf8Res = c;
	while (((c<<=1) & 0xC0) == 0xC0) {
	  utf8Bytes++;
	}
	utf8Res = (utf8Res&(0x3f>>utf8Bytes)) << (6*utf8Bytes);
	if (utf8Bytes > 2) utf8Res = 0xFFFF; /* Too big value, can't be encoded */
	/* UTF8 code not finished, no character to return. */
	return;
      }
#ifdef SUPPORT_UIMESSAGES
      if (inProtocol) {
	if (isxdigit(c)) {
	  c = tolower(c);
	  value = (value << 4) | (c-(isdigit(c)?'0':'a'));
	} else if (c == 'm') {
	  address = value;
	  value = 0;
	} else if (c == 's') {
	  SystemUiMessage(0, address, value);
	  inProtocol = 0;
	}
	return;
      }
#endif
    }
  }

  *uart.rxWr = c;
  uart.rxWr = ringmodY(uart.rxWr, MAKEMODF(uart.rxSize));
}



void UartPutChar(char c) {
  __mem_y char *nextTxWr = ringmodY(uart.txWr, MAKEMODF(uart.txSize));

  while (nextTxWr == uart.txRd) {
    Delay(1);
  }

  Disable();
  if (!(PERIP(UART0_STATUS) & UART_ST_TXFULL) && uart.txWr == uart.txRd) {
    PERIP(UART0_DATA) = c;
  } else {
    *uart.txWr = c;
    uart.txWr = nextTxWr;
  }
  Enable();
}

char UartGetChar(void) {
  char ch;
  while (uart.rxRd == uart.rxWr) {
    Delay(1);
  }
  ch = *uart.rxRd;
  uart.rxRd = ringmodY(uart.rxRd, MAKEMODF(uart.rxSize));
  return ch;
}

extern SIMPLE_FILE uartInFile;

IOCTL_RESULT UartIoctl(register __i0 VO_FILE *self, s_int16 request, IOCTL_ARGUMENT arg) {
  if (request == IOCTL_TEST) {
    return (self == &uartInFile) ?
      (s_int16)(uart.rxWr-uart.rxRd)%uart.rxSize :
      (s_int16)(uart.txWr-uart.txRd)%uart.txSize;
  }
  return S_ERROR;
};

u_int16 UartWrite(register __i0 VO_FILE *self, void *buf, u_int16 sourceIndex, u_int16 bytes) {
#if 1
  u_int16 utf16 = *((u_int16 *)buf);
  if (utf16 < 128) {
    UartPutChar(utf16);
  } else if (utf16 < 0x800) {
    UartPutChar(0xc0|utf16>>6);
    UartPutChar(0x80|(utf16&0x3f));
#if 0
    UartPutChar('#');
    UartPutChar('2');
    UartPutChar('#');
#endif
  } else {
    UartPutChar(0xe0|utf16>>12);
    UartPutChar(0x80|((utf16>>6)&0x3f));
    UartPutChar(0x80|(utf16&0x3f));
#if 0
    UartPutChar('#');
    UartPutChar('3');
    UartPutChar('#');
#endif
  }
#else
  UartPutChar(*((u_int16 *)buf)); //Send character to raw UART port
#endif
  return 1;
};

u_int16 UartRead(register __i0 VO_FILE *self, void *buf, u_int16 destinationIndex, u_int16 bytes) {
  char ch;
  ch = UartGetChar();
  /* Hack: Force vo_fgetc() to get 16-bit value in case UTF16 code doesn't
     fit into values 0..255 */
  if (destinationIndex == 1 && bytes == 1) {
    destinationIndex = 0;
    bytes = 2;
  } else {
    destinationIndex = bytes = 1;
  }
  MemCopyPackedBigEndian(buf, destinationIndex, &ch, destinationIndex, bytes);
  return 1;
};

u_int16 StdInWrite(register __i0 VO_FILE *self, void *buf, u_int16 sourceIndex, u_int16 bytes) {
  *uart.rxWr = ((char*)buf)[0]>>8;
  uart.rxWr = ringmodY(uart.rxWr, MAKEMODF(uart.rxSize));
  return 1;
};



const FILEOPS ConsoleInFileOperations = { NULL, NULL, UartIoctl, UartRead, StdInWrite };
//DLLENTRY(uartInFile)
SIMPLE_FILE uartInFile = {__MASK_PRESENT | __MASK_OPEN | __MASK_READABLE | __MASK_WRITABLE | __MASK_FILE, NULL, &ConsoleInFileOperations};

const FILEOPS ConsoleOutFileOperations = { NULL, NULL, UartIoctl, UartRead, UartWrite };
//DLLENTRY(uartOutFile)
SIMPLE_FILE uartOutFile = {__MASK_PRESENT | __MASK_OPEN | __MASK_READABLE | __MASK_WRITABLE | __MASK_FILE, NULL, &ConsoleOutFileOperations};

extern u_int16 IntUartRxVector;
extern u_int16 IntUartTxVector;

void init(char *parameters) {
  uart.origIntEna0Mask = PERIP(INT_ENABLE0_HP) | ~(INTF_UART0_TX|INTF_UART0_RX);
  uart.origRxVec = ReadIMem((0x20+INTV_UART0_RX));
  uart.origTxVec = ReadIMem((0x20+INTV_UART0_TX));
  uart.origIn = vo_stdin;
  uart.origOut = vo_stdout;
}

// Library finalization code. This is called when DropLibrary is called
void fini(void) {
  PERIP(INT_ENABLE0_HP) &= uart.origIntEna0Mask;
  WriteIMem((0x20+INTV_UART0_RX), uart.origRxVec);
  WriteIMem((0x20+INTV_UART0_TX), uart.origTxVec);
  vo_stdin = uart.origIn;
  vo_stdout = uart.origOut;
}

// The main function returns a pointer to the UART file.
ioresult main(char *parameters) {
  int in=0, out=0;
  int nParam=0;
  char *p = parameters;
  int i;
  ioresult retCode = S_ERROR;
  
  nParam = RunProgram("ParamSpl", parameters);

  for (i=0; i<nParam; i++) {
    if (!strcmp(p, "-h")) {
      printf("Usage: UartIn [-i[x]|-o[x]|-h]\n"
	     "-i[x]\tUse input / set size to x (%d)\n"
	     "-o[x]\tUse output / set size to y (%d)\n"
	     "-h\tShow this help\n\n",
	     uart.rxSize, uart.txSize);
      retCode = S_OK;
      goto finally;
    } else if (!strncmp(p, "-i", 2)) {
      u_int16 t = strtol(p+2, NULL, 0);
      in = 1;
      if (t > 1) uart.rxSize = t;
    } else if (!strncmp(p, "-o", 2)) {
      u_int16 t = strtol(p+2, NULL, 0);
      out = 1;
      if (t > 1) uart.txSize = t;
    } else {
      printf("W: Unknown parameter \"%s\"\n", p);
      WaitUart0ToClear(100);
    }
    p += strlen(p)+1;
  }

  if (!(in|out)) {
    in = out = 1;
  }

  if (in) {
    Disable();
    if (uart.rxRd) FreeMemY(uart.rxRd, uart.rxSize);
    uart.rxRd = uart.rxWr = AllocMemY(uart.rxSize, CeilPow2(uart.rxSize));
    if (!uart.rxRd) SysFatalError("RX Buffer");
    WriteIMem((0x20+INTV_UART0_RX), ReadIMem((u_int16)(&IntUartRxVector)));
    PERIP(INT_ENABLE0_HP) |= INTF_UART0_RX;
    vo_stdin = &uartInFile;
    Enable();
  }

  if (out) {
    Disable();
    if (uart.txRd) FreeMemY(uart.txRd, uart.txSize);
    uart.txRd = uart.txWr = AllocMemY(uart.txSize, CeilPow2(uart.txSize));
    if (!uart.txRd) SysFatalError("TX Buffer");
    WriteIMem((0x20+INTV_UART0_TX), ReadIMem((u_int16)(&IntUartTxVector)));
    PERIP(INT_ENABLE0_HP) |= INTF_UART0_TX;
    vo_stdout = &uartOutFile;
    Enable();
  }

  retCode = S_OK;
 finally:
  return retCode;
}
