/*                                       -*- 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 no be set or read. */
    TRIGGER_RESERVED4 =  0x10,  /**< Reserved for future use; should no be set or read. */
    TRIGGER_RESERVED5 =  0x20,  /**< Reserved for future use; should no be set or read. */
    TRIGGER_RESERVED6 =  0x40,  /**< Reserved for future use; should no be set or read. */
    TRIGGER_RESERVED7 =  0x80,  /**< Reserved for future use; should no 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) ;

  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 indirectory) 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) ;

  void trigger(u8 * packet, u8 face) ;

  SFBReactor() ;

  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);
};

void reflex(const char type, PacketHandler ph, int flags = SFBReactor::TRIGGER_SPINE);

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_ */

