/*
||
|| @file 	MenuBackend.h
|| @version 1.4
|| @author 	Alexander Brevig
|| @contact alexanderbrevig@gmail.com
|| @contribution Adrian Brzezinski adrb@wp.pl, http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?action=viewprofile;username=vzhang
||
|| @description
|| | Provide an easy way of making menus
|| #
||
|| @license
|| | 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; version
|| | 2.1 of the License.
|| |
|| | 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 Lesser 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
|| #
||
*/

#ifndef MenuBackend_h
#define MenuBackend_h

/*
	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:
	MenuItem(const char* itemName, char shortKey='\0' ) : name(itemName), shortkey(shortKey) {
		before = right = after = left = 0;
	}

	//void use(){} //update some internal data / statistics
	inline const char* getName() const { return name; }
	inline const char getShortkey() const { return shortkey; }
	inline const bool hasShortkey() const { return (shortkey!='\0'); }
	inline void setBack(MenuItem *b) { back = b; }
	inline MenuItem* getBack() const { return back; }
	inline MenuItem* getBefore() const { return before; }
	inline MenuItem* getRight() const { return right; }
	inline MenuItem* getAfter() const { return after; }
	inline MenuItem* getLeft() const { return left; }

	MenuItem *moveBack() { return back; }

	MenuItem *moveUp() { 
		if (before) { before->back = this; }
		return before; 
	}

	MenuItem *moveDown() { 
		if (after) { after->back = this; }
		return after; 
	}

	MenuItem *moveLeft() { 
		if (left) { left->back = this; }
		return left; 
	}

	MenuItem *moveRight() { 
		if (right) { right->back = this; }
		return right; 
	}

	//default vertical menu
	MenuItem &add(MenuItem &mi) { return addAfter(mi); }

	MenuItem &addBefore(MenuItem &mi) {
		mi.after = this;
		before = &mi;
		if ( !mi.back ) mi.back = back;
		return mi;
	}
	MenuItem &addRight(MenuItem &mi) {
		mi.left = this;
		right = &mi;
		if ( !mi.back ) mi.back = back;
		return mi;
	}
	MenuItem &addAfter(MenuItem &mi) {
		mi.before = this;
		after = &mi;
		if ( !mi.back ) mi.back = back;
		return mi;
	}
	MenuItem &addLeft(MenuItem &mi) {
		mi.right = this;
		left = &mi;
		if ( !mi.back ) mi.back = back;
		return mi;
	}
protected:

	const char* name;
	const char shortkey;

	MenuItem *before;
	MenuItem *right;
	MenuItem *after;
	MenuItem *left;
	MenuItem *back;
};

//no dependant inclusion of string or cstring
bool menuTestStrings(const char *a, const char *b) {
	while (*a) { if (*a != *b) { return false; } b++; a++; }
	return true;
}
bool operator==(MenuItem &lhs, char* test) {
	return menuTestStrings(lhs.getName(),test);
}
bool operator==(const MenuItem &lhs, char* test) {
	return menuTestStrings(lhs.getName(),test);
}
bool operator==(MenuItem &lhs, MenuItem &rhs) {
	return menuTestStrings(lhs.getName(),rhs.getName());
}
bool operator==(const MenuItem &lhs, MenuItem &rhs) {
	return menuTestStrings(lhs.getName(),rhs.getName());
}

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

struct MenuUseEvent {
	const MenuItem &item;
};

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

class MenuBackend {
public:

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

	MenuItem &getRoot() {
		return root;
	}
	MenuItem &getCurrent() {
		return *current;
	}

	void moveBack() {
		setCurrent(current->getBack());
	}

	void moveUp() {
		setCurrent(current->moveUp());
	}

	void moveDown() {
		setCurrent(current->moveDown());
	}

	void moveLeft() {
		setCurrent(current->moveLeft());
	}

	void moveRight() {
		setCurrent(current->moveRight());
	}

	void use(char shortkey)
	{
		recursiveSearch(shortkey,&root);
		use();
	}
	
	void use() {
		//current->use();
		if (cb_menuUse) {
			MenuUseEvent mue = { *current };
			cb_menuUse(mue);
		}
	}

private:
	void setCurrent( MenuItem *next ) {
		if (next) {
			if (cb_menuChange) {
				MenuChangeEvent mce = { *current, *next };
				(*cb_menuChange)(mce);
			}
			current = next;
		}
	}
	void foundShortkeyItem(MenuItem *mi) {
		mi->setBack(current);
		current = mi;
	}
	char canSearch(const char shortkey, MenuItem *m) {
		if (m==0) { return 0; }
		else  {
			if (m->getShortkey()==shortkey) {
				foundShortkeyItem(m);
				return 1;
			}
			return -1;
		}
	}
	void rSAfter(const char shortkey, MenuItem *m) {
		if (canSearch(shortkey,m)!=1) {
			rSAfter(shortkey, m->getAfter());
			rSRight(shortkey, m->getRight());
			rSLeft(shortkey, m->getLeft());
		}
	}
	void rSRight(const char shortkey, MenuItem *m) {
		if (canSearch(shortkey,m)!=1) {
			rSAfter(shortkey, m->getAfter());
			rSRight(shortkey, m->getRight());
			rSBefore(shortkey, m->getBefore());
		}
	}
	void rSLeft(const char shortkey, MenuItem *m) {
		if (canSearch(shortkey,m)!=1) {
			rSAfter(shortkey, m->getAfter());
			rSLeft(shortkey, m->getLeft());
			rSBefore(shortkey, m->getBefore());
		}
	}
	void rSBefore(const char shortkey, MenuItem *m) {
		if (canSearch(shortkey,m)!=1) {
			rSRight(shortkey, m->getRight());
			rSLeft(shortkey, m->getLeft());
			rSBefore(shortkey, m->getBefore());
		}
	}
	void recursiveSearch(const char 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