/*                                              -*- mode:C++; fill-column:100 -*-
  SFBMemory.h Support for 'Working Memory' - redispatching packets
  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 SFBMemory.h Support for 'Working Memory' - redispatching packets
  \author David H. Ackley.  
  \date (C) 2009 All rights reserved.
  \lgpl
 */
#ifndef SFBMEMORY_H
#define SFBMEMORY_H

#include <stdarg.h>
#include "SFBFrame.h"
#include "SFBHeap.h"
#include "SFBReactor.h"  /* For PacketHandler, ::reflex */
#include "SFBPrint.h"    /* For FacePrinter */ 


#define MAX_PACKETS_IN_MEMORY 32

/**
 * A class for temporarily storing packets, and then retriggering on them at a later time.
 */
class SFBMemory {

public:

  SFBMemory() ;

  /**
   * Define a reflex that is operational only when a packet is triggered from memory.
   */
  void reflex(const char type, PacketHandler ph) {
    ::reflex(type, ph, SFBReactor::TRIGGER_MEMORY);
  }

  /**
   * Reflex-trigger up to \a maxPackets currently remembered packets, so long as #millis() has not
   * yet exceeded \a deadline.  Normally called automatically during #delay()s and between #loop()
   * calls, but may be explicitly invoked in the #loop() method to force faster memory processing.
   */
  bool dispatch(int maxPackets, u32 deadline) ;

  /**
   * Save a copy of \a packet for later redispatch (if memory is available to do so).  Returns -1 if
   * no room was available, else returns a 'slot number' in the range of
   * 0..#MAX_PACKETS_IN_MEMORY-1.  This slot number can be used to forget the copied packet if
   * desired (though this must be used with caution; see #forget(u32) for details.)
   *
   * \returns -1 if no room was available to copy \a packet, else returns index of slot containing
   * copied packet.
   *
   * \blinks #E_API_NULL_PACKET if \a packet is null.
   *
   * \see #forget(u32)
   */
  int remember(const u8 * packet) ;

  int rememberUnread(const u8 * packet,u8 sourceOverride = MAX_FACE_INDEX) ; 

  u8 * makeNewMemory(u32 packetLength) ;

  /**
   * Determine if we are reacting to a memorized packet, rather than a 'real' packet (that has just
   * arrived on a face).  Only valid inside packet handlers.
   *
   * \returns \c true if the current packet handler was triggered by a memory packet; \c false if not.
   */
  bool remembering() {
    return inTrigger;
  }

  /**
   * Specify that the current memory packet should be forgotten after the current packet handler
   * finishes.  Only effective inside a packet handler that is handling a memorized packet.  Note
   * that forgetting memory packets after triggering reflexes is the default behavior, so this
   * method usually doesn't need to be called explicitly.
   *
   * \see #remember()
   */
  void forget() {
    if (inTrigger)
      killIt = true;
  }

  /**
   * Specify that the current memory packet should be remembered after the current packet handler
   * finishes.  Only effective inside a packet handler that is handling a memorized packet.
   * Normally, a memorized packet ia forgotten after it triggers a reflex once; if it is desired to
   * cause an additional trigger sometime after the current packet handler is finished, this method
   * must be called during packet handling.
   *
   * \see #forget()
   */
  void remember() {
    if (inTrigger)
      killIt = false;
  }

  
  /**
   * Specify that the packet in slot \a index should be forgotten.  Returns \c true if a packet was
   * successfully forgotten from slot \a index; returns \c false if slot \a index did not currently
   * contain a packet.
   *
   * This method must be used with caution.  In particular, it is up to the caller to ensure that
   * the \a index forgotten still contains whatever packet the caller actually intends to forget.
   * This will be true so long as no packet dispatching has occurred since the time the \a index was
   * returned from #remember(const u8 *), but will not be true in general: Once dispatching has
   * occurred, the packet in slot \a index may be been dispatched and forgotten, and another packet
   * may have been stored in that same \a index slot.
   *
   * \returns \c if \e some packet was forgotten from slot \a index, and \c false if slot \a index
   * was already empty.
   *
   * \blinks #E_API_MAX_RANGE if \a index is outside the range 0..#MAX_PACKETS_IN_MEMORY-1.
   *
   * \see #remember(const u8 *)
   */
  bool forget(u32 index) ;

  /**
   * Access the packet associated with slot \a index.
   *
   * \returns 0 if there is no packet stored in slot \a index, or a pointer to the packet stored
   * there.
   *
   * \blinks #E_API_MAX_RANGE if \a index is greater than or equal to #MAX_PACKETS_IN_MEMORY.
   */
  u8 * get(const u32 index) {
    API_ASSERT_MAX(index,MAX_PACKETS_IN_MEMORY);
    return packets[index];
  }

  /**
   * Reflex-trigger the packet stored in slot \a index, if any.
   *
   * \returns \c true iff a packet was triggered, \c false if slot \a index was empty
   * 
   * \blinks #E_API_MAX_RANGE if \a index is greater than or equal to #MAX_PACKETS_IN_MEMORY.
   */
  bool fire(const u32 index) ;

  int find(const u8 type) {
    return find((const char *) &type, 1);
  }
  int find(const char * prefix, const int len = -1) ;

  /**
   * Formatting printing to a temporary local packet.  Equivalent to \c facePrint(WMEM,format,...).
   *
   * \returns slot index where the last-just-printed packet was placed. Or returns -1 if more or
   * less than a complete packet was printed, or there were no free slots to store the
   * last-just-printed packet.
   *
   * \since 0.9.18
   */
  int printf(const char * format, ...) ;

  /**
   * Returns #WMEM.
   */
  u8 getFace() { return WMEM; }

private:
  SFBHeap heap;
  u8 * packets[MAX_PACKETS_IN_MEMORY];
  s8 lastIndexPrinted;          // -1 if none or 0..MAX_PACKETS_IN_MEMORY-1
  u8 spinner;
  u8 inUse;
  bool inTrigger;
  bool killIt;

  static const FacePrinter memoryFacePrinter ;
  static void MemoryFacePrint(u8 face, u8 ch) ;
  static void MemoryFacePrintln(u8 face) ;

  void facePrint(u8 face, u8 ch) ;
  void facePrintln(u8 face) ;

  u8 memoryPacketBuffer[PACKET_PRINTER_PACKET_BUFFER_SIZE];
};

/**
 * The predefined #SFBMemory instance.
 */
extern SFBMemory Memory;

#endif  /* SFBMEMORY_H */

