/*
  SFBHWSerialPortable.cpp - (At least semi-)Portable low-level serial port support
  Copyright (C) 2008-2009 The Regents of the University of New Mexico.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA

  $Id$
*/

#include <ctype.h>            /* for isprint and isspace */
#include <stdlib.h>           /* for exit() */
#include <stdio.h>            /* Standard input/output definitions */
#include <string.h>           /* String function definitions */
#include <unistd.h>           /* UNIX standard function definitions */
#include <fcntl.h>            /* File control definitions */
#include <errno.h>            /* Error number definitions */
#include <time.h>             /* for struct timeval */

#include "SFBHWSerialPortable.h"
#include "SFBSerial.h"
#include "SFBAssert.h"
#include "SFBHWMisc.h"
#include "SFBPrintf.h"
#include "SFBRandom.h"          /* for random(int) */ 

#include "serial.h"             /* for union filedescriptor */ 

static void quit(int code) {
  exit(code);
}

/*
 * set or change baud rate on fd
 */
void SFBHWSerial::setBaudRate(u32 baudRate,bool isOddParity) {
  API_ASSERT_NONNULL(ufd);
  API_ASSERT_FALSE(isOddParity); // WE ONLY SUPPORT NO PARITY IN THESE HERE PARTS 
  int rc = serial_setspeed((union filedescriptor*) ufd, baudRate);
  if (rc) {
    fprintf(stderr, 
            "Can't set baud rate on device: %s\n", strerror(-rc));
    exit(1);
  }
  logNormal("[%s baud rate set to %d]\n",FACE_NAME(face),baudRate);
}

SFBSerial Faces[FACE_COUNT] IN_BODYRAM = {
  SFBSerial(NORTH),
  SFBSerial(SOUTH),
  SFBSerial(EAST),
  SFBSerial(WEST)
};

#if 0
void * fdToFace[FACE_COUNT] = { -1, -1, -1, -1};
int getFaceFromFd(void * fd) {
  for (int i = 0; i < FACE_COUNT; ++i)
    if (fdToFace[i]==fd) 
      return i;
  return -1;
}
bool canReadFromFd(int fd) {
  if (fd <= 0) return false;
  int face = getFaceFromFd(fd);
  if (face<0) return false;
  return Faces[face].getHWSerial().canRead();
}
int readByteFromFd(int fd) {
  if (fd <= 0) return -1;
  int face = getFaceFromFd(fd);
  if (face<0) return -1;
  return Faces[face].getHWSerial().readByte();
}
#endif

void SFBHWSerial::setUFD(void * newUFD) {
  ufd = newUFD; 
  //  fdToFace[face] = fd;
}

#if 0
void SFBHWSerial::setOutputFD(int newFD) {
  ofd = newFD; 
  fdToFace[face] = fd;
}
#endif

SFBSerial & SFBHWSerial::getFace(u32 face) {
  API_ASSERT_VALID_FACE(face);
  return Faces[face];
}

static u8 byteBuffers[FACE_COUNT][2][BYTE_BUFFER_BYTES] ;
static PacketBuffer packetBuffers[FACE_COUNT] ;
#if 0
u8 buffers[FACE_COUNT][2][SFBPACKETIO_BUFFER_SIZE_BYTES] = {
  { { 0 }, { 0 }, },
  { { 0 }, { 0 }, },
  { { 0 }, { 0 }, },
  { { 0 }, { 0 }, }};
#endif

void SFBHWSerial::setLowBaudCode(u8 code) {
  API_ASSERT_MAX(code,10);
  API_ASSERT(code <= SFBAUD_RENDEZVOUS,E_API_ARGUMENT); // Can't exclude rendezvous speed
  lbc = code; 

  if (lbc > hbc)
    hbc = lbc;
}
void SFBHWSerial::setPreferredBaudCode(u8 code) {
  API_ASSERT_MAX(code,10);
  API_ASSERT(code >= lbc && code <= hbc,E_API_ARGUMENT); // Must stay in your own range
  pbc = code; 
}
void SFBHWSerial::setHighBaudCode(u8 code) {
  API_ASSERT_MAX(code,10);
  API_ASSERT(code >= SFBAUD_RENDEZVOUS,E_API_ARGUMENT); // Can't exclude rendezvous speed
  hbc = code;

  if (hbc < lbc)
    lbc = hbc;
}

SFBHWSerial::SFBHWSerial(int theFace) :
  rxBuf(packetBuffers[theFace]),
  rxBytes(byteBuffers[theFace][0]),
  txBytes(byteBuffers[theFace][1]),
  theFrame(rxBytes,txBytes,rxBuf,theFace),
  isRunning(false),
  face(theFace),
  ufd(0),
  pbc(4),
  lbc(0),
  hbc(6),
  readBuf(-1),
  bufferedChar(-1),
  errorOdds(0),
  nextErrorOdds(0),
  lastByteError(false)

{ }

void SFBHWSerial::checkTx() { /* nothing to do */ }

void SFBHWSerial::readInput() {
  if (!ufd) return;
  while (1) {
    u8 ch;
    int n = serial_recv((union filedescriptor*) ufd,&ch,1);
    if (n==0) 
      return;
    if (n<0) { 
      if (errno==EAGAIN || errno==EINTR || errno==0) return;
      logQuiet("%s died in readInput: %s\n",FACE_NAME(face),strerror(errno));
      quit(4);
    }
    rxBytes.storeByteIL(ch);    /* Store it (if possible) */
  }
}

bool SFBHWSerial::canRead() {
  if (!ufd) 
    return false;
  if (readBuf < 0)
    readBuf = readByte();
  return readBuf >= 0;
}

int SFBHWSerial::readByte() {
  if (!ufd) 
    return -1;
  if (readBuf < 0) {
    u8 buf[1];
    int n = serial_recv((union filedescriptor*)ufd,buf,1);
    if (n==0) 
      return -1;
    if (n<0) { 
      if (errno==EAGAIN || errno==EINTR) return -1;
      logQuiet("%s died in readByte: %s\n",FACE_NAME(face),strerror(errno));
      quit(4);
    }
    readBuf = buf[0];
  }
  int ret = readBuf;
  readBuf = -1;
  return ret;
}

bool SFBHWSerial::writeByte(u8 byte) {
  if (!ufd) 
    return false;

  // serial_send dies if the write fails.. So, no error checking on us..
  serial_send((union filedescriptor*) ufd,&byte,1);
  return true;
}

/* Write to output fd 'ofd', if any, else try input i/o fd 'fd', else nothing */
void SFBHWSerial::writeOutput() {

  if (!ufd) return;
  if (bufferedChar >= 0) {
    if (writeByte((u8) bufferedChar)) 
      bufferedChar = -1;
    else
      return;
  }
  while (1) {
    int val;
    val = txBytes.fetchByteIL();
    if (val < 0)
      break;                    // Nothing to write

    if (!writeByte((u8) val)) {
      bufferedChar = (u8) val;      // No room to output
      break;
    }
  }
}

void SFBHWSerial::putcBlocking(const u8 byte) {
  while (bufferedChar >= 0) { 
    delayMicroseconds(100);
    writeOutput();
  }
  bufferedChar = byte;
  while (bufferedChar >= 0) { 
    delayMicroseconds(100);
    writeOutput();
  }
}



void SFBHWSerial::predispatch() { /* do whatever IO we can, now */
  if (ufd) {        // If this guy's in use..
    readInput();
    writeOutput();
  }
}

#if 0
void SFBHWSerial::forceRxSync() {
  rxBytes.forceSync();
}
#endif

void SFBHWSerial::start(u32 baud,bool oddParity) {

  API_ASSERT_FALSE(oddParity);  // We only do parity none

  if (isRunning)
    stop();      // Stop, to get a clean start

  ////
  // Initialize state

  rxBytes.resetBG();
  txBytes.resetBG();

  ////
  // Set up the hardware including the baud rate
  setBaudRate(baud);

  isRunning = true;
}

void SFBHWSerial::stop()
{
  /* We can't close the fd, here, because start() didn't open it.  Hmm..
  if (fd >= 0)
    close(fd);
  */
  isRunning = false;
}
