/*                                       -*- mode:C++; fill-column: 100 -*-
  SFBReactor.h - Register packet handlers and dispatch 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
*/

/**
  \file SFBReactor.h Register packet handlers and dispatch packets
  \author David H. Ackley.  
  \date (C) 2009 All rights reserved.
  \lgpl
 */

#ifndef SFBREACTOR_H_
#define SFBREACTOR_H_

#include "SFBTypes.h"
#include "SFBAssert.h"

typedef void (*PacketHandler)(u8 * packet) ;

typedef void (*DispatchHandler)(u8 * packet, u8 face);

/**
 * A GRL is a "General Reflex Locator".
 *
 * Conceptually, a GRL is just a 16 bit index into a set of function pointers (specificially,
 * PacketHandler's).  It exists primarily so that a SFBDispatchEntry can unite a PacketHandler with
 * a u8 type code and eight bits of flags, and fit it all in a word.
 *
 * Within the 16 bits of a GRL, a two-level (table, entry) structure different parts of the codebase
 * (sketch, reflex core, external libraries..) each to provide their own arrays of PacketHandler
 * pointers.  Each such array is assigned a table number (when passed to SFBReactor::reflexes), and
 * then the associated entry numbers are its array indices.  Such arrays will typically be declared
 * "const", so they are stored in flash memory, avoiding the need to waste RAM on the function
 * pointers themselves.
 */
struct GRL {
  u8 tableIndex;
  u8 entryIndex;
};
extern bool isNone(GRL g) ;
extern GRL makeGRL(u8 table, u8 entry) ;
extern void initGRL(GRL & g, u8 table = 0, u8 entry = 0) ;

/**
 * The maximum number of tables of GRLs that can be defined.  Calls to SFBReactor::reflexes draw
 * against this limit.  This value consumes BODYRAM at the rate of 5*MAX_GRL_TABLES.
 */
#define MAX_GRL_TABLES 7

/**
 * The description of all the known arrays-of-PacketHandler's 'reflex tables'.
 */
struct SFBGRLMasterTable {
  const PacketHandler *(tables[MAX_GRL_TABLES]);
  u8 lengths[MAX_GRL_TABLES];
  u8 inUse;

  SFBGRLMasterTable() ;
  u8 addTable(const PacketHandler * table, int length) ;

  GRL find(PacketHandler ph) ;

  PacketHandler find(GRL index) ;
};

#define MAX_REACTORS 200
/**
 * An SFBDispatchEntry describes an SFB reflex.  It combinesa PacketHandler with a u8
 * type code and eight bits of flags, and fit it all in a word.
 */
struct SFBDispatchEntry {
  SFBDispatchEntry() ;
  GRL grl;
  u8 type;
  u8 flags;
};
/**
   The maximum number of packet handlers that can be used in reflexes 'casually' -- i.e., without
   making a packet handler array and calling SFBReactor::reflexes() (which see).
 */
#define MAX_CASUAL_REFLEXES 10 

#define TRIGGER_SET_TYPE s16
#define MAX_NESTED_TRIGGERS (sizeof(TRIGGER_SET_TYPE)*8)

/**
 * The SFBReactor manages the SFB reflex triggering system: It determines how an SFB will react to
 * any given packet type.
 */
class SFBReactor {

  // The partitions
  enum {
    END_PARTITION=BRAIN+1,
    PARTITION_COUNT
  };
  int nextPartition(int partition) ;

  int partitionLength(const u8 partition) ;
  bool inPartition(const u8 partition, const u8 type);
  void openUp(const u8 inPartition, const u8 atPosition);
  void addToPartition(const u8 partition, const u8 type, GRL grl, u8 flags);

  GRL addToCasual(PacketHandler ph) ;

  GRL findOrAdd(PacketHandler ph) ;

public:

  enum { 
    TRIGGER_IF_BROKEN  = 0x01,  /**< This handler wants packets even with errors. */
    TRIGGER_EMPTY =      0x02,  /**< This handler wants only empty packets. */
    TRIGGER_SUPPRESSED = 0x04,  /**< This reflex is currently suppressed. */
    TRIGGER_RESERVED3 =  0x08,  /**< Reserved for future use; should not be set or read. */
    TRIGGER_RESERVED4 =  0x10,  /**< Reserved for future use; should not be set or read. */
    TRIGGER_RESERVED5 =  0x20,  /**< Reserved for future use; should not be set or read. */
    TRIGGER_RESERVED6 =  0x40,  /**< Reserved for future use; should not be set or read. */
    TRIGGER_RESERVED7 =  0x80,  /**< Reserved for future use; should not be set or read. */

    // The rest of these triggers are not stored in the flags: Instead
    // they determine which partition(s) the reflex is in.
    TRIGGER_FIRST_PARTITION = 0x100,
    TRIGGER_NORTH =  TRIGGER_FIRST_PARTITION<<NORTH, /**< Trigger if packet came from NORTH */
    TRIGGER_SOUTH =  TRIGGER_FIRST_PARTITION<<SOUTH, /**< Trigger if packet came from SOUTH */
    TRIGGER_EAST =   TRIGGER_FIRST_PARTITION<<EAST,  /**< Trigger if packet came from EAST */
    TRIGGER_WEST =   TRIGGER_FIRST_PARTITION<<WEST,  /**< Trigger if packet came from WEST */
    TRIGGER_SPINE =  TRIGGER_FIRST_PARTITION<<SPINE, /**< Trigger if packet reaches the Spine (from any face) */
    TRIGGER_MEMORY = TRIGGER_FIRST_PARTITION<<WMEM,  /**< Trigger if packet came from working memory */
    TRIGGER_BRAIN =  TRIGGER_FIRST_PARTITION<<BRAIN, /**< Trigger if packet reaches the Brain (from Spine or memory) */

    TRIGGER_ALL_PARTITIONS =
      TRIGGER_NORTH|TRIGGER_SOUTH|TRIGGER_EAST|TRIGGER_WEST|TRIGGER_SPINE|TRIGGER_MEMORY|TRIGGER_BRAIN
  };

  void reset() ;

  int faceToFlag(const u32 face) ;

  /**
     Make an array of PacketHandler's available for use as reflex handlers.  To conserve base RAM
     usage, the core software allocates only a small table of pointers to packet-handling functions.
     If a sketch has more than #MAX_CASUAL_REFLEXES packet handlers, the sketch is liable to trip
     the #E_API_REFLEX_FULL blink code.  To avoid that, the sketch makes a table of packet handlers,
     and calls this method to tell the core software about it.  After that, the core software will
     refer to that table whenever a reflex is created mentioning one of the packet handlers in that
     table.  

     Sample use: \code
     void handleTypeA(u8 * packet) { ..respond to 'a' packets.. }
     void handleTypeE(u8 * packet) { ..respond to 'e' packets.. }
     void handleTypeI(u8 * packet) { ..respond to 'i' packets.. }
     void handleTypeO(u8 * packet) { ..respond to 'o' packets.. }
     void handleTypeU(u8 * packet) { ..respond to 'u' packets.. }
     void handleTypeY(u8 * packet) { ..respond to 'y' packets.. }

     // Getting to be a lot of handlers -- let's make a handler table for them
     const PacketHandler myHandlers[] = { // Note the 'const' -- important!  Makes the table go into flash
       handleTypeO,   // Note that the order handlers are
       handleTypeY,   // listed in a table is _not_ important
       handleTypeI,
       handleTypeA,
       handleTypeE,
       handleTypeU
     };
  
     void setup() {
       // Tell the core about my table, using the 'array size trick' to compute the length of my
       // table (the number of elements in any array is the array's total size in bytes divided by
       // the size in bytes of one array element, say, element[0]).
       Body.reflexes(myHandlers,sizeof(myHandlers)/sizeof(myHandlers[0]));  

       // Note we still have to create each reflex!
       Body.reflex('a',handleTypeA);
       Body.reflex('e',handleTypeE);
       Body.reflex('i',handleTypeI);
       Body.reflex('o',handleTypeO);
       Body.reflex('u',handleTypeU);
       Body.reflex('y',handleTypeY);
     }

     void loop() { ..whatever.. }
    \endcode

   */
  u8 reflexes(const PacketHandler * table, int length) ;

  /**
     Create a reflex association between a particular character \a type, and a particular
     #PacketHandler function \a pfunc.  

     Optionally, one may specify which \a partition(s) the reflex should be created within; by
     default reflexes are created in the spine (which is searched after the individual faces, but
     before the brain).  Also optionally, one may specify that \a pfunc should be invoked on packets
     even \a ifBroken, where 'broken' means the packet had some kind of error in transmission or
     receipt; by default broken packets are not passed to \a pfunc.

     @param type The byte value to trigger the reflex on.  Can be any of the 230 possible byte value
                 \e excluding the uppercase alphabetics (i.e., anything that is \e NOT 'A'-'Z'),
                 because those are reserved for core software reflexes.

     @param pfunc The 'packet handler' function to invoke when a \a type packet is dispatched (in
                  any of the \a partitions, if supplied).  \a pfunc is a pointer to a function
                  taking a \c u8* argument -- the packet to handle -- and returning \c void.


     @blinks E_API_BAD_REFLEX If \a type is 'A'..'Z'.                  

     @blinks E_API_REFLEX_FULL If enough reflexes have been defined that the 'casual reflex table'
                               has run out of room.  (See #reflexes() for how to fix this problem
                               if it occurs.)

  */
  void reflex(const char type, PacketHandler pfunc, int partitions = TRIGGER_SPINE, bool ifBroken = false) ;

  void reflexReflex(const char type, PacketHandler pfunc, int partitions = TRIGGER_SPINE, bool ifBroken = false) ;

  void implReflex(const char type, PacketHandler pfunc, int partitions = TRIGGER_SPINE, bool ifBroken = false) ;

  bool reflexExists(const char type, int partitions = TRIGGER_ALL_PARTITIONS) ;

  void suppress(const char type, int partitions);  /* No default on partitions, you have to say.. */
  void allow(const char type, int partitions);  /* No default on partitions, you have to say.. */

  /** Add a 'catch-all' handler. */
  void otherwise(PacketHandler pfunc, int partitions = TRIGGER_BRAIN, bool allowBroken = false) ;

  void empty(PacketHandler pfunc, int partitions = TRIGGER_SPINE, bool allowBroken = false) ;

  void triggerUp() ;

  /**
     Are we currently executing inside a packet handler function?  
     
     \return \c true if we are in (or were called directly or indirectly) from a packet handler; \c
             false if not (which, at sketch level, most commonly means in (code called from)
             setup(), loop(), or an alarm handler).
   */
  bool inHandler() ;

  u8 source() ;

  u8 getFlags(const char type) ;

  /**
     Attempt to trigger reflexes on \a packet, as though it had arrived on \a face.  Note that if
     calls on this method are nested within active packet handlers, the triggering may or may not
     occur, depending on the depth of nesting.  If this is a problem, a sketch should check
     #canTrigger() first to determine if another level of reflex nesting is possible.

     Note that the core software performs no locking of data structures nor any enforcement of
     mutual exclusion or critical sections.  If a sketch's packet handlers are not prepared to be
     reentered recursively, then this method should only be called from the \c loop() method, and
     never be called from inside any packet handler.

     If #canTrigger() is \c false, this method invokes no packet handlers.

     \blinks #E_API_INVALID_PACKET if \a is null or invalid
   */
  void trigger(u8 * packet, u8 face) ;

  SFBReactor() ;

  /**
     Check if it is possible to trigger reflexes on a packet at this time.  It is possible to
     trigger reflexes on a packet when already inside a packet handler, but only a limited amount of
     such 'reflex nesting' is allowed.  

     \return \c true if a #trigger() call will actually attempt to find and execute an appropriate
             packet handler; \c false if the max reflex nesting limit has been reached.
   */
  bool canTrigger() ;

private:

  void setSpecial(const u8 offset, u8 flags, GRL grl, int partitions) ;

  bool fireTrigger(u8 * packet, PacketHandler ph, u8 face) ;

  int positionInPartition(const u8 partition, const u8 type) ;

  GRL findInPartitions(const u8 type, int flags) ;


  SFBGRLMasterTable masterTable;
  SFBDispatchEntry entries[MAX_REACTORS];
  PacketHandler casualTable[MAX_CASUAL_REFLEXES];
  u16 partitionStarts[PARTITION_COUNT];
  u8 casualTableInUse;

  u8 triggerSources[MAX_NESTED_TRIGGERS];
  TRIGGER_SET_TYPE triggerUpFlags;
  s8 topTrigger;

  friend void reflex(const char type, PacketHandler ph, int flags);
};

/**
   Equivalent to #Body.reflex(type,ph,flags).
 */
void reflex(const char type, PacketHandler ph, int flags = SFBReactor::TRIGGER_SPINE);

/** 
 *  The 'central locus' for reacting to packets.  
 */
extern SFBReactor Body;

/**
 * A 'convenience object' which provides a means of declaring reflexes that trigger at the "brain"
 * -- i.e., after the individual faces and the Spine.
 *
 * A call like:
 * \code
 *   Brain.reflex('q',myQBrainHandler);
 * \endcode
 *
 * is entirely equivalent to the call:
 * \code
 *   reflex('q',myQBrainHandler, SFBReactor::TRIGGER_BRAIN);
 * \endcode
 */
class SFBBrainReflex {
 public:
  void reflex(const char type, PacketHandler ph) {
    ::reflex(type, ph, SFBReactor::TRIGGER_BRAIN);
  }
};
extern SFBBrainReflex Brain;

/**
 * A 'convenience object' which provides a means of declaring reflexes that trigger at the "spine"
 * -- i.e., after the individual faces, but before the Brain.
 *
 * A call like:
 * \code
 *   Spine.reflex('z',myZSpineHandler);
 * \endcode
 *
 * is entirely equivalent to the call:
 * \code
 *   reflex('z',myZSpineHandler, SFBReactor::TRIGGER_SPINE);
 * \endcode
 */
class SFBSpineReflex {
 public:
  /**
   * A convenience method to specify that packets of the given 'type' should trigger the
   * PacketHandler 'ph' if and when they reach the spine.
   *
   * @param type The type to associate, corresponding to the first byte of a packet that can trigger
   * this reflex.
   *
   * @param ph The handler to call when a 'type' packet reaches the spine.
   *
   * @blinks #E_API_BAD_REFLEX If 'type' is an upper-case alphabetic, since those are reserved for
   * the reflex library.
   */
  void reflex(const char type, PacketHandler ph) {
    ::reflex(type, ph, SFBReactor::TRIGGER_SPINE);
  }
};
extern SFBSpineReflex Spine;

#endif /* SFBREACTOR_H_ */

