/*                                              -*- mode:C++; fill-column:100 -*-
  SFBAlarm.h - Support for time-based events
  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 SFBAlarm.h Support for time-based events.
  \author David H. Ackley.  
  \date (C) 2009 All rights reserved.
  \lgpl
 */
#ifndef SFBALARM_H_
#define SFBALARM_H_

#include "SFBTypes.h"
#include "SFBWiring.h"  /* for millis() */

/**
   A function pointer type that can be called when an alarm is triggered.  This is the simpler of
   the such funtion pointer types; see SFBAlarmArgHandler for the more complicated type.
 */
typedef void (*SFBAlarmHandler)(u32 when);

/**
   A function pointer type that can be called when an alarm is triggered.  This is the more powerful
   of the such funtion pointer types; see SFBAlarmArgHandler for the more limited type.
 */
typedef void (*SFBAlarmArgHandler)(u32 when, void *arg);

#ifndef SFBALARM_QUEUE_SIZE
#define SFBALARM_QUEUE_SIZE 51  ///< The maximum number of alarms.  The RAM footprint of an SFBAlarm
                                ///  grows (at least) as (14 bytes * SFBALARM_QUEUE_SIZE).
#endif

#if SFBALARM_QUEUE_SIZE < 256
typedef u8 SFBAlarmIndexType;   ///< The type of an alarm index.  u8 suffices unless SFBALARM_QUEUE_SIZE exceeds 255
#else
#if SFBALARM_QUEUE_SIZE < 65536
typedef u16 SFBAlarmIndexType;
#else
typedef u32 SFBAlarmIndexType;
#endif
#endif

/**
 * Manage "alarm clock"-style settable timers.
 */
class SFBAlarm {
  enum { MAX_EVENTS = SFBALARM_QUEUE_SIZE };
 public: 

  SFBAlarm() ;

  /**
     Create a new alarm, return its corresponding alarm number.  Note this method blinks
     #E_API_NO_MORE_ALARMS if no more alarms are available; if a sketch needs to survive running out
     of alarms it must call #canCreate() to ascertain another alarm is available before calling this
     method.

     @param handler function to call whenever this alarm goes off

     @return a non-zero \c alarmNumber to use to refer to this alarm in the future

     @blinks #E_API_NO_MORE_ALARMS if the alarm system is full so a new alarm could not be created.
  */ 
  u32 create(SFBAlarmHandler handler) ;  

  /**
     Create a new alarm, return its corresponding alarm number.  Note this method blinks
     #E_API_NO_MORE_ALARMS if no more alarms are available; if a sketch needs to survive running out
     of alarms it must call #canCreate() to ascertain another alarm is available before calling this
     method.

     @param handler function to call whenever this alarm goes off

     @param arg an arbitrary pointer value to associate with this alarm, which will be provided to
     \a handler whenever the alarm goes off.

     @return a non-zero \c alarmNumber to use to refer to this alarm in the future

     @blinks #E_API_NO_MORE_ALARMS if the alarm system is full so a new alarm could not be created.
  */ 
  u32 create(SFBAlarmArgHandler handler, void * arg) ; /* Create a new alarm object, return alarm number > 0 or 0 if full */ 

  void update(u32 alarmNumber, SFBAlarmHandler handler) { /* Alter the handler of an existing alarm */ 
    update(alarmNumber,(SFBAlarmArgHandler) handler, (void*) 0);
  }

  void update(u32 alarmNumber, SFBAlarmArgHandler handler, void * arg); /* Alter the handler and/or arg of an existing alarm */ 

  /**
     If alarm \a alarmNumber is set, access the alarm time.  If \a alarmNumber is \e not set,
     returns the \e most \e recent \e previous alarm time; if \a alarmNumber has never been set,
     return 0.

     @param alarmNumber The alarm to access

     @return the current or most-recent alarm setting, or zero

     @blinks #E_API_BAD_ALARM if \a alarmNumber is not an alarm number previously returned by
     #create(SFBAlarmHandler) or #create(SFBAlarmArgHandler,void*)

     @since 0.9.5
  */

  u32 get(u32 alarmNumber) ;

  /**
     Set (or reset) alarm \a alarmNumber to go off at \a when.  If \a when is in the past relative
     to #millis() (in the sense of #IS_EARLIER), the alarm will go off at its soonest possible
     opportunity.

     @param alarmNumber The alarm to set

     @return \c true if the alarm was currently set, \c false if not.

     @blinks #E_API_BAD_ALARM if \a alarmNumber is not an alarm number previously returned by
     #create(SFBAlarmHandler) or #create(SFBAlarmArgHandler,void*)
  */
  bool set(u32 alarmNumber, u32 when) ;

  /**
     Cancel any pending alarm for alarm \a alarmNumber.

     @param alarmNumber The alarm to cancel

     @return \c true if the alarm had been currently set, \c false if not.

     @blinks #E_API_BAD_ALARM if \a alarmNumber is not an alarm number previously returned by
     #create(SFBAlarmHandler) or #create(SFBAlarmArgHandler,void*)
  */
  bool cancel(u32 alarmNumber) ;   

  /**
     What alarm is active?  This method should be called only during a callback to an alarm handler; its
     return value is not defined elsewhere.

     @return The alarm number that triggered the current alarm handler callback.
  */
  u32 currentAlarmNumber() { return currentAlarmIndex; }      /* what alarm is active (only valid during callback) */ 

  int runDueEvents(int maxEvents, u32 deadline) ;

  int runPriorityEvents(int maxEvents, u32 priorityAtLeast) ;

  /**
     Determine if it is possible to create another alarm.  The alarm creating functions
     #create(SFBAlarmHandler) and #create(SFBAlarmArgHandler,void*) die blinking if they are called
     when there's no more room to create an additional alarm.  To avoid that risk, a sketch may call
     this function first.

     @return \c true if at least one more alarm can be created, \c false otherwise.

   */
  bool canCreate() ;

  u32 created() { 
    return alarms; 
  }

  bool canAdd() ;

  bool canRemove() ;

  u32 count() { return used; }

  bool inUse(u32 alarmIndex) {          /* true iff alarmIndex is a legal, currently scheduled alarm number */
    return alarmIndex <= alarms
      && idxs[alarmIndex] > 0;
  }

  /**
   * Get the earliest time in the queue.  Only valid
   * when canRemoveEvent() returns true.
   */
  u32 getTopTime() {
    return times[slots[1]];
  }

  int getUsed() {
    return used;
  }
 private:

  /**
   * Remove the topmost element and run it.  Only valid
   * when canRemoveEvent() returns true.
   */
  void removeAndRunTop() ;

  void deleteElt(int eltIdx);              /* eltIdx must currently be in pq! */

  u32 times[MAX_EVENTS];                   /* when this event goes off */
  SFBAlarmArgHandler handlers[MAX_EVENTS]; /* who to call when it does */
  void * args[MAX_EVENTS];                 /* argument to pass back to them */
  SFBAlarmIndexType slots[MAX_EVENTS];     /* priority queue map slot idx->elt idx */
  SFBAlarmIndexType idxs[MAX_EVENTS];      /* reverse map elt idx->slot idx */
  SFBAlarmIndexType used;
  SFBAlarmIndexType alarms;
  SFBAlarmIndexType currentAlarmIndex;

  void bubbleUp(int fromIndex);
  void bubbleDown(int fromIndex);

#ifdef TEST_SFBALARM
  friend void heapcheck(int) ;
  friend void runRandoms() ;
#endif

};

/**
   The object controlling all #SFBAlarm functions.
 */
extern SFBAlarm Alarms;

#endif /*SFBALARM_H_*/
