/*                                              -*- mode:C++; fill-column:100 -*-
  SFBHWSerialPortable.h - Low-level serial support for the (Unix / Posix / *?) host
  Copyright (C) 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$
*/

/**
  \file SFBHWSerialPortable.h - Low-level serial support for the (Unix / Posix / *?) host
  \author David H. Ackley.  
  \date (C) 2009 All rights reserved.
  \lgpl
 */

#ifdef SFBHWSERIALBOARD_H
#error SFBHWSerialBoard.h and SFBHWSerialPortable.h must not be #included together!
#endif

#ifndef SFBHWSERIALPORTABLE_H
#define SFBHWSERIALPORTABLE_H

#include "SFBPacket.h"
#include "SFBFrame.h"
#include "SFBByteBuffer.h"
#include "SFBAssert.h"

class SFBSerial; /* FORWARD DECLARATION */

/**
 * The object supporting 'hardware level' serial operations on the host.  
 *
 * WARNING!  There are two classes _both_ named 'SFBHWSerial'; this one for host-side communication,
 * defined in SFBHWSerialPortable.h and SFBHWSerialPortable.cpp, and another one for SFB on-board
 * communication, defined in SFBHWSerialBoard.h and SFBHWSerialBoard.cpp.  Only one definition is
 * seen on any particular compilation, due to conditional compilation in SFBHWSerial.h.  This
 * definition is used if HOST_MODE is defined.
 *
 * This is awkward, but was done to avoid inheritance, with its associated complexities and
 * additional vtable pointers.
 */
class SFBHWSerial {  /* Note we are defining 'SFBHWSerial', not 'SFBHWSerialPortable'! */

public:

  SFBHWSerial(int face) ;

  ///
  // A routine for deliberately injecting line errors on output for testing.  byteErrorOdds of 0
  // means 'no errors', otherwise there is a 1-in-byteErrorOdds chance that a byte will be mangled.
  //
  // In addition, if the immediately previous byte was mangled by an error, and nextErrorOdds is
  // non-zero, then there is also a 1-in-nextErrorOdds that this byte will be mangled.
  //
  // Both odds should be 0 for normal use.  For testing, something like byteErrorOdds==1000,
  // nextErrorOdds=4 is enough to make getting a clean burn a bit of a challenge.
  //
  // Note that a byteErrorOdds of 1 will attempt to mangle every byte.  But since the mangling
  // involves a 50% chance of flipping each bit of the byte, there is a 1-in-256 chance that a
  // 'mangled' byte will in fact be unaltered.
  void setNoiseParameters(u32 byteErrorOdds, u32 nextErrorOdds) {
    this->errorOdds = byteErrorOdds;
    this->nextErrorOdds = nextErrorOdds;
    this->lastByteError = false;
  }

  SFBFrame & getFrame() { return theFrame; }

  void putcBlocking(const u8 byte) ;

  static u32 getFirstFace() { return NORTH; }
  static bool moreFaces(u32 face) { return face <= WEST; }
  static u32 nextFace(u32 face) { return face+1; }
  static char faceCode(u32 face) { return "NSEW"[face]; }
  static SFBSerial & getFace(u32 face) ;

  bool getPreferOddParity() { return false; } // Odd vs none; it's N unless both say O; we say N so it's N with us
  u8 getPreferredBaudCode() { return pbc; }
  u8 getLowBaudCode() { return lbc; }
  u8 getHighBaudCode() { return hbc; }

  void setPreferredBaudCode(u8 code) ;
  void setLowBaudCode(u8 code) ;
  void setHighBaudCode(u8 code) ;

  void predispatch() ;

  void start(u32 baud, bool oddParity=false) ;
  void stop() ;
  void setBaudRate(u32 baud,bool isOddParity=false) ;
  void checkTx() ;

  void * getUFD() { return ufd; }
  void setUFD(void * newUFD) ;

  bool canRead() ;
  int readByte() ;

  void forceRxSync() ;

private:

  void readInput();
  void writeOutput();
  bool writeByte(u8 byte);

  PacketBuffer & rxBuf;         /* Inbound packets are dispatched from here */
  SFBRxByteBuffer rxBytes;      /* Interrupt level writes raw inbound UART RX data to here */
  SFBTxByteBuffer txBytes;      /* Interrupt level reads raw outbound UART TX data from here */
  SFBFrame theFrame;            /* Packets are framed and deframed here */
  bool isRunning;
  const u8 face;
  void * ufd;
  u8 pbc, lbc, hbc;
  int readBuf;
  int bufferedChar;

  u32 errorOdds;  
  u32 nextErrorOdds;
  bool lastByteError;

  enum {
    FLAG_RUNNING = 0x01     /* Port has been start()ed more recently than is was stop()ped */
  };

public:
  static void startup_initialization() ;
};

extern bool canReadFromFd(int fd);
extern int readByteFromFd(int fd);
#define CAN_WRITE(fd) (REGISTER_OFFSET(fd,UxLSR) & 0x20)
#define WRITE_BYTE(fd,byte) (REGISTER_OFFSET(fd,UxTHR) = (byte))
#define CAN_READ(fd) canReadFromFd(fd)
#define READ_BYTE(fd) readByteFromFd(fd)

#endif  /* SFBHWSERIALPORTABLE_H */

