/*                                       -*- mode:C++; fill-column: 100 -*-

  SFBByteBuffer.h - Circular byte buffering between the UART interrupt and the 
                    background packet dispatching levels
  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 SFBByteBuffer.h Support circular byte buffering between UART interrupt level and background
        packet dispatching.  The main goals of this code are:
        -# To provide fast basic buffer functionality, including O(1) store and fetch
        -# To provide limited advanced buffer functionality, specifically:
           - On the inbound/rx side, deliver H/W error and buffer overflow status reports in order
             of occurrence with respect to delivered data bytes; and
           - On the outbound/tx side, accept data from background processing one byte at a time, but
             defer shipping any of them until they are 'committed' by background processing, to
             support all-or-none shipment of blocked data (a.k.a.  'packets') regardless of
             potential buffer overflows.
        -# To be as simple and auditable as possible consistent with those functions, to make it
           easier to avoid races between interrupt and background processing; but also 
        -# To leave interrupts enabled during all frequently-used background buffer accesses.

        Note that, except for the tx side 'commit' concept, SFBByteBuffer does \e not deal with
        packet framing or deframing; it simply buffers a stream of uninterpreted data bytes (with
        interleaved status information where needed).  So received packets must be deframed by
        background processing as bytes are removed from an SFBRxByteBuffer, and bytes must be packet
        framed by background process as bytes are stored in an SFBTxByteBuffer.

  \author David H. Ackley.  
  \date (C) 2009 All rights reserved.
  \lgpl
 */
#ifndef SFBBYTEBUFFER_H
#define SFBBYTEBUFFER_H

#include "SFBTypes.h"       /* For u8 etc */
#include "SFBConstants.h"   /* For BYTE_BUFFER_BYTES */

/**
   Base class providing services common to #SFBRxByteBuffer and #SFBTxByteBuffer.
 */

class SFBByteBuffer {

protected:
  u8 (&buf)[BYTE_BUFFER_BYTES];
  u16 oldIndex;
  u16 newIndex;


  SFBByteBuffer(u8 (&buffer)[BYTE_BUFFER_BYTES]) 
    : buf(buffer),
      oldIndex(0),
      newIndex(0)
  { }

  u16 spaceAvailable() {
    return ((BYTE_BUFFER_BYTES-1)-(newIndex-oldIndex))&BYTE_BUFFER_MASK;
  }

  u16 bytesAvailable() {
    return (newIndex-oldIndex)&BYTE_BUFFER_MASK;
  }

  u16 wrap(u16 index) {
    return index&BYTE_BUFFER_MASK;
  }
  void uncheckedStoreByte(u8 data) {
    buf[newIndex] = data;
    newIndex = wrap(newIndex+1);
  }
  u8 uncheckedFetchByte() {
    u8 val = buf[oldIndex];
    oldIndex = wrap(oldIndex+1);
    return val;
  }

  enum {
    BB_ESC = 0xA5,
    BB_EESC = 0x00       // BB_ESC+BB_EESC -> single BB_ESC datum
                         // BB_ESC+anything else -> overflow and/or hw error occurred, anything else==flags
  };

};

/**
   A specialized #SFBByteBuffer that handles asynchronous data reception, i.e., data is added to the
   buffer at interrupt level (IL), and removed from the buffer in the background process (BG).

   An #SFBRxByteBuffer promises to BG that its #bytesAvailable() will \e never decrease during IL
   processing.  So if BG observes that #bytesAvailable() are large enough to do something (e.g.,
   fetch one byte), BG can then go ahead and perform that action without locking or disabling
   interrupts.

*/
class SFBRxByteBuffer : public SFBByteBuffer {
public:

  enum {
    BBFLAG_ERRORBITS= PK_OVERRUN|PK_PARITY|PK_FRAMING|PK_BREAK, ///< Error bits; H/W detected comms problem(s) if non-zero
    BBFLAG_OVERFLOW=  PK_BUFFER, ///< Error bits; S/W detected buffer overrun or reset if non-zero; data may have been lost
    BBFLAG_ZERO=      PK_DELETED|PK_BAD_ESCAPE  ///< These bits will be zero in any error code returned from #fetchByteBG
  };

  SFBRxByteBuffer(u8 (&buffer)[BYTE_BUFFER_BYTES]) : SFBByteBuffer(buffer), flags(0) { }

  /**
     Called in background to reset an rx buffer
  */
  void resetBG() ;

  /**
     Called in background to fetch next byte from old end of rx buf, if any.
   */
  int fetchByteBG() ;

  /**
     Called at interrupt level to record some error status that arrived from the hardware.
   */
  void storeErrorStatusIL(u8 status) {
    flags |= status;            // Just add in the bits, will be flushed in storeByteIL
  }

  /**
     Called at interrupt level to store a byte at new end of rx buf, if possible.
   */
  void storeByteIL(u8 data) ;

private:
  u8 flags;

};

/**
   A specialized #SFBByteBuffer that handles asynchronous data transmission, i.e., data is added to
   the buffer in the background process (BG), and removed from the buffer at interrupt level (IL).

   An #SFBTxByteBuffer promises to BG that its #spaceAvailable() will \e never decrease during IL
   processing.  So if BG observes that #spaceAvailable() is large enough to do something (e.g.,
   store one byte), BG can then go ahead and perform that action without locking or disabling
   interrupts.

*/
class SFBTxByteBuffer : public SFBByteBuffer {
public:
  SFBTxByteBuffer(u8 (&buffer)[BYTE_BUFFER_BYTES]) : SFBByteBuffer(buffer), pendingCount(0) { }

  /**
     Called in background to reset a tx buffer
  */
  void resetBG() ;

  /**
     Called in background to store a byte at new end of tx buf, if possible.  Note that bytes stored
     in this manner will \e not actually ship until the next #shipBytesBG call.
   */
  void storeByteBG(u8 data) ;

  /**
     Called in background to check if \a count more bytes can be stored at new end of tx buf,
     without a buffer overrun.
   */
  bool canStoreBytesBG(u32 count) ;

  /**
     Called in background to either start the transmission of, or discard, \e all bytes stored via
     #storeByteBG since the last call on #shipBytesBG or #resetBG.  If the buffer overflowed during
     one or more of those #storeByteBG calls, then all pending bytes will be discarded, otherwise
     all pending bytes will be released for transmission.

     \return \c true if all pending bytes were released for transmission, and \c false if all
     pending bytes were discarded.
   */
  bool shipBytesBG() ;


  /**
     Called at interrupt level to fetch next byte from old end of tx buf, if any.  Returns -1 if no
     bytes available.
   */
  int fetchByteIL() ;

private:
  int pendingCount;

};

#endif  /* SFBBYTEBUFFER_H */

