/*                                              -*- mode:C++; fill-column:100 -*-
  SFBReactor.cpp - 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
*/

#include "SFBReactor.h"
#include "SFBPacket.h"         /* For packetLength and packetFlags */
#include "SFBReflex.h"           /* For TEST_REFLEX_FLAGS */ 
#include "SFBAssert.h"
#include "SFBTicker.h"         /* For Ticker */

#ifdef TEST_SFBREACTOR

/* Compilation for testing, e.g.:
g++ -DTEST_SFBREACTOR -DHOST_POSIX -O0 -g3 -Wall -pedantic -Werror -Wundef    -I../../../src/include  -DHOST_MODE -MMD -MP -MF"../../../targets/host/build/SFBAlarm.d" -MT"../../../targets/host/build/SFBAlarm.d" -o"./sfbreactortest" SFBReactor.cpp SFBPacket.cpp SFBTicker.cpp SFBSerial.cpp SFBPrint.cpp SFBPrintf.cpp SFBCodeFlow.cpp SFBChecksum.c SFBBootBlock.cpp SFBRandom.cpp SFBByteBuffer.cpp random.c SFBMacros.cpp SFBPins.cpp SFBFrame.cpp SFBMemory.cpp SFBHeap.cpp ../host/SFBHWSerialPortable.cpp ../host/SerialShims.c ../host/SFBHWMiscHost.cpp ../host/SFBHWPinsHost.cpp ../host/SFBWiring.cpp ../host/SFBEepromRAMImpl.cpp SFBEeprom.cpp SFBAlarm.cpp SFBReflex.cpp;./sfbreactortest
 */

#include <string.h> /* for strlen */
#include <stdio.h> /* for fprintf */
#include <stdlib.h> /* for exit */
/*
void _apiError_(u32 code, const char * file, int lineno) { exit(code); }

void _dieNow_(const char * m1, const char * m2, const char * m3, const char * m4, u32 blinkCode) {
  exit(blinkCode);
}
*/
void die(const char * file, int line, const char * msg) {
  fprintf(stderr,"%s:%d: %s\n",file, line, msg);
  exit(100);
}
#endif
  
SFBReactor Body IN_BODYRAM_BSS = SFBReactor();

SFBBrainReflex Brain IN_BODYRAM_BSS = SFBBrainReflex();

SFBSpineReflex Spine IN_BODYRAM_BSS = SFBSpineReflex();

SFBReactor::SFBReactor() {
  reset(); 
}

int SFBReactor::nextPartition(int partition) {
  switch (partition) {
  case NORTH:  // From the specific faces..
  case SOUTH:
  case EAST:
  case WEST:
    return SPINE;  // ..the next stop is the spine
  case SPINE:
    return BRAIN;   // And from there to the brain
  case WMEM:
    return BRAIN;   // From memory-specific goes to brain
  case BRAIN:       
    return END_PARTITION;     // After the brain, we're done
  default:
    API_BUG(E_API_ARGUMENT);
  }
  return 0; /* NOT REACHED */
}

int SFBReactor::positionInPartition(const u8 partition, const u8 type) {
  API_ASSERT_MAX(partition,END_PARTITION);
  int count = 2; // Skip empty and default slots
  for (int i = partitionStarts[partition]+2; i < partitionStarts[partition+1]; ++i, ++count) {
    if (type <= entries[i].type) break;
  }
  return count;
}

void SFBReactor::openUp(const u8 inPartition, const u8 atPosition) {
  API_ASSERT_MAX(inPartition, END_PARTITION);
  API_ASSERT_MAX(partitionStarts[END_PARTITION]+1, MAX_REACTORS);
  int newSlot = partitionStarts[inPartition]+atPosition;
  for (int i = partitionStarts[END_PARTITION]; i >= newSlot; --i) // Move up existing slot contents
    entries[i+1] = entries[i];
  for (int i = inPartition+1; i < PARTITION_COUNT; ++i) // Move up affected starts
    ++partitionStarts[i];
}

bool isNone(GRL g) { return g.tableIndex==255 && g.entryIndex==255; }

GRL makeGRL(u8 table, u8 entry) {
  GRL g;
  initGRL(g,table,entry);
  return g;
}

void initGRL(GRL & g, u8 table, u8 entry) {
  g.tableIndex = table;
  g.entryIndex = entry;
}

static const GRL GRLnone = { 255, 255 }; 
static const GRL GRLzero = { 0, 0 }; 

void SFBReactor::addToPartition(const u8 partition, const u8 type, GRL grl, u8 flags) {
  API_ASSERT_FALSE(isNone(grl));
  int index = positionInPartition(partition, type);
  int entryIndex = partitionStarts[partition]+index;
  if (index >= partitionLength(partition) || entries[entryIndex].type != type) {
    openUp(partition,index);
    entries[entryIndex].type = type;
    entries[entryIndex].flags = flags;
  }
  entries[entryIndex].grl = grl;
}

int SFBReactor::faceToFlag(const u32 face) { 
  return TRIGGER_FIRST_PARTITION<<face; 
}

u8 SFBReactor::reflexes(const PacketHandler * table, int length) {
  API_ASSERT(table,E_API_NONZERO);
  return masterTable.addTable(table,length);
}

int SFBReactor::partitionLength(const u8 partition) {
  API_ASSERT_MAX(partition, END_PARTITION);
  return partitionStarts[partition+1]-partitionStarts[partition];
}

bool SFBReactor::reflexExists(const char type, int partitions) {
  return !isNone(findInPartitions(type,partitions));
}

SFBGRLMasterTable::SFBGRLMasterTable() {
  for (int i = 0; i < MAX_GRL_TABLES; ++i) {
    tables[i] = 0;
    lengths[i] = 0;
  }
  inUse = 0;
}

GRL SFBGRLMasterTable::find(PacketHandler ph) {
  for (int i = 0; i < inUse; ++i) {
    for (int j = 0; j < lengths[i]; ++j) {
      if (tables[i][j] == ph) {
        return makeGRL(i,j);
      }
    }
  }
  return GRLnone;
}
PacketHandler SFBGRLMasterTable::find(GRL index) {
  if (index.tableIndex>=inUse)
    return 0;
  if (index.entryIndex >= lengths[index.tableIndex])
    return 0;
  return tables[index.tableIndex][index.entryIndex];
}

void SFBReactor::reflex(const char ch, PacketHandler pfunc, int partitions, bool ifBroken) {
  u8 type = (u8) ch;
  API_ASSERT(type < 'A' || type > 'Z', E_API_BAD_REFLEX);  /* Cannot be A-Z for sketch level */
  implReflex(type,pfunc,partitions,ifBroken);
}

void SFBReactor::reflexReflex(const char ch, PacketHandler pfunc, int partitions, bool ifBroken) {
  u8 type = (u8) ch;
  API_ASSERT(type >= 'A' && type <= 'Z', E_API_BAD_REFLEX);  /* Must be A-Z for library level */
  API_ASSERT(!reflexExists(type,partitions), E_API_REFLEX_REDEF);    /* Library must not try to redefine */
  implReflex(type,pfunc,partitions,ifBroken);
}

void SFBReactor::implReflex(const char ch, PacketHandler pfunc, int partitions, bool ifBroken) {
  u8 type = (u8) ch;
  GRL grl = findOrAdd(pfunc);
  for (int p = NORTH; p < END_PARTITION; ++p) {
    if (partitions&(TRIGGER_FIRST_PARTITION<<p)) { 
      addToPartition(p, type, grl, ifBroken?TRIGGER_IF_BROKEN:0);
      continue;
    }
  }
}
  
void SFBReactor::setSpecial(const u8 offset, u8 flags, GRL grl, int partitions) { 
  for (int p = NORTH; p < END_PARTITION; ++p) {
    if (partitions&(TRIGGER_FIRST_PARTITION<<p)) { 
      entries[partitionStarts[p]+offset].type = 0;
      entries[partitionStarts[p]+offset].flags = flags;
      entries[partitionStarts[p]+offset].grl = grl;
    }
  }
}


void SFBReactor::suppress(const char ch, int partitions) { 
  u8 type = (u8) ch;
  for (int p = NORTH; p < END_PARTITION; ++p) {
    if (partitions&(TRIGGER_FIRST_PARTITION<<p)) { 
      for (int j = partitionLength(p); --j >= 2; ) { // Yes..
        if (entries[partitionStarts[p]+j].type == type) { // Find it?
          entries[partitionStarts[p]+j].flags |= TRIGGER_SUPPRESSED;
        }
      }
    }
  }
}

void SFBReactor::allow(const char ch, int partitions) { 
  u8 type = (u8) ch;
  for (int p = NORTH; p < END_PARTITION; ++p) {
    if (partitions&(TRIGGER_FIRST_PARTITION<<p)) { 
      for (int j = partitionLength(p); --j >= 2; ) { // Yes..
        if (entries[partitionStarts[p]+j].type == type) { // Find it?
          entries[partitionStarts[p]+j].flags &= ~TRIGGER_SUPPRESSED;
        }
      }
    }
  }
}

GRL SFBReactor::addToCasual(PacketHandler ph) {
  API_ASSERT_MAX_CODE(casualTableInUse,MAX_CASUAL_REFLEXES-1,E_API_REFLEX_FULL);
  int use = casualTableInUse++;
  casualTable[use] = ph;
  masterTable.addTable(casualTable,casualTableInUse);
  return makeGRL(0,use);
}

void SFBReactor::reset() {
  for (int i = NORTH; i < PARTITION_COUNT; ++i) {
    partitionStarts[i] = i*2;
    entries[partitionStarts[i]].type = 0;             // No empty handler
    entries[partitionStarts[i]].flags = 0;
    entries[partitionStarts[i]].grl = GRLnone;
    entries[partitionStarts[i]+1].type = 0;           // No default handler
    entries[partitionStarts[i]+1].flags = 0;
    entries[partitionStarts[i]+1].grl = GRLnone;
  }

  casualTableInUse = 0;
  for (int i = 0; i < MAX_CASUAL_REFLEXES; ++i) {
    casualTable[i] = 0;
  }
  masterTable.addTable(casualTable,0);

  triggerUpFlags = 0;
  topTrigger = -1;              // No trigger in use now
}
  
void reflex(const char type, PacketHandler ph,int flags) {
  Body.reflex(type,ph,flags);
}

void SFBReactor::otherwise(PacketHandler pfunc, int partitions, bool allowBroken) {
  GRL grl = findOrAdd(pfunc);
  setSpecial(1,allowBroken?TRIGGER_IF_BROKEN:0,grl,partitions);
}

void SFBReactor::empty(PacketHandler pfunc, int partitions, bool allowBroken) {
  GRL grl = findOrAdd(pfunc);
  setSpecial(0,(allowBroken?TRIGGER_IF_BROKEN:0)|TRIGGER_EMPTY,grl,partitions);
}

void SFBReactor::triggerUp() {
  API_ASSERT(topTrigger >= 0,E_API_BAD_TRIGGER);
  triggerUpFlags |= 1<<topTrigger;
}

bool SFBReactor::inHandler() {
  return topTrigger >= 0;
}

u8 SFBReactor::source() {
  API_ASSERT(topTrigger >= 0,E_API_BAD_TRIGGER);
  return triggerSources[topTrigger];
}

extern void leds_on();

SFBDispatchEntry::SFBDispatchEntry() :
  type(0),
  flags(0)
{
  initGRL(grl);
}

bool SFBReactor::canTrigger() {
  return topTrigger < (s8) (MAX_NESTED_TRIGGERS-1);
}

#include "SFBPrintf.h"
#include "SFBWiring.h"
#include "SFBSerial.h"

void SFBReactor::trigger(u8 * packet, u8 face) {

  API_ASSERT_VALID_PACKET(packet);

  u8 type;
  u8 needFlags;
  /* If empty packet, dispatch if there's an empty handler */
  if (packetLength(packet)==0) {
    type = 0;
    needFlags = TRIGGER_EMPTY;
  } else {
    type = packet[0];
    needFlags = 0;
  }

  if (packetFlags(packet)&PK_BROKEN) {
    if (face < FACE_COUNT)
      Faces[face].incrBrokenPacketCount();
    needFlags |= TRIGGER_IF_BROKEN;
    API_ASSERT(!TEST_REFLEX_FLAGS(RF_DIE_PACKET_ERROR),E_API_PACKET_ERROR);
  }
  /* Now start scanning from the periphery inwards */
  for (int partition = face; partition != END_PARTITION; partition = nextPartition(partition)) {
    API_ASSERT_NOT_EQUAL(partition,END_PARTITION);
    if (needFlags&TRIGGER_EMPTY) {
      SFBDispatchEntry entry = entries[partitionStarts[partition]];
      GRL empty = entry.grl;
      if (!isNone(empty)) { /* We have an empty handler */
        if (!(needFlags&TRIGGER_IF_BROKEN) || (entry.flags&TRIGGER_IF_BROKEN)) { // If the packets okay, or they want broken ones..
          PacketHandler handler = masterTable.find(empty);
          API_ASSERT(handler,E_BUG_INCONSISTENT_STATE);
          bool ret = fireTrigger(packet,handler,face);
          if (ret) 
            continue;   /* Handled at this level, but wants to triggerUp */
          else
            break;
        }
      }
    } else {                    // Not an empty packet.
      
      int start = partitionStarts[partition]+2; /* Skipping empty and default */
      int end = partitionStarts[partition+1];
      bool more = false;
      bool handled = false;
      for (int i = start; i < end; ++i) { // XXX TODO Where's the fancy binary search, you wuss!
        SFBDispatchEntry entry = entries[i];
        if (entry.type == type &&
            !(entry.flags&TRIGGER_SUPPRESSED) && 
            (!(needFlags&TRIGGER_IF_BROKEN) || (entry.flags&TRIGGER_IF_BROKEN))) {
          GRL grl = entry.grl;
          API_ASSERT_FALSE(isNone(grl));
          PacketHandler handler = masterTable.find(grl);
          API_ASSERT_NONNULL(handler);
          more = fireTrigger(packet,handler,face);  
          handled = true;
          break;
        }
      }
      if (handled && !more)
        break;
    }
    // Here we're either unhandled or the handler said keep going -- so check for default
    SFBDispatchEntry defaultEntry = entries[partitionStarts[partition]+1];    
    if (!isNone(defaultEntry.grl) && (!(needFlags&TRIGGER_IF_BROKEN) || (defaultEntry.flags&TRIGGER_IF_BROKEN))) {
      PacketHandler defaultHandler = masterTable.find(defaultEntry.grl);
      API_ASSERT(defaultHandler,E_BUG_INCONSISTENT_STATE);
      API_ASSERT_NONNULL(defaultHandler);
      bool more = fireTrigger(packet,defaultHandler,face); 
      if (!more)
        break;
    }
  }

  return;
}

bool SFBReactor::fireTrigger(u8 * packet, PacketHandler ph, u8 face) {
  API_ASSERT(ph!=0,E_BUG_INCONSISTENT_STATE); // We're a private method; this really shouldn't happen..

  if (!canTrigger()) // Can't trigger that deep, sorry; you should have checked.
    return false; 

  ++topTrigger;
  triggerSources[topTrigger] = face;
  
  packetReread(packet);         // Start him from the top

  Ticker.pushTime(TICKER_PACKET_HANDLER);
  ph(packet);
  Ticker.popTime();

  bool ret = triggerUpFlags&(1<<topTrigger);
  triggerUpFlags &= ~(1<<topTrigger);

  --topTrigger;

  return ret;
}

u8 SFBGRLMasterTable::addTable(const PacketHandler * table, int length) {
  API_ASSERT_MAX(length,256);

  for (int i = 0; i < inUse; ++i)
    if (tables[i] == table) {
      API_ASSERT_LESS_EQUAL(lengths[i],length);  // Can't shrink a declared table
      lengths[i] = length;    // But can grow it
      return i;               // Done
    }

  API_ASSERT_MAX(inUse,MAX_GRL_TABLES);
  tables[inUse] = table;
    
  for (int count = 0; count < length; ++count) 
    API_ASSERT(table[count]!=0,E_BUG_INCONSISTENT_STATE);
  lengths[inUse] = (u8) length;
  return inUse++;
}

GRL SFBReactor::findOrAdd(PacketHandler ph) {
  GRL ret = masterTable.find(ph);
  if (isNone(ret)) 
    ret = addToCasual(ph);
  return ret;
}


GRL SFBReactor::findInPartitions(const u8 type, int flags) {
  for (int i = 0; i < END_PARTITION; ++i) {
    if (flags&(TRIGGER_FIRST_PARTITION<<i)) { // Search this one?
      for (int j = partitionLength(i); --j >= 2; ) { // Yes.. (2 to exclude empty/default handlers)
        if (entries[partitionStarts[i]+j].type == type) { // Find it?
          return makeGRL(i,j);    // Yes.
        }
      }
    }
  }
  return GRLnone;         // No.
}



#ifdef TEST_SFBREACTOR
const char * progname = "sfbreactortest";
bool verbose = true;
void watchdogFeed() { }
/* Compilation for testing, e.g.:
g++ -DTEST_SFBREACTOR -O0 -g3 -Wall -pedantic -Werror -Wundef    -I../../../src/include  -DHOST_MODE -MMD -MP -MF"../../../targets/host/build/SFBReactor.d" -MT"../../../targets/host/build/SFBReactor.d" -o"./sfbReactortest" "SFBReactor.cpp" ../../../targets/host/build/SFBPacketIO.o;valgrind ./sfbReactortest
 */
#include <stdio.h>
#include <stdlib.h>
volatile u32 timer_1msec_counter;
int die(const char * message) {
  printf("%s\n",message);
  exit(1);
}
#define ASSERT(condition) ((condition)?0:die(#condition))


SFBReactor * theReactor;
#define makeHandler(typeVal,redis,argVal)                \
int var_##typeVal##_##redis##_##argVal = 0;                      \
void handler_##typeVal##_##redis##_##argVal(u8 * packet) {       \
  ASSERT(packetLength(packet) > 0);                              \
  ASSERT(packet[0] == #typeVal[0]);                              \
  ++var_##typeVal##_##redis##_##argVal;                          \
  if (redis) theReactor->triggerUp();                            \
}
void runRandoms();
u8 packetBuff[MAX_PACKET_LENGTH];
u8 * makePacket(const char * str, int flags = 0) {
  packetBuff[0] = 0;
  packetBuff[1] = flags;
  packetBuff[2] = 0;
  packetBuff[3] = strlen(str);
  int i;
  for (i = 4; *str; packetBuff[i++] = *str++) ;
  packetBuff[i] = 0;
  return &packetBuff[4];
}

makeHandler(a,0,1)
makeHandler(a,0,2)
void runBasic1() {
  SFBReactor dis;
  theReactor = &dis;
  dis.reflex('a',&handler_a_0_1);
  dis.trigger(makePacket("ack"),NORTH);
  ASSERT(var_a_0_1==1);
  ASSERT(var_a_0_2==0);
}

makeHandler(x,0,1)
makeHandler(x,1,2)
makeHandler(x,0,3)
makeHandler(x,0,4)
makeHandler(y,0,1)
makeHandler(y,1,2)
makeHandler(y,1,3)
void runBasic2() {
  SFBReactor dis;
  theReactor = &dis;
  dis.reflex('x',&handler_x_0_1, SFBReactor::TRIGGER_NORTH); // x on north eats
  dis.reflex('x',&handler_x_1_2, SFBReactor::TRIGGER_SOUTH); // but x on sourth continues
  dis.reflex('x',&handler_x_0_3, SFBReactor::TRIGGER_SPINE); // x on spine eats
  dis.reflex('x',&handler_x_0_4, SFBReactor::TRIGGER_BRAIN); // x on brain eats
  dis.trigger(makePacket("xray"),NORTH); // eaten by north
  ASSERT(var_x_0_1==1);
  ASSERT(var_x_1_2==0);
  ASSERT(var_x_0_3==0);
  ASSERT(var_x_0_4==0);
  dis.trigger(makePacket("xray"),SOUTH); // continued by south
  ASSERT(var_x_0_1==1); // north doesn't see it
  ASSERT(var_x_1_2==1); // south does
  ASSERT(var_x_0_3==1); // so does spine, who eats it
  ASSERT(var_x_0_4==0); // so brain doesn't
  dis.trigger(makePacket("xray"),WEST); // no handler in west
  ASSERT(var_x_0_1==1); // north doesn't see it
  ASSERT(var_x_1_2==1); // south doesn't see it
  ASSERT(var_x_0_3==2); // spine does, who eats it
  ASSERT(var_x_0_4==0); // so brain doesn't
  dis.trigger(makePacket("xray"),SPINE); // we can cheat and twitch a phantom limb
  ASSERT(var_x_0_1==1); // north doesn't see it
  ASSERT(var_x_1_2==1); // south doesn't see it
  ASSERT(var_x_0_3==3); // spine does, who eats it
  ASSERT(var_x_0_4==0); // so brain doesn't
  dis.trigger(makePacket("xray"),WMEM); // we can also cheat and create a phantom memory
  ASSERT(var_x_0_1==1); // north doesn't see it
  ASSERT(var_x_1_2==1); // south doesn't see it
  ASSERT(var_x_0_3==3); // spine doesn't see it, because it's not spatial
  ASSERT(var_x_0_4==1); // so brain does
  dis.trigger(makePacket("xray"),BRAIN); // and we can do the matrix thing..
  ASSERT(var_x_0_1==1); // north doesn't see it
  ASSERT(var_x_1_2==1); // south doesn't see it
  ASSERT(var_x_0_3==3); // spine doesn't see it, because it's not spatial
  ASSERT(var_x_0_4==2); // only the brain sees it
  dis.trigger(makePacket("yrax"),NORTH); // some other random packet type
  ASSERT(var_x_0_1==1); // north doesn't see it
  ASSERT(var_x_1_2==1); // south doesn't see it
  ASSERT(var_x_0_3==3); // spine doesn't see it
  ASSERT(var_x_0_4==2); // brain doesn't see it
}

int empty_packets1 = 0;
int broken_packets1 = 0;
int empty_and_broken_packets1 = 0;
void emptyHandler1(u8 * packet) {
  ASSERT(packetLength(packet)==0);
  ++empty_packets1;
  if (packetFlags(packet)&PK_BROKEN) {
    ++empty_and_broken_packets1;
  }
}
int empty_packets2 = 0;
int broken_packets2 = 0;
int empty_and_broken_packets2 = 0;
void emptyHandler2(u8 * packet) {
  ASSERT(packetLength(packet)==0);
  ++empty_packets2;
  if (packetFlags(packet)&PK_BROKEN) {
    ++empty_and_broken_packets2;
  }
  theReactor->triggerUp();
}
void neverHandler(u8 * packet) {
  ASSERT(0);
}
int xhits = 0;
int brokenxhits = 0;
void brokenXHandler(u8 * packet) {
  ASSERT(packetLength(packet)>0);
  ASSERT(packet[0]=='x');
  ++xhits;
  if (packetFlags(packet)&PK_BROKEN) 
    ++brokenxhits;
}
int defaultEmpty = 0;
int defaultBroken = 0;
int defaultX = 0;
int defaultY = 0;
int defaultZero = 0;
int defaultOther = 0;
void defaultHandler(u8 * packet) {
  if (packetFlags(packet)&PK_BROKEN)
    ++defaultBroken;
  if (packetLength(packet)==0) 
    ++defaultEmpty;
  else {
    switch (packet[0]) {
    case 0: ++defaultZero; break;
    case 'x': ++defaultX; break;
    case 'y': ++defaultY; break;
    default: 
      ++defaultOther; 
      theReactor->triggerUp(); 
      break;
    }
  }
}

void runExotic() {
  SFBReactor dis;
  theReactor = &dis;
  dis.empty(emptyHandler1,SFBReactor::TRIGGER_SOUTH|SFBReactor::TRIGGER_WEST|SFBReactor::TRIGGER_MEMORY);
  dis.empty(emptyHandler1,SFBReactor::TRIGGER_EAST, true);

  dis.reflex('x',neverHandler);  // Implicitly on SPINE

  dis.trigger(makePacket(""),NORTH); 
  ASSERT(empty_packets1==0);
  ASSERT(broken_packets1==0);
  ASSERT(empty_and_broken_packets1==0);
  ASSERT(empty_packets2==0);
  ASSERT(broken_packets2==0);
  ASSERT(empty_and_broken_packets2==0);

  dis.trigger(makePacket(""),WEST); 
  ASSERT(empty_packets1==1);
  ASSERT(broken_packets1==0);
  ASSERT(empty_and_broken_packets1==0);
  ASSERT(empty_packets2==0);
  ASSERT(broken_packets2==0);
  ASSERT(empty_and_broken_packets2==0);

  dis.trigger(makePacket("",PK_BAD_ESCAPE),WEST);   // West should _NOT_ trigger
  ASSERT(empty_packets1==1);
  ASSERT(broken_packets1==0);
  ASSERT(empty_and_broken_packets1==0);
  ASSERT(empty_packets2==0);
  ASSERT(broken_packets2==0);
  ASSERT(empty_and_broken_packets2==0);

  dis.trigger(makePacket("",PK_BAD_ESCAPE),EAST);   // But East should trigger
  ASSERT(empty_packets1==2);
  ASSERT(broken_packets1==0);
  ASSERT(empty_and_broken_packets1==1);
  ASSERT(empty_packets2==0);
  ASSERT(broken_packets2==0);
  ASSERT(empty_and_broken_packets2==0);

  dis.trigger(makePacket("x",PK_BUFFER),NORTH);   // No broken packets should trigger
  dis.trigger(makePacket("x",PK_OVERRUN),SOUTH);   // No broken packets should trigger
  dis.trigger(makePacket("x",PK_PARITY),EAST);   // No broken packets should trigger
  dis.trigger(makePacket("x",PK_FRAMING),WEST);   // No broken packets should trigger
  dis.trigger(makePacket("x",PK_BREAK),SPINE);   // No broken packets should trigger

  dis.reflex('x',brokenXHandler,SFBReactor::TRIGGER_WEST,true);
  ASSERT(xhits==0);
  ASSERT(brokenxhits==0);
  dis.trigger(makePacket("x",PK_BUFFER),NORTH); 
  ASSERT(xhits==0);
  ASSERT(brokenxhits==0);
  dis.trigger(makePacket("y",PK_BUFFER),NORTH); 
  ASSERT(xhits==0);
  ASSERT(brokenxhits==0);
  dis.trigger(makePacket("x",PK_BUFFER),WEST);  
  ASSERT(xhits==1);
  ASSERT(brokenxhits==1);
  dis.trigger(makePacket("x"),WEST);  
  ASSERT(xhits==2);
  ASSERT(brokenxhits==1);
  dis.trigger(makePacket("X"),WEST);  // Note capital X
  ASSERT(xhits==2);
  ASSERT(brokenxhits==1);

  dis.otherwise(defaultHandler, SFBReactor::TRIGGER_SPINE); // SPINE, no broken
  ASSERT(defaultEmpty==0);
  ASSERT(defaultBroken==0);
  ASSERT(defaultX==0);
  ASSERT(defaultY==0);
  ASSERT(defaultZero==0);
  ASSERT(defaultOther==0);
  dis.otherwise(defaultHandler,SFBReactor::TRIGGER_MEMORY, true);
  ASSERT(defaultEmpty==0);
  ASSERT(defaultBroken==0);
  ASSERT(defaultX==0);
  ASSERT(defaultY==0);
  ASSERT(defaultZero==0);
  ASSERT(defaultOther==0);
  dis.trigger(makePacket("x"),WEST);  // Doesn't reach spine
  ASSERT(xhits==3);
  ASSERT(brokenxhits==1);
  dis.trigger(makePacket("y"),WEST);  // Does reach spine
  ASSERT(xhits==3);
  ASSERT(brokenxhits==1);
  ASSERT(defaultEmpty==0);
  ASSERT(defaultBroken==0);
  ASSERT(defaultX==0);
  ASSERT(defaultY==1);
  ASSERT(defaultZero==0);
  ASSERT(defaultOther==0);
  u8 * zpac = makePacket("a");
  zpac[0] = 0;
  dis.trigger(zpac,WEST);  // Does reach spine
  ASSERT(xhits==3);
  ASSERT(brokenxhits==1);
  ASSERT(defaultEmpty==0);
  ASSERT(defaultBroken==0);
  ASSERT(defaultX==0);
  ASSERT(defaultY==1);
  ASSERT(defaultZero==1);
  ASSERT(defaultOther==0);

  dis.trigger(makePacket("q"),WEST);  // Does reach spine
  ASSERT(defaultEmpty==0);
  ASSERT(defaultBroken==0);
  ASSERT(defaultX==0);
  ASSERT(defaultY==1);
  ASSERT(defaultZero==1);
  ASSERT(defaultOther==1);

}


void runTests() {
  runBasic1();
  runBasic2();
  runExotic();
}

int main() {
  runTests();
  return 0;
}
#endif

