#include <conio.h>
#include <stdio.h>
#include <stdlib.h>

/*
 * Kody klaves.
 */
enum
{
	KC_UP,   /* nahoru */
	KC_DOWN, /* dolu */
	KC_ESC,  /* escape */
	KC_ENTER /* enter */
};

/*
 * Typy uzlu pro popis menu.
 */
enum
{
	NT_ITEM, /* polozka */
	NT_MENU  /* menu */
};

/*
 * Hodnoty spolecne pro vsechny uzly menu.
 */
typedef struct MENU_NODE
{
	/*
	 * Typ uzlu.
	 * Jedna z konstant NT_xxx.
	 */
	int type;
	
	/*
	 * Nazev polozky.
	 */
	const char *name;
} MENU_NODE;

/*
 * Polozka menu.
 */
typedef struct MENU_ITEM
{
	/*
	 * Spolecne hodnoty.
	 */
	MENU_NODE node;
	
	/*
	 * Kod polozky vraceny pri jejim vyberu.
	 */
	int code;
} MENU_ITEM;

/*
 * Menu.
 */
typedef struct MENU_MENU
{
	/*
	 * Spolecne hodnoty.
	 */
	MENU_NODE node;
	
	/*
	 * Pole ukayatelu na uzly menu.
	 * Pole musi byt ukoncene hodnotou NULL.
	 */
	const MENU_NODE **nodes;
} MENU_MENU;

/*
 * Makra pro definici menu.
 * Volani:
 *   ITEM(var, name, code);
 *   MENU(var, name) {nodes};
 * Parametry:
 *   var - Nazev vytvorene promenne s uzlem, napr. item1.
 *   name - Zobrazeny nazev uzlu, napr. "Item 1".
 *   code - Identifikace polozky vracena pri jejim vyberu, napr. 1.
 *   nodes - Seznam odkazu na uzly daneho menu ukonceny NULL, napr. &item1, &item2, NULL.
 * Priklad:
 *   ITEM(i1, "Item 1", 1);
 *   MENU(m1, "Menu 1") { &i1, NULL };
 */
#define ITEM(var, name, code) \
	static const MENU_ITEM var = {{NT_ITEM, name}, code}
#define MENU(var, name) \
	extern const void *menu_nodes_##var[]; \
	static const MENU_MENU var = {{NT_MENU, name}, (const MENU_NODE **) menu_nodes_##var}; \
	static const void *menu_nodes_##var[] =

/*
 * Pocka na stisk klavesy a vrati jeji kod.
 * Vraci:
 *   Kod klavesy. Jedna z konstant KC_xxx.
 */
static int get_key(void)
{
	while ( 1 )
	{
		switch ( getch() )
		{
		case 224:
			switch ( getch() )
			{
			case 72:
				return KC_UP;
			case 80:
				return KC_DOWN;
			}
			break;
		case 27:
			return KC_ESC;
		case 13:
			return KC_ENTER;
		}
	}
}

/*
 * Zobrazi na displayi dany text.
 * Parametry:
 *   text - Text pro zobrazeni ukonceny nulou.
 */
static void show(const char *text)
{
	printf("%40s\r", text);
}

/*
 * Implementace menu.
 * Vraci:
 *   Vybrana polozka nebo NULL, pokud bylo stisknuto ESC.
 */
static const MENU_ITEM *call_menu(const MENU_MENU *menu)
{
	int index;
	
	/* zacneme prvnim uzlem */
	index = 0;
	
	/* vyber polozky */
	while ( 1 )
	{
		const MENU_NODE *node;
		int key;
		
		/* najdi uzel, muze byt NULL, pokud je menu prazdne */
		node = menu->nodes[index];
		
		/* zobraz text */
		show(node ? node->name : "");
		
		/* pockej na klavesu */
		key = get_key();
		switch ( key )
		{
		case KC_UP:
			if ( index > 0 )
			{ /* nejsme na zacatku, lze jit zpet, jdi zpet */
				index--;
			}
			continue;
			
		case KC_DOWN:
			if ( node && menu->nodes[index + 1] )
			{ /* nejsme na konci, lze jit dopredu, jdi dopredu */
				index++;
			}
			continue;
			
		case KC_ESC:
			return NULL;
			
		case KC_ENTER:
			/* otestuj, co bylo vybrano */
			if ( ! node )
			{ /* nic, menu je prazdne, ignoruj */
				continue;
			}
			else if ( node->type == NT_ITEM )
			{ /* byla vybrana polozka, vrat ji */
				const MENU_ITEM *item = (const MENU_ITEM *) node;
				return item;
			}
			else
			{ /* bylo vybrano menu, zavolej ho rekurzivne */
				const MENU_MENU *submenu = (const MENU_MENU *) node;
				const MENU_ITEM *item;
				
				/* zavolej podmenu */
				item = call_menu(submenu);
				if ( item )
				{ /* neco bylo vybrano, vrat to */
					return item;
				}
				else
				{ /* bylo stisknuto ESC, pokracuj v tomto menu */
					continue;
				}
			}
			
		default:
			continue;
		}
	}
}

/*
 * Struktura menu.
 */
ITEM(item1, "Item 1", 1);
ITEM(item2, "Item 2", 2);
ITEM(item3_1, "Item 3/1", 3);
ITEM(item3_2, "Item 3/2", 4);
MENU(menu3, "Menu 3") {&item3_1, &item3_2, NULL};
MENU(main_menu, "") {&item1, &item2, &menu3, NULL};

/*
 * Hlavni program.
 */
int main(int argc, const char **argv)
{
	const MENU_ITEM *item;
	
	item = call_menu(&main_menu);
	if ( item )
	{ /* neco bylo vybrano */
		printf("Vybrano: %d\n", item->code);
	}
	else
	{ /* bylo stisknuto ESC */
		printf("Tak nic, no.\n");
	}
	
	return 0;
}
