// Implements a class to draw and animate a pair of 'emotive' eyes for a robot
//
#pragma once

#include <MD_MAX72xx.h>

// Misc defines
#if !defined(ARRAY_SIZE)
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))  ///< number of elements in an array
#endif

#define EYE_COL_SIZE 8                          ///< number of columns in one eye

// Module offsets from first module specified
#define LEFT_MODULE_OFFSET   1    ///< offset from the base LED module for the left eye
#define RIGHT_MODULE_OFFSET  0    ///< offset from the base LED module for the right eye

// Array references for eyeData array below
#define LEFT_EYE_INDEX   1        ///< array reference in the eye data for the left eye
#define RIGHT_EYE_INDEX  0        ///< array reference in the eye data for the right eye

// Basic unit of time a frame is displayed
#define FRAME_TIME 100            ///< minimum animation time

/**
* Robot Eyes Class.
* This class manages the displayed of animated eyes using LED matrices using the functions
* provided by the MD_MAX72xx library.
*/
class MD_RobotEyes
{
public:
  /**
  * Emotions enumerated type.
  *
  * This enumerated type defines the emotion animations
  * available in the class for the eyes display
  */
  //
  typedef enum
  {
    E_NONE,     ///< placeholder for no emotions, not user selectable
    E_NEUTRAL,  ///< eyes in neutral position (no animation)
    E_BLINK,    ///< both eyes blink
    E_WINK,     ///< one eye blink
    E_LOOK_L,   ///< both eyes look left
    E_LOOK_R,   ///< both eyes look right
    E_LOOK_U,   ///< both eyes look up
    E_LOOK_D,   ///< both eyes look down
    E_ANGRY,    ///< eyes look angry (symmetrical)
    E_SAD,      ///< eyes look sad (symmetrical)
    E_EVIL,     ///< eyes look evil (symmetrical)
    E_EVIL2,    ///< eyes look evil (asymmetrical)
    E_SQUINT,   ///< both eye squint
    E_DEAD,     ///< eyes indicate dead (different)
    E_SCAN_UD,  ///< both eyes scanning Up/Down
    E_SCAN_LR,  ///< both eyes scanning Left/Right
  } emotion_t;

  /**
  * Class Constructor.
  *
  * Instantiate a new instance of the class.
  */
  MD_RobotEyes(void):
  _timeBlinkMinimum(5000), _animState(S_IDLE), _autoBlink(true), _nextEmotion(E_NEUTRAL)
  {};

  /**
  * Class Destructor.
  *
  * Released any allocated memory and does the necessary to clean
  * up once the object is no longer required.
  */
  ~MD_RobotEyes(void) { };

  /**
  * Initialize the object.
  *
  * Initialize the object data. This needs to be called during setup() to initialize new
  * data for the class that cannot be done during the object creation.
  *
  * Outside of the class, the MD_MAX72xx library should be initialized and the pointer
  * to the MD_MAX72xx object passed to the parameter. Also, as the eyes could be in the
  * middle of a string of LED modules, the first 'eye' module can be specified.
  *
  * /param M            pointer to the MD_MAX72xx library object.
  * /param moduleStart  the first 'eye' LED module. Defaults to 0 if not specified.
  */
  void begin(MD_MAX72XX *M, uint8_t moduleStart = 0);

  /**
  * Set the animation type and parameters.
  *
  * Set the next animations to the specified. Additionally, set whether the animation should
  * auto reverse the action (eg, blink down then back up again) and whether the animation
  * should be run in reverse.
  *
  * Animations are generally symmetric, so only half the animation needs to be specified.
  * If an animated expression needs to be held, the animation should be run without auto
  * reverse, which holds the animation at the end point, and then later run the animation
  * in reverse from the last position to return to the idle state.
  *
  * \param e  the type of emotion to be displayed, one of the emotion_T enumerated values.
  * \param r  if true, run auto reverse.
  * \param b  if true, start the animation from the end of the sequence.
  */
  inline void setAnimation(emotion_t e, bool r, bool b = false) { _nextEmotion = e; _autoReverse = r; _animReverse = b; };

  /**
  * Set the blink time.
  *
  * When no animation is running and AutoBlink is set, the eyes will occasionally blink.
  * Set the minimum time period between blinks. A blink will occur a random time after this.
  *
  * \param t  the minimum time between blinking actions in milliseconds.
  */
  inline void setBlinkTime(uint16_t t) { _timeBlinkMinimum = t; };

  /**
  * Set or reset auto blink mode.
  *
  * When no animation is running and AutoBlink is set, the eyes will occasionally blink.
  *
  * \param b  set auto blink if true, reset auto blink if false.
  */
  inline void setAutoBlink(bool b) { _autoBlink = b; };

  /**
  * Display a text message.
  *
  * At the end of the current animation, the text will be scrolled across the 'eyes'
  * and then the eyes are returned to the neutral expression
  *
  * \param p  a pointer to a char array containing a nul terminated string.
              The string must remain in scope while the message is being displayed.
  */
  inline bool setText(const char *pText) { if (_pText != nullptr) return(false); else _pText = pText; return(true); };

  /**
  * Animate the display.
  *
  * This method needs to be invoked as often as possible to ensure smooth animation.
  *
  * The calling program should monitor the return value for 'true' in order to know when
  * the animation has concluded. A 'true' return value means that the animation is complete.
  *
  * \return bool  true if the animation has completed, false otherwise.
  */
  bool runAnimation(void);

protected:
  // Animations FSM state
  typedef enum
  {
    S_IDLE,
    S_RESTART,
    S_ANIMATE,
    S_PAUSE,
    S_TEXT,
  } animState_t;

  // Define an animation frame
  typedef struct
  {
    uint8_t eyeData[2];  // [LEFT_MODULE_OFFSET] and [RIGHT_MODULE_OFFSET] eye character from font data
    uint16_t timeFrame;  // time for this frame in milliseconds
  } animFrame_t;

  // Define an entry in the animation sequence lookup table
  typedef struct
  {
    emotion_t   e;
    const animFrame_t *seq;
    uint8_t     size;
  } animTable_t;

  // Display parameters
  MD_MAX72XX  *_M;
  uint16_t _sd;   // start module for the display

  // Animation parameters
  uint32_t  _timeStartPause;
  uint32_t  _timeLastAnimation;
  uint16_t  _timeBlinkMinimum;
  animState_t _animState;
  bool      _autoBlink;
  uint16_t  _scrollDelay;

  // Animation control data
  animTable_t _animEntry;     // record with animation sequence parameters
  int8_t      _animIndex;     // current index in the animation sequence
  bool        _animReverse;   // true = reverse sequence, false = normal sequence
  bool        _autoReverse;   // true = always play the reverse, false = selected direction only
  emotion_t   _nextEmotion;   // the next emotion to display
  const char *      _pText;         // pointer to text data in user code. Not null means there is text to print

  // Methods
  void loadEye(uint8_t module, uint8_t ch);
  void drawEyes(uint8_t L, uint8_t R);
  uint8_t loadSequence(emotion_t e);  // return the size of the sequence
  void loadFrame(animFrame_t* pBuf);
  void showText(bool bInit = false);

  void dumpSequence(const animFrame_t* pBuf, uint8_t numElements);  // debugging routine only

  // Static data tables
  static const animFrame_t seqBlink[], seqWink[];
  static const animFrame_t seqLeft[], seqRight[], seqUp[], seqDown[];
  static const animFrame_t seqAngry[], seqSad[], seqEvil[], seqEvil2[];
  static const animFrame_t seqSquint[], seqDead[];
  static const animFrame_t seqScanUpDown[], seqScanLeftRight[];

  // Lookup table to find animation
  static const animTable_t lookupTable[];
};
