// page ref.: http://www.atmel.com/dyn/resources/prod_documents/doc8271.pdf
// compile with gcc avrtty.c  -o avrtty, and run ./avrtty | less
// to check against tables on pp. 196..199

#ifdef __AVR
#include "avrtty.h"
#include "avrtime.h"
#include <string.h>
#else  // __AVR
#include <stdint.h>
#include <stdio.h>
uint32_t time_sysosc;
const uint32_t time_syspsc = 0;
struct tty_baudcode_t {
  uint16_t code;
  uint32_t err; // divide with 100 to get %
};
uint8_t tty_baudcode(uint32_t baudrate, struct tty_baudcode_t arr[2]);

int main(int argc, char *argv[]) {
  uint32_t freq[] = {
    1000000, 1843200, 2000000, 3686400, 4000000, 7372800, 8000000, 11059200, 14745600, 16000000, 18432000, 20000000
  };
  uint32_t bauds[] = {
    2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 76800, 115200,
    230400, 250000, 500000, 1000000
  };
  int kx, ix;
  struct tty_baudcode_t arr[2];

  (void) argc;
  (void) argv;
  for (kx = 0; kx < sizeof(freq)/sizeof(uint32_t); kx++) {
    time_sysosc = freq[kx];
    printf("\n  %d\n\n", time_sysosc);
    for (ix = 0; ix < sizeof(bauds)/sizeof(uint32_t); ix++ ) {
      (void) tty_baudcode(bauds[ix], arr);
      printf("%d\t %d\t%5.2f\t %d\t%5.2f\n",
	     bauds[ix], arr[0].code, ((double) arr[0].err)/100, arr[1].code, ((double) arr[1].err)/100);
    }
  }
  return 0;
}

#endif // __AVR

/*********************
 low level
*/

// result of this checked against tables p.196-199
uint8_t tty_baudcode(uint32_t baudrate, struct tty_baudcode_t arr[2]) {
  // x2 is things calculated with double speed, x1 is without
  uint32_t baudcode1, baudcode2;
  uint32_t merr1, merr2;
  uint32_t baud1, baud2;
  uint16_t val;
  uint32_t fcpu = time_sysosc >> time_syspsc;

  // unknown system/cpu clock frequency
  if (fcpu <= 0) return 255;

  // formulas on p. 180, rounding to nearest integer added
  // otherwise I don't the same values as on p. 199
  if (fcpu > 8UL * baudrate)
    baudcode1 = (fcpu + 8UL * baudrate) / (16UL * baudrate) - 1UL;
  else
    baudcode1 = 0;

  if (fcpu > 4UL * baudrate)
    baudcode2 = (fcpu + 4UL * baudrate) / ( 8UL * baudrate) - 1UL;
  else
    baudcode2 = 0;

#define UBRRMAX 4095
  // UBRR0 is a 12bit register, i.e. 0 <= UBRR0 <= 4096-1
  if (baudcode1 > UBRRMAX) baudcode1 = UBRRMAX;
  if (baudcode2 > UBRRMAX) baudcode2 = UBRRMAX;
 
  val = 16 * (baudcode1 + 1);
  baud1 = fcpu / val;

  val =  8 * (baudcode2 + 1);
  baud2 = fcpu / val;

  merr1 = baud1 > baudrate ? baud1 - baudrate : baudrate - baud1;
  merr2 = baud2 > baudrate ? baud2 - baudrate : baudrate - baud2;
  merr1 *= 10000;
  merr2 *= 10000;
  merr1 += baudrate/2;
  merr2 += baudrate/2;
  merr1 /= baudrate;
  merr2 /= baudrate;

  arr[0].code = baudcode1;
  arr[0].err = merr1;
  arr[1].code = baudcode2;
  arr[1].err = merr2;

  return 0;
}

#if defined(__AVR) && defined(UCSR0A)

void tty_setbaud(uint8_t port, uint16_t baudcode, uint8_t u2x) {
  (void) port;
  UBRR0 = baudcode;
  if (u2x) u2x = _BV(U2X0);
  UCSR0A = u2x;
  /* compiler complained: will never be executed, why??
  if (u2x == 0) {
    UCSR0A = 0; //&= ~_BV(U2X0);
  } else {
    UCSR0A = _BV(U2X0); //|= _BV(U2X0);
  }
  */
}

void tty_power(uint8_t port, uint8_t enable_rx, uint8_t enable_tx) {
  uint8_t flag = 0;

  (void) port;
  if (enable_rx) flag |= _BV(RXEN0);
  if (enable_tx) flag |= _BV(TXEN0);

  if (flag) {
    power_usart0_enable();
    UCSR0A = 0x00;
    UCSR0B = flag;
    UCSR0C = 0x06; // async, no parity, 1stop bit, 8bit/byte
  } else {
    power_usart0_disable();
    UCSR0B &= (uint8_t) ~(_BV(RXEN0) | _BV(TXEN0));
  }
}

uint8_t tty_writechar(uint8_t port, uint8_t cc) {
  if (tty_writeable(port)) {
    tty_rwrite(port, cc);
    return 1;
  } else {
    return 0;
  }
}

uint8_t tty_readchar(uint8_t port, uint8_t *cc) {
  if (tty_readable(port)) {
    tty_rread(port, cc);
    return 1;
  } else {
    *cc = 0;
    return 0;
  }
}

/*********************
 mid level
*/

struct ttydata_t ttydata[TTYDATASZ];

void tty_init(uint8_t port, uint32_t baudrate, uint8_t *wbuf, uint8_t wsz, uint8_t *rbuf, uint8_t rsz) {
  struct tty_baudcode_t arr[2];
  if (wbuf && wsz > 0) buf_init(&ttydata[port].wr, wbuf, wsz);
  if (rbuf && rsz > 0) buf_init(&ttydata[port].rd, rbuf, rsz);

  tty_power(port, 1, 1);
  tty_baudcode(baudrate, arr);
  if (arr[0].err < 2*arr[1].err) tty_setbaud(port, arr[0].code, 0);
  else tty_setbaud(port, arr[1].code, 1);
  //baudrate /= 10;
  ttydata[port].bytetime = (baudrate/2 + (time_sysosc >> (time_syspsc + time_tpsc))) / baudrate;
}

uint8_t tty_run(uint8_t port, uint8_t enable_echo) {
  uint8_t icc = 0;

  if (tty_readable(port) && tty_readchar(port, &icc) == 1) {
    buf_putlast(&ttydata[port].rd, icc);
    if (enable_echo) {
      buf_putlast(&ttydata[port].wr, icc);
      if (icc == '\r') buf_putlast(&ttydata[port].wr, '\n');
    }
  }

  if (buf_len(&ttydata[port].wr) && tty_writeable(port)) {
    uint8_t occ;
    buf_getfirst(&ttydata[port].wr, &occ);
    tty_rwrite(port,occ);
  }
  return icc;
}

/*****************************************
 serial port is connected to a half duplex bus

 to simplyfy buffer handling (for .wr atleast), make sure .sta == 0
 when possible

 for bus writes, we can experience collissions, if so, send out jammer
 bytes (zeroes) to notify sender, and then resend packet after some delay
*/

#define HDPORT 0
#define JAMMER_BYTE '\0'
#define RD ttydata[HDPORT].rd
#define WR ttydata[HDPORT].wr

enum tty_hdstate_t {
  tty_hdstart,
  tty_hdidle,
  tty_hdfree,
  tty_hdrecv,
  tty_hdsend,
  tty_hdcoll,
};

static enum tty_hdstate_t tty_hdstate = tty_hdstart;
static uint32_t tty_hdipts;
static uint32_t tty_hdicts;
uint8_t chk_sta;
uint8_t col_cnt;

uint8_t tty_recv(uint8_t *buf, uint8_t sz) {
  // only return pkg after whole is received
  if (tty_hdstate != tty_hdrecv && RD.len) {
    uint8_t len = RD.len;
    if (sz < len) len = sz;
    memcpy(buf, RD.buf, RD.len < sz ? RD.len : sz);
    buf_flush(&RD);
    return len;
  } else return 0;
}

uint8_t tty_send(uint8_t *buf, uint8_t sz) {
  uint8_t len = WR.sz;

  if (WR.len) return 0;

  WR.sta = chk_sta = 0;
  if (sz < WR.sz) WR.sz = sz;
  memcpy(WR.buf, buf, len);
  WR.len = len;
  return len;
}

static uint8_t rdbyte(void) {
  uint8_t icc;
  if (tty_readchar(HDPORT, &icc) == 1) {
    if (RD.len < RD.sz) {
      RD.buf[RD.len] = icc;
      RD.len++;
    }
    //buf_putlast(&ttydata[HDPORT].rd, icc);
    return icc;
  }
  return 0;
}
static uint8_t wrbyte(void) {
  if (buf_len(&ttydata[HDPORT].wr) && tty_writeable(HDPORT) && WR.len) {
    uint8_t occ = WR.buf[WR.sta];
    WR.len--;
    WR.sta++;
    return tty_rwrite(HDPORT,occ);
  }
  return 0;
}
//#define BYTE_TIMEOUT (time_current - tty_hdicts >  200 * ttydata[HDPORT].bytetime)
#define BYTE_TIMEOUT (icc == '\r' || icc == '\n')
#define PKT_TIMEOUT  (time_current - tty_hdipts > 1000 * ttydata[HDPORT].bytetime)

void tty_hdrun(void) {
  uint8_t icc;
  switch (tty_hdstate) {
  case tty_hdstart:
    tty_hdstate = tty_hdidle;
    tty_hdipts = time_current;
    tty_hdicts = time_current;
    RD.len = RD.sta = WR.len = WR.sta = chk_sta = 0;
    break;
  case tty_hdidle:
    if ((icc = rdbyte())) {
      tty_hdstate = tty_hdrecv;
      tty_hdicts = time_current;
    } else if (PKT_TIMEOUT) {
      tty_hdstate = tty_hdfree;
    }
    break;
  case tty_hdfree:
    if ((icc = rdbyte())) {
      tty_hdstate = tty_hdrecv;
      tty_hdicts = time_current;
    } else if (WR.len) {
      tty_hdstate = tty_hdsend;
      chk_sta = 0;
      (void) wrbyte();
    }
    break;
  case tty_hdrecv:
    if ((icc = rdbyte())) {
      // we cannot here identify collisions between other nodes
      tty_hdicts = time_current;
      //} else if (BYTE_TIMEOUT) { // timeout interbyte: goto idle
      if (BYTE_TIMEOUT) {
	tty_hdstate = tty_hdidle;
	tty_hdipts = time_current;
      }
    }
    break;
  case tty_hdsend:
    // collision check/detect
    if (chk_sta < WR.sta) {
      if (tty_readchar(HDPORT, &icc) == 1) {
	if (icc == WR.buf[chk_sta]) {
	  chk_sta++;
	} else {
	  tty_hdstate = tty_hdcoll;
	  col_cnt = 5;
	}
      }
    }

    if (WR.len) {
      (void) wrbyte();
    } else if (chk_sta >= WR.sta) {
      // WR.len == 0 here, i.e. whole packet has been sent
      tty_hdstate = tty_hdidle;
      tty_hdipts = time_current;
      WR.len = WR.sta = chk_sta = 0;
    }
    break;
  case tty_hdcoll:
    if (col_cnt) {
      if (tty_writechar(HDPORT, JAMMER_BYTE) == 1) {
	col_cnt--;
      }
      if (tty_readchar(HDPORT, &icc)) tty_hdicts = time_current;
    } else {
      if (tty_readchar(HDPORT, &icc)) {
	tty_hdicts = time_current;
	//} else if (BYTE_TIMEOUT) { // timeout interbyte: goto idle
	if (BYTE_TIMEOUT) {
	  tty_hdstate = tty_hdidle;
	  tty_hdipts = time_current;
	}
      }
    }
    break;
  }
}

#endif
