/*                                             -*- mode:C++; fill-column:100 -*-
  SFBQLED.h - Queuing system to blink the built-in LEDs without blocking
  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   SFBQLED.h Queuing system to blink the built-in LEDs without blocking
  \author David H. Ackley.  
  \date (C) 2009 All rights reserved.
  \lgpl
 */

#ifndef SFBQLED_H
#define SFBQLED_H

#include "SFBTypes.h"     /* For u8 */
#include "SFBConstants.h" /* For FACE_COUNT */

/**
   Mode values for the second argument to SFBQLED::whenIdle()
*/
enum SFBQLEDWhenIdleModes {
  QLED_IDLE_OFF,                ///< LED should be off when no events are scheduled on it
  QLED_IDLE_ON,                 ///< LED should be on when no events are scheduled on it
  QLED_IDLE_REFLEX              ///< LED should be returned to reflex control when no events are scheduled on it
};

/**
   A simple queuing system to allow displaying various 'blink patterns' on the SFB built-in LEDs
   without having to block.  The system, if used, consumes one alarm out of the #Alarms pool.

   The traditional easy way to blink an LED is to do it from the #loop() method:
   \code
     void loop() {
       ledOn(BODY_RGB_RED_PIN);
       delay(2000);             // 2seconds of red on
       ledOff(BODY_RGB_RED_PIN);
       delay(1000);             // 1second of red off
     }
  \endcode

   but that style of code doesn't transfer well when you want to blink an LED from inside a packet
   handler, because of the mantra: <b>#delay()ing A Handler Is A Bad Idea</b>.  As long as you are
   inside one packet (or alarm) handler, \e no \e other \e handler \e will \e run \e until \e you \e
   return, and calling #delay() greatly, well, delays, the packet handler's return:

   \code
     // Calling delay() inside a packet handler is a bad idea!
     void showPacketBADWAY(u8 *) {   // While inside one packet handler, NO OTHER packet handler WILL RUN!
       ledOn(BODY_RGB_RED_PIN);
       delay(2000);             // BAD IDEA!  Delays handling any other packet for 2 seconds
       ledOff(BODY_RGB_RED_PIN);
       delay(1000);             // BAD IDEA!  Delays handling any other packet for another 1 second after that
     }
     void setup() {
       Body.reflex('p',showPacketBADWAY);
     }
     void loop() { }
  \endcode

  The #QLED, an object of class #SFBQLED, provides a better solution:

   \code
     // Blinking LEDs from packet handlers without delay(), using QLED
     void showPacket(u8 *) {
       QLED.on(BODY_RGB_RED_PIN,2000);  // Add an 'on' event for RED lasting (about) 2 seconds 
       QLED.off(BODY_RGB_RED_PIN,1000); // Add an 'off' event for RED lasting (about) 1 second
     }
     void setup() {
       QLED.begin();                    // Start up the queue for LEDs!
       Body.reflex('p',showPacket);
     }
     void loop() { }
  \endcode

  <h2>SFBQLED theory of operation</h2>

  We would like to satisfy these incompatible goals:

  -# Schedule events on LEDs individually and asynchronously, so we
    can, say, decide to blink the NORTH_LED_PIN for two seconds, and
    while that's in progress, we can later decide we also want to
    blink SOUTH_LED_PIN for two seconds, and the south blink can
    start without waiting for the north blink to finish.
  -# Schedule events on LEDs jointly and simultaneously, so we can,
    say, schedule both RED and GREEN to be on for a second together
    to make yellow.
  -# Keep the API code reasonably simple and non-threatening.  Don't
    require bit masks or '|'s in API calls, and reuse existing
    constants and symbols as much as possible.

  The SFBQLED design favors goal (1) at the expense of (2) because
  individual and asynchronous can, with some overhead, simulate joint
  and synchronous, while the reverse is more expensive or impossible
  depending on the approach.  To support (1), SFBQLED provides a
  separate event queue for each built-in LED, using (compact) linked
  lists to store all the event queues in a single buffer.

  A single event involves turning on or off a single built-in LED for
  some multiple of 16ms from 16ms to 2032ms; to schedule an event for,
  say two, LEDs simultaneously, the programmer must schedule a copy of
  the event on each LED -- but before that, the programmer should
  first ensure that both LEDs have the same amount of time currently
  scheduled on them, or else there is no guarantee the events will
  start at the same time.  Ensuring two LEDs are 'schedule-matched'
  can be done:

   -# By design, always scheduling equal times on the LEDs, making it
     impossible for them to get out of sync, or
   -# By calling forget() on each, thus canceling any pending events
     on them, or
   -# By calling time() on each to determine their current scheduling
     depth, and then adding an (on or off) event to the LED with the
     shorter schedule to line them up.
   
  The SFBQLED design is aimed most naturally at non-repeating display
  patterns -- something that you want to display once in response to
  an event, and then move on.  But since there is support for
  detecting how full (or empty) the queue is, one iteration of a
  repeating pattern can be scheduled, and then the queue can be polled
  from time to time and reloaded with the same pattern, once there is
  enough room #available().

  \since 0.9.12

 */
class SFBQLED {
  enum {
    QLED_BODY_RGB_RED_PIN,
    QLED_BODY_RGB_GREEN_PIN,
    QLED_BODY_RGB_BLUE_PIN,
    QLED_NORTH_LED_PIN,
    QLED_SOUTH_LED_PIN,
    QLED_EAST_LED_PIN,
    QLED_WEST_LED_PIN,
    QLED_NORTH_SIDE_LED_PIN,
    QLED_SOUTH_SIDE_LED_PIN,
    QLED_EAST_SIDE_LED_PIN,
    QLED_WEST_SIDE_LED_PIN,
    QLED_COUNT,
   
    FLAG = 0xff,
    MAX_TICKS=0x7f,
    MS_PER_TICK_BITS = 4,       // Min granularity is 16ms, max single event len=2032ms
    MS_PER_TICK = 1<<MS_PER_TICK_BITS
  };
  struct qentry {
    u8 onOffTime;
    u8 next;
  };

  qentry * queue;
  u16 idleOurs;                // If bit clear, return qled to reflex when idle
  u16 idleOn;                  // if idleOurs, ledSet(idleOn) when idle
  u8 oldest[QLED_COUNT];
  u8 newest[QLED_COUNT];
  u8 freeList;
  u8 freeCount;
  u8 alarm;
  u8 slots;

  u32 indexFromSFBPin(u32 sfbPin) ;
  u8 sfbPinFromIndex(u32 index) ;
  u32 allocEntryIndex() ;
  void freeEntryIndex(u32 index) ;
  void initEmpty() ;
  void initLists() ;
  void initQueue() ;
  void tick(u32 when) ;

  bool isEmpty(u32 idx) { return oldest[idx]==newest[idx]; }

  void addOp(u32 sfbPin, bool on, u32 ms);

  u32 next(u32 index) { ++index; if (index >= slots) index = 0; return index; }
  bool pop(u32 qled) ;

  void makeIdle(u32 qled) ;

  static void alarmHandler(u32 when, void * arg) ;

 public:
  SFBQLED() ;
  
  /**
     Return the number of events that can currently be added to the SFBQLED before it will be
     completely full.  Maximum return value is #size() when the queue is completely empty; minimum
     return value is 0 with the queue is completely full.
   */
  u32 available() { return freeCount; }

  /**
     Return the total number of events that can fit in the queue.  If SFBQLED was most recently
     started with #begin(), this method will return the default size of SFBQLED, which is 32.  If
     SFBQLED was most recently started with #begin(u16 * buffer, u32 size), this method will return
     the size specified there.
   */
  u32 size() { return slots; }

  /**
     Initialize the SFBQLED for use using the default size of 32 and a private internal buffer for
     event storage.  Inverted by #end(). See also #begin(u16 * buffer, u32 size).
   */
  void begin() ;

  /**
     Initialize the SFBQLED for use, using the given \a buffer and \a size.  \a buffer must be
     non-null and pointing to space big enough for at least \a size \c u16's, for event storage.
     Inverted by #end(). See also #begin().

     \param buffer non-null pointer to array of u16's to use for event storage

     \param size size of \a buffer (number of u16s); a value from 1 to 255 (\e note \e limits!) 

     \blinks E_API_NULL_POINTER if \a buffer is null
     \blinks E_API_NONZERO if \a size is zero
     \blinks E_API_LESS_EQUAL if \a size is greater than 255
     \blinks E_API_NO_MORE_ALARMS if the needed alarm could not be created
   */
  void begin(u16 * buffer, u32 size) ;

  /**
     Inverts the effect of the most recent #begin() or #begin(u16 * buffer, u32 size) call.  In the
     latter case, in particular, \c buffer may be reused for other purposes after calling end().
     Each built-in LED pin returns to its idle mode as specified by #whenIdle() (or to reflex mode
     if #whenIdle() was not called for it).  

     Note that because Alarms once created cannot be freed, the alarm used by SFBQLED will remain
     allocated even if #end() is called.  However, if #begin() is subsequently called again, that
     alarm will be reused; at most one alarm will ever be allocated by SFBQLED.
  */
  void end() ;

  /**
     Cancel any pending events for \a sfbPin.  The pin enters its idle state (see #whenIdle()).

     \param sfbPin the pin number of a built-in LED.

     \blinks E_API_ARGUMENT if \a sfbPin is not the pin number of a built-in LED.
   */
  void forget(u32 sfbPin) ;

  /**
     Specify the desired state of \a sfbPin when there are no events queued for it.  When there are
     no events queued for a pin, \c mode specifies how the pin should be handled.  It can be left on
     or off or be returned to reflex mode.  The default behavior is #QLED_IDLE_REFLEX if this method
     is not used.

     \param sfbPin the pin number of a built-in LED.

     \param mode idle behavior of \a sfbPin: #QLED_IDLE_ON, #QLED_IDLE_OFF, or #QLED_IDLE_REFLEX.

     \blinks E_API_ARGUMENT if \a sfbPin is not the pin number of a built-in LED.
   */
  void whenIdle(u32 sfbPin, u32 mode) ;

  /**
     Compute and return the (approximate) total amount of time in all events currently queued for \a
     sfbPin.  The returned time is only approximate because (1) All events are measured in 16ms
     units, and (2) it doesn't include the amount of time remaining until SFBQLED's alarm next tick.

     \param sfbPin the pin number of a built-in LED.

     \blinks E_API_ARGUMENT if \a sfbPin is not the pin number of a built-in LED.
   */
  u32 time(u32 sfbPin) ;

  /**
     Schedule an 'on' event for \a sfbPin lasting approximately \a ms, to begin after any other
     events currently on \a sfbPin.  The duration is only approximate because all events are
     measured in 16ms units, with a minimum duration of 16ms.  (This is billed ripoff-style like
     cellphone airtime: 0..15ms is one unit, 16ms..31ms is two units, etc.)

     Note that the maximum duration that can fit in a single event is 2032ms, but it is \b not an
     error for \a ms to exceed that value.  Instead, if \a ms exceeds 2032ms, then \b multiple \b
     events will be scheduled (as long as room is #available()) until the requested number of \a ms
     have been scheduled.

     \param sfbPin the pin number of a built-in LED.

     \param ms the approximate number of milliseconds this event should last.

     \blinks E_API_ARGUMENT if \a sfbPin is not the pin number of a built-in LED.
   */
  void on(u32 sfbPin, u32 ms) ;

  /**
     Schedule an 'off' event for \a sfbPin lasting approximately \a ms, to begin after any other
     events currently on \a sfbPin.  See #on() for details.

     \param sfbPin the pin number of a built-in LED.

     \param ms the approximate number of milliseconds this event should last.

     \blinks E_API_ARGUMENT if \a sfbPin is not the pin number of a built-in LED.
  */
  void off(u32 sfbPin, u32 ms) ;

#ifdef TEST_SFBQLED
  void assertOK() ;
  u32 getAlarm() { return alarm; }
  void doTick(u32 w) { tick(w); }
#endif

};

extern SFBQLED QLED;

#endif /* SFBQLED_H */
