// -----------------------------------------------
// qmenu.cpp
// -----------------------------------------------
// Quick menu system for my games/etc
// ----------------------------------------------- 
// Developed By Kronoman - Copyright (c) 2004
// In loving memory of my father
// -----------------------------------------------

// Don't open this file until doomsday!!

#include "qmenu.h"
#include "gerror.h" // error reporting
// -------------------------------------------------------------------
// This are allegro timer routines, needed to keep the 
// menu manager calling the events at constant rate
// -------------------------------------------------------------------
#include <limits.h> // need this to know the max size of the counter...

static volatile int time_counter = 0; // first timer counter, counts in range from 0..BPS_OF_MENU_MANAGER
static volatile unsigned long int big_timer_counter = 0; // second timer, counts in range from 0..maximun int
static volatile int speed_counter = 0; // this speed counter is to keep the rate of drawing at constant fps

// how many times we have installed / erased the timer handler?
// really, the timer gets installed only once, this only counts how many calls are made
// when reaches <= 0, the timer is uninstalled and the 2 counter resets to zero
static int timers_installed = 0; 

// --------------------------------------------------------
// The timer routine
// --------------------------------------------------------
static void menu_manager_timer_handler() 
{ 
	time_counter++; 
	big_timer_counter++;
	speed_counter++;
	if (time_counter > BPS_OF_MENU_MANAGER) time_counter = 0;
	if (big_timer_counter > ULONG_MAX - 2 ) big_timer_counter = 0; 
}
END_OF_FUNCTION(menu_manager_timer_handler);

// --------------------------------------------------------
// This starts the global timer
// --------------------------------------------------------
static void menu_manager_start_global_timer()
{
	if (timers_installed == 0)
	{
		// Install timer	
		LOCK_VARIABLE(time_counter);
		LOCK_VARIABLE(big_timer_counter);
		LOCK_VARIABLE(speed_counter);
      		LOCK_FUNCTION((void *)menu_manager_timer_handler);
		
		// DEBUG - this SHOULD BE CHECKED FOR ERROR!!!
		if (install_int_ex(menu_manager_timer_handler, BPS_TO_TIMER(BPS_OF_MENU_MANAGER)))
			raise_error("menu_manager_start_global_timer():\nERROR: Can't install timer at %d bps\n", BPS_OF_MENU_MANAGER);
	}
	
	timers_installed++;	 // this counts how many calls to this function were made
}


// --------------------------------------------------------
// This ends the global timer
// --------------------------------------------------------
static void menu_manager_stop_global_timer()
{
	if (timers_installed == 0) return; // no timer installed
		
	timers_installed--;	
	
	if (timers_installed <= 0)
	{
		// remove and reset the timer
		remove_int(menu_manager_timer_handler);
		time_counter = 0;
		speed_counter = 0;
		big_timer_counter = 0;
		timers_installed = 0;
	}
}

// ===================================================================
// ===================================================================
// From here is the menu class itself
// ===================================================================
// ===================================================================

CQMenu::CQMenu()
{
	// set defaults
	menu_font_s = menu_font_ns = font;
	menu_fg_color_ns = makecol(128,128,128);
	menu_bg_color_ns = -1;
	
	menu_fg_color_s = makecol(255,255,255);
	menu_bg_color_s = makecol(128,0,0);
	
	to_bitmap = screen;
	back_bitmap = NULL;

	mx = my = 0;
	mw = 640;
	mh = 480;
	item_y_separation = 0;
	text_align = 0;	
	callback = NULL;

	// DEBUG - we use all available controllers by default
	control.set_use_mouse(true);
	control.set_use_joystick(true);
	control.set_use_keyboard(true);
	
	this->clear_menu_list();
}

CQMenu::~CQMenu()
{
	this->clear_menu_list();
}

// --------------------------------------------------------
// empty the menu item list
// --------------------------------------------------------
void CQMenu::clear_menu_list()
{
	menu_item_text.clear();
}

// --------------------------------------------------------
// adds a selectable item to the menu
// returns the index of the item added, or -1 on error (menu full)
// --------------------------------------------------------
int CQMenu::add_item_to_menu(string item)
{
	menu_item_text.push_back(item);
	
	return menu_item_text.size(); // done
}

// --------------------------------------------------------
// Starts the menu loop until the user selects one
// Return the index of the item selected by the user (starts at 0).
// selected is the item to let selected by default
// --------------------------------------------------------
int CQMenu::do_the_menu(int selected)
{
	bool d_done = false; // dialog done?
	int mis = 0; // mis == Menu Item Selected (current selected item by user)
	int cret = 0; // controller return value
	int mhi = 0; // menu item height (measured in items)
	int msd = 0, med = 0, mdy = 0; // menu start drawing, menu end drawing, menu draw Y
	int last_hit = 0; // timer to last hit of a key, to take a small delay; if not, the menu scrolls too fast :(
	BITMAP *dbuf = NULL; // double buffer
	
	if (menu_item_text.size() < 1) raise_error("CQMenu::do_the_menu()\nERROR: no items in menu\n");
		
	if (to_bitmap == NULL) raise_error("CQMenu::do_the_menu()\nERROR: no destination bitmap to render the menu\n");

	menu_manager_start_global_timer(); // we need a timer running
	
	dbuf = create_bitmap(to_bitmap->w, to_bitmap->h); // double buffer
	if (dbuf == NULL) raise_error("CQMenu::do_the_menu()\nERROR: unable to create doble buffer bitmap!\n");
	
	mhi = mh / (text_height(menu_font_ns)+item_y_separation); // how many items can we fit vertically?
	
	// ------------- Main loop --------------
	clear_keybuf();
	rest(25);
	clear_keybuf();
	
	show_mouse(NULL); // go to hell, little mouse cursor :P
	mis = selected;
	if (mis < 0 || (unsigned)mis > menu_item_text.size()-1) mis = 0;
	
	while (!d_done)
	{
		while (speed_counter > 0)
		{
			// update menu logic here

			cret = control.do_input_poll();
			
			// if the last hit was very close (1/6 of second), ignore input
			if (abs((int)big_timer_counter - last_hit) < BPS_OF_MENU_MANAGER/10)
			{
				key[KEY_UP] = key[KEY_DOWN] = key[KEY_ENTER] = key[KEY_SPACE]=0;
				clear_keybuf();
				cret = 0;
			}
			
			if ((cret & KC_UP) || (cret & KC_LEFT) || key[KEY_UP])
			{
				mis--;
				// take a little delay to let the guy release the key (if any)
				clear_keybuf();
				while (keypressed()) readkey();
				last_hit = big_timer_counter;
			}
			
			if ((cret & KC_DOWN) || (cret & KC_RIGHT) || key[KEY_DOWN])
			{
				mis++;
				// take a little delay to let the guy release the key (if any)
				clear_keybuf();
				while (keypressed()) readkey();
				last_hit = big_timer_counter;
			}
			
			// don't let the selection go out of bounds
			if (mis < 0) mis = menu_item_text.size()-1;
			if ((unsigned)mis > menu_item_text.size()-1) mis = 0; 
			
			if ((cret & KC_BTN1) || (cret & KC_BTN2) || (cret & KC_BTN3) || key[KEY_ENTER] || key[KEY_SPACE]) d_done = true; // done, item selected wow!
				
			if (callback != NULL) callback(*this, true); // call the callback if needed, to update logic
			speed_counter--;
			if (time_counter > BPS_OF_MENU_MANAGER - 5) yield_timeslice(); // we play nice with the OS multitask, when we can (when the timer is about to reach peak)
			
			//if (key[KEY_ESC]) d_done = true; // emergency key :^O
		}
		
		// here update screen
		if (back_bitmap) 
			{ blit(back_bitmap, dbuf, 0,0,0,0,back_bitmap->w, back_bitmap->h);}
		else
			{ clear(dbuf); }
		
		if (callback != NULL) callback(*this, false); // call the callback if needed, to update screen
		
		// Here we draw the menu
		msd = mis - mhi/2 - 1; // menu start drawing from here
		if (msd < 0) msd = 0;
		med = msd + mhi; // menu drawing ends here
		if ((unsigned)med > menu_item_text.size()) med = menu_item_text.size();
		mdy = my;
		for (int i = msd; i < med; i ++)
		{
			if (i == mis) // is selected or unselected item?
			{
				text_mode(menu_bg_color_s);
				switch (text_align) // wich text align to use?
				{
					case 1:
						textout_right(dbuf, menu_font_s, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_s);
					break;
					case 2:
						textout_centre(dbuf, menu_font_s, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_s);
					break;
					case 3:
						textout_justify(dbuf, menu_font_s, menu_item_text[i].c_str(), mx,mx+mw, mdy, mw, menu_fg_color_s);
					break;
					default:
						textout(dbuf, menu_font_s, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_s);
					break;
				}
				mdy += text_height(menu_font_s);
			}
			else
			{
				text_mode(menu_bg_color_ns);
				switch (text_align)
				{
					case 1:
						textout_right(dbuf, menu_font_ns, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_ns);
					break;
					case 2:
						textout_centre(dbuf, menu_font_ns, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_ns);
					break;
					case 3:
						textout_justify(dbuf, menu_font_ns, menu_item_text[i].c_str(), mx,mx+mw, mdy, mw, menu_fg_color_ns);
					break;
					default:
						textout(dbuf, menu_font_ns, menu_item_text[i].c_str(), mx, mdy, menu_fg_color_ns);
					break;
				}
				mdy += text_height(menu_font_ns);
			}
			mdy += item_y_separation;
		}
		
		// show it
		blit(dbuf, to_bitmap, 0,0,0,0, dbuf->w, dbuf->h);
	} // end of while !d_done

	// ------------- End of main loop -------
	menu_manager_stop_global_timer(); // we finish the timer 
	text_mode(-1);

	clear_keybuf();
	rest(25);
	clear_keybuf();
	
	return mis; // return menu item selected
}

// --------------------------------------------------------
// Set functions
// --------------------------------------------------------

void CQMenu::set_font_s(FONT *f) { menu_font_s = f; }
void CQMenu::set_font_ns(FONT *f) { menu_font_ns = f; }
void CQMenu::set_fg_color_ns(int fg) { menu_fg_color_ns = fg; }
void CQMenu::set_bg_color_ns(int bg) { menu_bg_color_ns = bg; }
void CQMenu::set_fg_color_s(int fg) { menu_fg_color_s = fg; }
void CQMenu::set_bg_color_s(int bg) { menu_bg_color_s = bg; }

void CQMenu::set_xywh(int x, int y, int w, int h) { mx = x; my = y; mw = w; mh = h; }

void CQMenu::set_to_bitmap(BITMAP *b) { to_bitmap = b; }
void CQMenu::set_back_bitmap(BITMAP *b) {back_bitmap = b; }

void CQMenu::set_callback_drawer(void (*c)(CQMenu &d, bool do_logic))
{
	callback = c;	
}

// --------------------------------------------------------
// Get functions
// --------------------------------------------------------
FONT *CQMenu::get_font_s() { return menu_font_s; }
FONT *CQMenu::get_font_ns(){ return menu_font_ns; }
int CQMenu::get_fg_color_ns() { return menu_fg_color_ns; }
int CQMenu::get_bg_color_ns() { return menu_bg_color_ns; }
int CQMenu::get_fg_color_s() { return menu_fg_color_s; }
int CQMenu::get_bg_color_s() { return menu_bg_color_s; }
int CQMenu::get_menu_item_count() { return menu_item_text.size(); }
int CQMenu::get_x() { return mx; }
int CQMenu::get_y() { return my; }
int CQMenu::get_w() { return mw; }
int CQMenu::get_h() { return mh; }
BITMAP *CQMenu::get_to_bitmap() { return to_bitmap; }
BITMAP *CQMenu::get_back_bitmap() { return back_bitmap; }

string CQMenu::get_menu_item_text(int item_index)
{
	if (item_index < 0 || (unsigned)item_index > menu_item_text.size()-1) return menu_item_text[0];
	return menu_item_text[item_index];	
} 

// --------------------------------------------------------
// Small timer, goes in range 0 to BPS_OF_MENU_MANAGER
// --------------------------------------------------------
int CQMenu::get_time_counter()
{
	return time_counter;
}

// --------------------------------------------------------
// Big timer, goes in range 0 to ULONG_MAX
// --------------------------------------------------------
unsigned long int CQMenu::get_big_timer_counter()
{
	return big_timer_counter;
}

