/*                                             -*- mode:C++; fill-column:100 -*-
  SFBNet.h - Simple 'functional gradient' network routing
  Copyright (C) 2010 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 SFBNet.h Simple 'functional gradient' network routing; see #SFBNet for details.
  \author Daniel Cannon (dccannon@cs.unm.edu); conversion for SFB core by Dave Ackley.
  \date (C) 2010 All rights reserved.
  \lgpl
 */
#ifndef SFBNET_H_
#define SFBNET_H_

#include <stdarg.h>

#include "SFBConstants.h"       /* For FACE_COUNT, etc */
#include "SFBAlarm.h"           /* For SFBAlarmIndexType */ 
#include "SFBPrint.h"           /* For FacePrinter, etc */ 

/**
 * Class supporting 'service gradient routing' via the #Net.  Allows a sketch to offer 'services' to
 * a (region of) connected boards.  Service requests from the network appear as simple reflex
 * triggerings on the service-offering board; replies generated during reflex processing can be
 * automatically reverse-routed to the requesting board.
 *
 * \since 0.9.18
 */
class SFBNet {
public:

  /**
   * Access tie-breaking strategy for equal-length routing decisions.
   *
   * \returns \c true if configured to route to the first-encountered among all equally shortest
   * directions; \c false if picking randomly among all such choices.
   *
   * \see #setShouldTakeFirstTie(bool)
   */
  bool shouldTakeFirstTie() { return flags&TAKE_FIRST_TIE; }

  /**
   * Control tie-breaking strategy for equal-length routing decisions.  Determines whether
   * shortest-direction ties should be resolved in favor of the first such direction encountered (\a
   * on set to \c true) or by a random choice (\a on set to \c false).
   *
   * \see #shouldTakeFirstTie()
   */
  void setShouldTakeFirstTie(bool on) { 
    if (on)
      flags |= TAKE_FIRST_TIE;
    else
      flags &= ~TAKE_FIRST_TIE;
  }

  /** 
   * Initializes the Net, by registering the 'N' reflex and starting the
   * broadcast alarm.
   */
  void begin() ;

  /** 
   * \c true if the most-recent call to #begin() is newer than the most-recent call to #end().
   */
  bool isActive() { return (flags&NET_ACTIVE)!=0; } 

  /**
   * Stops the Net.
   */
  void end() ;

  /** 
   * Specifies that \a service is now locally provided.  Changes nothing if \a service was \e
   * already locally provided.
   *
   * @blinks #E_API_RESOURCE_EXHAUSTED if there's no more room to offer
   * local services
   * @see #MAX_LOCAL_SVCS
   * @see #withhold(u8)
   */
  void offer(u8 service) ;

  /**
   * Specifies that \a service is not (or no longer) locally
   * provided.
   *
   * @returns \c true iff \a service had been being offered before this call.
   * @see #offer(u8)
   */
  bool withhold(u8 service) ;

  /**
   * Is \a service currently being offered locally by this node?
   *
   * @returns \c true iff it is.  
   *
   * @see #isOfferedByNet(u8 service) for a mechanism to
   * determine if \a service is offered \e anywhere in the #Net
   * (not just locally).
   */
  bool isOfferedByNode (u8 service) ;

  /**
   * Is \a service being currently offered anywhere on the grid
   * (as far as we know)?
   *
   * @returns \c true iff it is.  
   *
   * @see #isOfferedByNode(u8 service) for a mechanism to
   * determine if \a service is offered specifically by the
   * current node.
   */
  bool isOfferedByNet(u8 service) ;

  /**
   * Opens a "connection" with a \a service provider. That is, it sets the
   * currently selected service to \a service, such that, when a newline is
   * printed, the current contents of the internal packet buffer are
   * shipped out to a provider of \a service.
   */
  void openServiceStream(u8 service) ;

  /**
   * Prints formatted output to the currently open service.
   * @see #openServiceStream(u8)
   */
  void printf ( const char * fmt, ...) ;

  /**
   * Prints formatted output to the currently open service.
   * @see #openServiceStream(u8)
   */
  void vprintf(const char * fmt, va_list & ap) ;

  /**
   * Prints formatted output to a provider of \a service, if any.
   */
  void printf(u8 service, const char * fmt, ...) ;

  /**
   * Sets the broadcast period to approximately \a millis.  Actual range of values is about 0.25
   * seconds to about one minute, at a granularity of about a quarter second; \a millis values
   * outside that range will be clipped to the nearest value.  Default value is about 5 seconds.  A
   * smaller period responds faster to node connectivity changes, but increases overall network
   * traffic.
   */
  void setBroadcastPeriod(u32 millis) ;

  /**
   * Sets the TTL ('Time To Live') horizon for all Net packets originating at this node to \a
   * distance.  Default value 6.  Increasing the TTL increases the effective 'radius' of the #Net,
   * but greatly increases the amount of traffic sent during broadcasts.
   *
   * \blinks #E_API_MAX_RANGE if \a distance is not less than half of #MAX_RETURN_ROUTE_LENGTH.
   */
  void setHorizon(u32 distance) ;

  /**
   * Gets the TTL ('Time To Live') horizon used for all Net packets
   * originating at this node.  
   *
   * \see #setHorizon(u32)
   */
  u32 getHorizon() { return defaultTTL; }

  /**
   * Get the virtual face used for printing to the internal packet buffer.
   */
  u32 getServiceStreamFace() {
    return net_printer_face;
  }

  /**
   * Returns \c true if we have recently heard SFBNet activity from \c face.
   */
  bool neighborActive(u8 face) ;

  /**
   * Scaling limits for SFBNet.  Increasing these values increases the SFBNet RAM footprint.
   *
   * \see #setHorizon(u32) and #setBroadcastPeriod(u32) for runtime-settable scaling parameters.
   */
  enum Sizes {
    MAX_TOTAL_SVCS=64,  /**< Maximum services a given node can route to.  RAM increases at 9*this bytes. */
    MAX_LOCAL_SVCS=32,  /**< Maximum services a given node can provide. RAM increases at this bytes. */
    MAX_RETURN_ROUTE_LENGTH=100 /**< Maximum hops allowed in a return route. RAM increases at this bytes. */
  };

  /**
   * Log internal state to face for debugging.
   */
  void dump(u8 face, const u8 * prefix = 0) ;

private:

/**
 * If this flag is set via SFBNet::setFlags, then when a routing
 * decision between equally good directions is needed, a fixed
 * but arbitrary choice will be made.  When this flag is clear
 * (the default), ties will be broken by randomization with all
 * tied choices equally likely.
 */
  enum Flags {
    TAKE_FIRST_TIE=      0x01, /**< If set, don't randomize over shortest directions */
    RESPONSE_SENT_HEADER=0x02, /**< If set, the "R" header has been printed already */
    NET_ACTIVE=          0x04, /**< If set, currently begin()ed */ 
    FLAG_RSRV3=          0x08,
    FLAG_RSRV4=          0x10,
    FLAG_RSRV5=          0x20,
    FLAG_RSRV6=          0x40,
    FLAG_RSRV7=          0x80
  };

  /**
   * Ticks at about 4Hz, turns over after about a minute.
   */
  u8 ticks() { return MsToTicks(millis()); }

  u32 ticksToMs(u8 ticks) { return ticks<<8; }

  u32 MsToTicks32(u32 ms) { return ms>>8; }

  u8 MsToTicks(u32 ms) { return (u8) MsToTicks32(ms); }


  /**
   * Returns \c true if we have heard SFBNet activity from \c face in the last couple seconds.
   */
  bool neighborActive_(u8 face) {
    u8 now = ticks();
    bool ret = IS_LATER8(neighborActiveTicks[face]+MsToTicks(2048),now);
    if (!ret) neighborActiveTicks[face] = now-10; // Pull along inactive faces to avoid wrapping
    return ret;
  }

  static const FacePrinter net_printer;
  static const FacePrinter response_printer;

  void functionPrint(u8 face, u8 byte) ;
  void functionPrintln(u8 face) ;

  void printResponseHeader() ;
  void printResponseHeaderIfNeeded() { if ((flags&RESPONSE_SENT_HEADER)==0) printResponseHeader(); }
  void responsePrint(u8 face, u8 byte) ;
  void responsePrintln (u8 face) ;

  static void NetBcastAlarm(u32) ;
  static void NetPacketHandler(u8 *) ;

  static void NetFunctionPrint(u8 face, u8 byte ) ;
  static void NetFunctionPrintln( u8 face) ;
  static void NetResponsePrint(u8 face, u8 byte) ;
  static void NetResponsePrintln(u8 face) ;

  /**
   * The broadcast alarm.
   */
  void broadcastAlarm(u32) ;

  /**
   * Packet handler for n-packets.
   */
  void packetHandler(u8 *) ;

  struct Entry {
    u8 func;                    /* Function. */
    u8 last_update[FACE_COUNT]; /* Bcast ID of last update. */
    u8 hops[FACE_COUNT];        /* Number of hops to provider. */
  };

  /* Pointer to the start of the temp packet in packet_buffer. */
  u8 * net_packet;

  Entry routes[MAX_TOTAL_SVCS];

  SFBAlarmIndexType alarmNumber; /* My alarm number */ 

  u8 last_route;

  u8 offered[MAX_LOCAL_SVCS];
  u8 last_offered;

  u8 bcastPeriodTicks;
  u8 ticksInGeneration;

  u8 defaultTTL;

  u8 neighborActiveTicks[FACE_COUNT];
  u8 bcastGeneration;

  u8 flags;

  /* Temporary packet buffer. */
  u8 packet_buffer[PACKET_PRINTER_PACKET_BUFFER_SIZE];

  /* Currently "open" function. */
  u8 open_function;

  u8 virtual_face;

  u8 packet_printer_face;

  /* Face for the virtual printer for the SFBNet. Dispatches to
   * u8_packet_printer_face. */
  u8 net_printer_face;

  /* The route back to the invoker. */
  u8 response_route[MAX_RETURN_ROUTE_LENGTH];

  /* The length of the route back to the invoker. */
  s8 response_route_len;

  /* The first face in the route back to the invoker. */
  u8 response_src;

  /**
   * Ship the current contents of the internal packet buffer
   * (whether it's currently newline-terminated or not.)
   */
  void ship() ;

  void handleBroadcast(u8 *) ;
  void handleInvoke(u8 *) ;

  Entry * lookup(u8 func, bool insert = false) ;

  u8 pickDirection(u8 func, u8 origin) ;

  u8 pickRandomFace(u8 func) ;

  void trigger(u8 * packet, u8 * return_route, u8 hops) ;

};

/**
 * The global SFBNet instance.
 */
extern SFBNet Net;

#endif /* SFBNET_H_ */
