/*
   ui/ui.cc
   This file is part of the Osirion project and is distributed under
   the terms of the GNU General Public License version 2
*/

#include <string>
#include <sstream>

#include "audio/audio.h"
#include "auxiliary/functions.h"
#include "core/core.h"
#include "core/application.h"
#include "filesystem/filesystem.h"
#include "render/gl.h"
#include "sys/sys.h"
#include "ui/button.h"
#include "ui/label.h"
#include "ui/menu.h"
#include "ui/paint.h"
#include "ui/ui.h"
#include "ui/widget.h"

namespace ui
{

/* -- static functions --------------------------------------------- */

bool UI::ui_debug = false;

float UI::elementmargin = 16;
math::Vector2f UI::elementsize(256, 48);

UI *global_ui = 0;

UI *root()
{
	return global_ui;
}

void init()
{
	con_print << "^BInitializing user interface..." << std::endl;
	
	if (!global_ui) {
		global_ui = new UI();
	} else {
		con_warn << "User interface already initialized!" << std::endl;
		return;
	}

	global_ui->load_menus();
	global_ui->load_settings();	
}

void shutdown()
{
	con_print << "^BShutting down user interface..." << std::endl;

	if (global_ui) {
		delete global_ui;
		global_ui = 0;
	}
}

/* -- class UI ----------------------------------------------------- */

UI::UI() : Window(0)
{
	set_label("root");
	set_size(1024, 768);
	set_border(false);
	set_background(false);
	
	// intialize console
	ui_console = new Console(this);

	// default palette
	ui_palette = new Palette();
	set_palette(ui_palette);
	
	// default fonts
	ui_font_small = new Font("gui", 12, 18);
	ui_font_large = new Font("gui", 14, 24);
	set_font(ui_font_small);
	
	ui_mouse_focus = this;
	ui_input_focus = this;
	set_focus();

	mouse_pointer_color = Palette::Pointer;
	mouse_pointer_bitmap.assign("pointer");
}

UI::~UI()
{
	delete ui_palette;
	
	delete ui_font_small;
	delete ui_font_large;
}

void UI::load_menus()
{
	ui_active_menu = 0;

	Menus::iterator it;
	for (it = ui_menus.begin(); it != ui_menus.end(); it++) {
		Window *menu = (*it);
		remove_child(menu);
	}
	ui_menus.clear();

	std::string filename("menu");
	filesystem::IniFile ini;
	ini.open(filename);
	if (!ini.is_open()) {
		con_error << "Could not open " << ini.name() << std::endl;
	} else {
		std::string strval;
		Button *button = 0;
		Label *label = 0;
		Menu *menu = 0;
		
		while (ini.getline()) {
			if (ini.got_section()) {
				if (ini.got_section("menu")) {
					menu = new Menu(this);
					add_menu(menu);
					continue;
				} else if (menu) {
					if (ini.got_section("button")) {
						button = menu->add_button();
						
					} else if (ini.got_section("label")) {
						label = menu->add_label();
						
					} else {
						ini.unknown_section();
					}
				} else {
					ini.unknown_section();
				}
			} else if (menu && ini.got_key()) {
				if (ini.in_section("menu")) {
					if (ini.got_key_string("label", strval)) {
						aux::to_label(strval);
						menu->set_label(strval);
					} else if (ini.got_key_string("background", strval)) {
						menu->set_background_texture(strval);
					} else {
						ini.unkown_key();
					}
				} else if (ini.in_section("button")) {
					if (ini.got_key_string("text", strval)) {
						aux::strip_quotes(strval);
						button->set_text(strval);
						
					} else if (ini.got_key_string("command", strval)) {
						for (size_t i =0; i <= strval.size(); i++) {
							if (strval[i] == ',') strval[i] = ';';
						}
						aux::strip_quotes(strval);
						button->set_command(strval);
						
					} else if (ini.got_key_string("align", strval)) {
						aux::to_label(strval);
						if (strval.compare("left") == 0) {
							button->set_alignment(AlignLeft | AlignVCenter);
						} else if (strval.compare("center") == 0) {
							button->set_alignment(AlignCenter);
						} else if (strval.compare("right") == 0) {
							button->set_alignment(AlignRight | AlignVCenter);
						} else {
							ini.unknown_value();
						}
					} else {
						ini.unkown_key();
					}
				} else if (ini.in_section("label")) {
					if (ini.got_key_string("text", strval)) {
						label->set_text(strval);
					} else if (ini.got_key_string("align", strval)) {
						aux::to_label(strval);
						if (strval.compare("left") == 0) {
							label->set_alignment(AlignLeft | AlignHCenter);
						} else if (strval.compare("center") == 0) {
							label->set_alignment(AlignCenter);
						} else if (strval.compare("right") == 0) {
							label->set_alignment(AlignRight | AlignHCenter);
						} else {
							ini.unknown_value();
						}
					} else {
						ini.unkown_key();
					}
				}
				
			}
		}

		con_debug << "  " << ini.name() << " " << ui_menus.size() << " menus" << std::endl;
		ini.close();
	}

	// fallback main menu
	if (!find_menu("main")) {
		con_warn << "menu 'main' not found, using default" << std::endl;
		Menu *menu = new Menu(this, "main");
		menu->add_label("Main Menu");
		menu->add_button("Connect", "connect");
		menu->add_button("Quit", "quit");
	}
	
	// fallback game menu
	if (!find_menu("game")) {
		con_warn << "menu 'game' not found, using default" << std::endl;
		Menu *menu = new Menu(this, "game");
		menu->add_label("Game Menu");
		menu->add_button("Disconnect", "disconnect");
		menu->add_button("Quit", "quit");
	}

	// fallback join menu
	if (!find_menu("join")) {
		con_warn << "menu 'join' not found, using default" << std::endl;
		Menu *menu = new Menu(this, "join");
		menu->add_label("Join Menu");
		menu->add_button("Join", "join; menu hide");
		menu->add_button("Game menu", "menu game");
	}
}


void UI::load_settings()
{

	ui_mouse_focus = this;
	
	std::string filename("ui");
	filesystem::IniFile ini;
	ini.open(filename);
	
	if (!ini.is_open()) {
		con_error << "Could not open " << ini.name() << std::endl;
		return;
	}
	
	std::string strval;
	math::Color color;

	float w = elementsize.width();
	float h = elementsize.height();
	float m = elementmargin;

	
	while (ini.getline()) {
	
		if (ini.got_section()) {
			if (ini.got_section("ui")) {
				continue;
				
			} else if (ini.got_section("colors")) {
				continue;
				
			} else if (ini.got_section("hud")) {
				continue;

			} else if (ini.got_section("text")) {
				continue;

			} else {
				ini.unknown_section();
				continue;
			}
			
		} else if (ini.got_key()) {
		
			if (ini.in_section("ui")) {
				if (ini.got_key_float("elementwidth", w)) {
					elementsize.assign(w, h);
					continue;
				} else if (ini.got_key_float("elementheight", h)) {
					elementsize.assign(w, h);
					continue;
				} else if (ini.got_key_float("elementmargin", m)) {
					elementmargin = m;
					continue;
				} else {
					ini.unkown_key();
					continue;
				}
				
			} else if (ini.in_section("colors")) {
			
				if (ini.got_key_color("foreground", color)) {
					ui_palette->set_foreground(color);
					continue;
				} else if (ini.got_key_color("background", color)) {
					ui_palette->set_background(color);
					continue;
				} else if (ini.got_key_color("border", color)) {
					ui_palette->set_border(color);
					continue;
				} else if (ini.got_key_color("text", color)) {
					ui_palette->set_text(color);
					continue;
				} else if (ini.got_key_color("highlight", color)) {
					ui_palette->set_highlight(color);
					continue;
				} else if (ini.got_key_color("disabled", color)) {
					ui_palette->set_disabled(color);
					continue;
				} else if (ini.got_key_color("pointer", color)) {
					ui_palette->set_pointer(color);
					continue;
				} else if (ini.got_key_color("active", color)) {
					ui_palette->set_active(color);
					continue;
				} else if (ini.got_key_color("debug", color)) {
					ui_palette->set_debug(color);
					continue;
				} else {
					ini.unkown_key();
					continue;
				}

			} else if (ini.in_section("hud")) {

				if (ini.got_key_color("mission", color)) {
					ui_palette->set_mission(color);
					continue;
				} else {
					ini.unkown_key();
					continue;
				}

			} else if (ini.in_section("text")) {

				if (ini.got_key_color("bold", color)) {
					ui_palette->set_bold(color);

				} else if (ini.got_key_color("fancy", color)) {
					ui_palette->set_fancy(color);

				} else if (ini.got_key_color("warning", color)) {
					ui_palette->set_warning(color);

				} else if (ini.got_key_color("error", color)) {
					ui_palette->set_error(color);
				}
			}
		}
	}
	
	ini.close();
}

void UI::apply_render_options()
{
	con_debug << "  initializing text colors" << std::endl;
	// apply palette colors
	paint::assign_color('N', palette()->text());
	paint::assign_color('D', palette()->debug());
	paint::assign_color('B', palette()->bold());
	paint::assign_color('F', palette()->fancy());
	paint::assign_color('W', palette()->warning());
	paint::assign_color('E', palette()->error());

}

void UI::list() const
{
	size_t n = Widget::list(0);
	con_print << n << " user interface widgets" << std::endl;
}

void UI::list_visible() const
{
	size_t n = Widget::list(0, true);
	con_print << n << " visible user interface widgets" << std::endl;
}

UI::Menus::iterator UI::find_menu(Window *menu)
{
	Menus::iterator it;
	for (it = ui_menus.begin(); it != ui_menus.end(); it++) {
		if ((*it) == menu)
			return it;
	}
	
	return it;
}

Window *UI::find_menu(const char *label)
{
	for (Menus::const_iterator it = ui_menus.begin(); it != ui_menus.end(); it++) {
		if ((*it)->label().compare(label) == 0) {
			return (*it);
		}
	}

	return 0;
}

void UI::list_menus() const
{
	for (Menus::const_iterator it = ui_menus.begin(); it != ui_menus.end(); it++) {
		const Window *menu = (*it);
		con_print << "  " <<  menu->label() << std::endl;
	}
	con_print << ui_menus.size()  << " menus" << std::endl;
}

void UI::add_menu(Window *menu)
{
	Menus::iterator it = find_menu(menu);
	if (it == ui_menus.end()) {
		ui_menus.push_back(menu);
	}
	
}

void UI::show_menu(const char *label)
{
	Window *menu = find_menu(label);
	
	if (menu) {
		if (ui_active_menu) {
			ui_active_menu->hide();
			menu->set_previous(ui_active_menu);
		} else {
			menu->clear_previous();
		}
		ui_mouse_focus = this;
		ui_input_focus = this;

		ui_active_menu = menu;
		ui_active_menu->event_resize();
		ui_active_menu->show();
		
		set_pointer("pointer");

		// raise console if it is visible
		if (ui_console->visible())
			ui_console->show();

	} else {
		con_warn << "Unknown window '" << label << "'" << std::endl;
	}
}

void UI::hide_menu()
{
	if (ui_active_menu) {
		ui_active_menu->hide();
		ui_active_menu = 0;
	}
}

void UI::previous_menu()
{
	if (ui_active_menu) {
		if (ui_active_menu->previous().size()) {
			show_menu(ui_active_menu->previous().c_str());
		} else {
			hide_menu();
		}
	}
}

void UI::frame()
{
	if (ui_active_menu && !ui_active_menu->visible()) {
		ui_active_menu = 0;
	}

	ui_input_focus = find_input_focus();
	Widget *f = find_mouse_focus(mouse_cursor);
	if (f) {
		f->event_mouse(mouse_cursor);
	}
	ui_mouse_focus = f;

	event_draw();

	if (visible())
		draw_pointer();
}

/* -- global event handlers ---------------------------------------- */
/*
	These functions receive input events from the client input
	subsystem and distributes them to the widget hierarchy
*/
void UI::input_mouse(const float x, const float y)
{
	mouse_cursor.assign(x, y);
}

bool UI::input_key(const bool pressed, const int key, const unsigned int modifier)
{
	bool handled = false;
	if (key < 512) {
		// keyboard keys
		Widget *f = find_input_focus();
		if (f) {
			handled = f->event_key(pressed, key, modifier);
		}
		ui_input_focus = f;
	} else {
		// mouse buttons
		Widget *f = find_mouse_focus(mouse_cursor);
		if (f) {
			f->event_mouse(mouse_cursor);
		}
		ui_mouse_focus = f;

		if (ui_mouse_focus)
			handled = ui_mouse_focus->event_key(pressed, key, modifier);
	}
	return handled;
}

bool UI::on_keypress(const int key, const unsigned int modifier)
{
	switch( key ) {
	
	case SDLK_ESCAPE:
		if (active()) {
			hide_menu();
			audio::play("ui/menu");
		}		
		return true;
		break;
	default:
		break;
	}

	return false;
}

bool UI::on_keyrelease(const int key, const unsigned int modifier)
{
	return false;
}

void UI::set_pointer(const char *pointerbitmap, const Palette::Color color, const bool animated)
{
	if (!pointerbitmap) {
		mouse_pointer_bitmap.clear();
	} else {
		mouse_pointer_bitmap.assign(pointerbitmap);
	}
	mouse_pointer_animated = animated;
	mouse_pointer_color = color;
}

void UI::draw_pointer()
{
	if (!mouse_pointer_bitmap.size())
		return;

	math::Color c(palette()->color(mouse_pointer_color));
	if (mouse_pointer_animated) {
		c.a = 1;
	} else {
		c.a = 0.5f;
	}
	paint::color(c);
	math::Vector2f pos(mouse_cursor.x - pointer_size * 0.5f, mouse_cursor.y - pointer_size * 0.5f);
	math::Vector2f s(pointer_size, pointer_size);
	
	std::string texture("pointers/");
	texture.append(mouse_pointer_bitmap);

	if (mouse_pointer_animated) {
		gl::push();
		gl::translate(mouse_cursor.x, mouse_cursor.y, 0);

		float angle = core::application()->time()* 0.75f - floorf(core::application()->time() * 0.75f);
		angle *= 360.0f;
		gl::rotate(angle, math::Vector3f(0, 0, 1.0f));
		gl::translate(-mouse_cursor.x, -mouse_cursor.y, 0);
	}

	paint::bitmap(pos, s, texture);

	if (mouse_pointer_animated) {
		gl::pop();
	}
}

}