/* $Id: MenuBackend.h 1231 2011-08-25 10:57:49Z abrevig $
||
|| @author         Alexander Brevig <abrevig@wiring.org.co>
|| @url            http://wiring.org.co/
|| @contribution   Adrian Brzezinski
|| @contribution   Bernhard Benum
|| @contribution   Brett Hagman <bhagman@wiring.org.co>
|| @contribution   Ryan Michael <kerinin@gmail.com>
||
|| @description
|| | Provides an easy way of making menus.
|| |
|| | Wiring Cross-platform Library
|| #
||
|| @license Please see cores/Common/License.txt.
||
*/

#ifndef MENUBACKEND_H
#define MENUBACKEND_H
#if defined(ARDUINO) && ARDUINO >= 100
  #include "Arduino.h"
#else
  #include "WProgram.h"
#endif

class MenuBackend; //forward declaration of the menu backend
class MenuItem; //forward declaration of the menu item

struct MenuChangeEvent
{
  const MenuItem &from;
  const MenuItem &to;
};

struct MenuUseEvent
{
  MenuItem &item;
};

struct MenuItemChangeEvent
{
  const MenuItem &item;
};

struct MenuMoveEvent
{
  const MenuItem &item;
};

typedef void (*cb_change)(MenuChangeEvent);
typedef void (*cb_use)(MenuUseEvent);
typedef void (*cb_move)(MenuMoveEvent);


/*
  A menu item will be a container for an item that is a part of a menu
  Each such item has a logical position in the hierarchy as well as a text and maybe a mnemonic shortkey
*/
class MenuItem
{
  public:    
    const __FlashStringHelper *name;    

    /*
    || @constructor
    || | Basic item with a name and an optional mnemonic  [File (f)]
    || #
    ||
    || @example
    || | MenuItem file = MenuItem("File",'F');
    || #
    ||
    || @parameter itemName  the name of the item
    || @parameter shortKey the mnemonic 'shortkey'
    */
    MenuItem(const __FlashStringHelper *itemName, uint8_t shortKey = 0) : name(itemName), shortkey(shortKey)
    {
      before = right = after = left = 0;
      menuBackend = 0;
    }

    /*
    || @description
    || | Register a backend for this item to use for callbacks and such
    || #
    ||
    || @parameter mb the menu backend that controls this item
    */
    inline void registerBackend(MenuBackend &mb)
    {
      menuBackend = &mb;
    }

    //void use(){} //update some internal data / statistics
    /*
    || @description
    || | Get the name of this item
    || #
    ||
    || @return The name of this item
    */
    inline const __FlashStringHelper * getName() const
    {
      return name;
    }
    /*
    || @description
    || | Get the shortkey of this item
    || #
    ||
    || @return The shortkey of this item
    */
    inline uint8_t getShortkey() const
    {
      return shortkey;
    }
    /*
    || @description
    || | Check to see if this item has a shorkey
    || #
    ||
    || @return true if this item has a shorkey
    */
    inline const bool hasShortkey() const
    {
      return (shortkey != '\0');
    }

    /*
    || @description
    || | Check to see if this item has children
    || #
    ||
    || @return true if the item has children
    */
    inline const bool hasChildren() const
    {
      return (before || right || after || left);
    }

    /*
    || @description
    || | Get the item 'before' this item
    || #
    ||
    || @return the item before this
    */
    inline MenuItem *getBefore() const
    {
      return before;
    }
    /*
    || @description
    || | Get the item 'right' of this item
    || #
    ||
    || @return the item right of this
    */
    inline MenuItem *getRight() const
    {
      return right;
    }
    /*
    || @description
    || | Get the item 'after' this item
    || #
    ||
    || @return the item after this
    */
    inline MenuItem *getAfter() const
    {
      return after;
    }
    /*
    || @description
    || | Get the item 'left' of this item
    || #
    ||
    || @return the item left of this
    */
    inline MenuItem *getLeft() const
    {
      return left;
    }

    //default vertical menu
    /*
    || @description
    || | Add an item after this item in the hierarchy
    || #
    ||
    || @parameter mi the item to add
    || @return the item sent as parameter for chaining
    */
    MenuItem &add(MenuItem &mi)
    {
      return addAfter(mi);
    }

    /*
    || @description
    || | Add an item before this item in the hierarchy
    || #
    ||
    || @parameter mi the item to add
    || @return the item sent as parameter for chaining
    */
    MenuItem &addBefore(MenuItem &mi)
    {
      mi.after = this;
      before = &mi;
      return mi;
    }
    /*
    || @description
    || | Add an item right to this item in the hierarchy
    || #
    ||
    || @parameter mi the item to add
    || @return the item sent as parameter for chaining
    */
    MenuItem &addRight(MenuItem &mi)
    {
      mi.left = this;
      right = &mi;
      return mi;
    }
    /*
    || @description
    || | Add an item after this item in the hierarchy
    || #
    ||
    || @parameter mi the item to add
    || @return the item sent as parameter for chaining
    */
    MenuItem &addAfter(MenuItem &mi)
    {
      mi.before = this;
      after = &mi;
      return mi;
    }
    /*
    || @description
    || | Add an item left of this item in the hierarchy
    || #
    ||
    || @parameter mi the item to add
    || @return the item sent as parameter for chaining
    */
    MenuItem &addLeft(MenuItem &mi)
    {
      mi.right = this;
      left = &mi;
      return mi;
    }
    /*
    || @description
    || | Set a callback to be fired before any 'move' function is called with this item 
    || | as the current MenuItem.  
    || #
    || 
    || @paramter cb The callback to be fired
    || @return this MenuItem
    */
    MenuItem &onChangeFrom(cb_change cb)
    {
      cb_onChangeFrom = cb;
      return *this;
    }
     /*
    || @description
    || | Set a callback to be fired after any 'move' function is called with this item 
    || | as the resulting MenuItem.  
    || #
    || 
    || @paramter cb The callback to be fired
    || @return this MenuItem
    */
    MenuItem &onChangeTo(cb_change cb)
    {
      cb_onChangeTo = cb;
      return *this;
    }
 
   /*
    || @description
    || | Set a callback to be fired when 'moveUp' is called with this item as the current MenuItem
    || #
    || 
    || @paramter cb The callback to be fired
    || @return this MenuItem
    */
    MenuItem &onUp(cb_move cb)
    {
      cb_onUp = cb;
      return *this;
    }
    /*
    || @description
    || | Set a callback to be fired when 'moveDown' is called with this item as the current MenuItem
    || #
    || 
    || @paramter cb The callback to be fired
    || @return this MenuItem
    */
    MenuItem &onDown(cb_move cb)
    {
      cb_onDown = cb;
      return *this;
    }
    /*
    || @description
    || | Set a callback to be fired when 'moveLeft' is called with this item as the current MenuItem
    || #
    || 
    || @paramter cb The callback to be fired
    || @return this MenuItem
    */
    MenuItem &onLeft(cb_move cb)
    {
      cb_onLeft = cb;
      return *this;
    }
    /*
    || @description
    || | Set a callback to be fired when 'moveRight' is called with this item as the current MenuItem
    || #
    || 
    || @paramter cb The callback to be fired
    || @return this MenuItem
    */
    MenuItem &onRight(cb_move cb)
    {
      cb_onRight = cb;
      return *this;
    }
    /*
    || @description
    || | Set a callback to be fired when 'use' is called with this item as the current MenuItem
    || #
    || 
    || @paramter cb The callback to be fired
    || @return this MenuItem
    */
    MenuItem &onUse(cb_use cb)
    {
      cb_onUse = cb;
      return *this;
    }

  protected:
    uint8_t shortkey;
    
    MenuItem *before;
    MenuItem *right;
    MenuItem *after;
    MenuItem *left;

    cb_change cb_onChangeFrom;
    cb_change cb_onChangeTo;
    cb_move cb_onUp;
    cb_move cb_onDown;
    cb_move cb_onLeft;
    cb_move cb_onRight;
    cb_use cb_onUse;

    MenuBackend *menuBackend;

  private:
    friend class MenuBackend;

    MenuItem *moveUp()
    {
      return before;
    }

    MenuItem *moveDown()
    {
      return after;
    }

    MenuItem *moveLeft()
    {
      return left;
    }

    MenuItem *moveRight()
    {
      return right;
    }

};

class MenuBackend
{
  public:
    MenuBackend(cb_use menuUse, cb_change menuChange = 0) : root(F("MenuRoot"))
    {
      current = &root;
      cb_menuChange = menuChange;
      cb_menuUse = menuUse;
    }

    /*
    || @description
    || | Get the root of this menu
    || #
    ||
    || @return the root item
    */
    MenuItem &getRoot()
    {
      return root;
    }
    /*
    || @description
    || | Get the current item of this menu
    || #
    ||
    || @return the current item
    */
    MenuItem &getCurrent()
    {
      return *current;
    }

    /*
    || @description
    || | Move up in the menu structure, will fire move event
    || #
    */
    void moveUp()
    {
      if (current->cb_onUp)
      {
        MenuMoveEvent mme = { *current };
        (*current->cb_onUp)(mme);
      }
      setCurrent(current->moveUp());
    }
    /*
    || @description
    || | Move down in the menu structure, will fire move event
    || #
    */
    void moveDown()
    {
      if (current->cb_onDown)
      {
        MenuMoveEvent mme = { *current };
        (*current->cb_onDown)(mme);
      }
      setCurrent(current->moveDown());
    }
    /*
    || @description
    || | Move left in the menu structure, will fire move event
    || #
    */
    void moveLeft()
    {
      if (current->cb_onLeft)
      {
        MenuMoveEvent mme = { *current };
        (*current->cb_onLeft)(mme);
      }
      setCurrent(current->moveLeft());
    }
    /*
    || @description
    || | Move right in the menu structure, will fire move event
    || #
    */
    void moveRight()
    {
      if (current->cb_onRight)
      {
        MenuMoveEvent mme = { *current };
        (*current->cb_onRight)(mme);
      }
      setCurrent(current->moveRight());
    }
    /*
    || @description
    || | Use an item
    || #
    ||
    || @parameter item is the item to use
    */
    void use(MenuItem &item)
    {
      setCurrent(&item);
      use();
    }
    /*
    || @description
    || | Use an item per its shortkey
    || #
    ||
    || @parameter shortkey the shortkey of the target item
    */
    void use(uint8_t shortkey)
    {
      recursiveSearch(shortkey, &root);
      use();
    }
    /*
    || @description
    || | Use an item, will fire use event
    || #
    */
    void use()
    {
      //current->use();
      if (current->cb_onUse)
      {
        MenuUseEvent mue = { *current };
        (*current->cb_onUse)(mue);
      }
      if (cb_menuUse)
      {
        MenuUseEvent mue = { *current };
        cb_menuUse(mue);
      }
    }
    /*
    || @description
    || | Select an item, will fire change event
    || #
    */
    void select(MenuItem &item)
    {
      setCurrent(&item);
    }

  private:
    void setCurrent(MenuItem *next)
    {
      if (next)
      {
        MenuChangeEvent mce = { *current, *next };
        if (current->cb_onChangeFrom)
        {
          (*current->cb_onChangeFrom)(mce);
        }
        if (next->cb_onChangeTo)
        {
          (*next->cb_onChangeTo)(mce);
        }
        if (cb_menuChange)
        {
          (*cb_menuChange)(mce);
        }
        current = next;
      }
    }

    void foundShortkeyItem(MenuItem *mi)
    {
      current = mi;
    }

    char canSearch(uint8_t shortkey, MenuItem *m)
    {
      if (m == 0)
      {
        return 0;
      }
      else
      {
        if (m->getShortkey() == shortkey)
        {
          foundShortkeyItem(m);
          return 1;
        }
        return -1;
      }
    }

    void rSAfter(uint8_t shortkey, MenuItem *m)
    {
      if (canSearch(shortkey, m) != 1)
      {
        rSAfter(shortkey, m->getAfter());
        rSRight(shortkey, m->getRight());
        rSLeft(shortkey, m->getLeft());
      }
    }

    void rSRight(uint8_t shortkey, MenuItem *m)
    {
      if (canSearch(shortkey, m) != 1)
      {
        rSAfter(shortkey, m->getAfter());
        rSRight(shortkey, m->getRight());
        rSBefore(shortkey, m->getBefore());
      }
    }

    void rSLeft(uint8_t shortkey, MenuItem *m)
    {
      if (canSearch(shortkey, m) != 1)
      {
        rSAfter(shortkey, m->getAfter());
        rSLeft(shortkey, m->getLeft());
        rSBefore(shortkey, m->getBefore());
      }
    }

    void rSBefore(uint8_t shortkey, MenuItem *m)
    {
      if (canSearch(shortkey, m) != 1)
      {
        rSRight(shortkey, m->getRight());
        rSLeft(shortkey, m->getLeft());
        rSBefore(shortkey, m->getBefore());
      }
    }

    void recursiveSearch(uint8_t shortkey, MenuItem *m)
    {
      if (canSearch(shortkey, m) != 1)
      {
        rSAfter(shortkey, m->getAfter());
        rSRight(shortkey, m->getRight());
        rSLeft(shortkey, m->getLeft());
        rSBefore(shortkey, m->getBefore());
      }
    }

    MenuItem root;
    MenuItem *current;

    cb_change cb_menuChange;
    cb_use cb_menuUse;
};

#endif
// MENUBACKEND_H
