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

#include <SDL/SDL.h>

#include <iostream>
#include <iomanip>
#include <cmath>
#include <cstdlib>

#include "audio/audio.h"
#include "audio/sources.h"
#include "client/client.h"
#include "client/video.h"
#include "client/input.h"
#include "client/soundext.h"
#include "core/core.h"
#include "core/loader.h"
#include "core/zone.h"
#include "render/render.h"
#include "ui/ui.h"

namespace client
{

core::Cvar *cl_framerate = 0;
core::Cvar *cl_frametime = 0;

Client app;

//--- public ------------------------------------------------------


Client *client()
{
	return &app;
}

void run(int count, char **arguments)
{
	std::cout << core::name() << " " << core::version() << std::endl;

	for (int i = 0; i < count; i++)
		std::cout << arguments[i] << " ";
	std::cout << std::endl;

	app.init(count, arguments);
	app.run();
	app.shutdown();
}

//--- private -----------------------------------------------------

void Client::quit(int status)
{
	SDL_Quit();
	core::Application::quit(status);
}

void Client::init(int count, char **arguments)
{
	client_worldview = 0;
	con_print << "^BInitializing client..." << std::endl;

	// initialize core
	core::Cvar::sv_private = core::Cvar::set("sv_private", "0");
	core::Application::init(count, arguments);

	// engine variables
	cl_framerate = core::Cvar::get("cl_framerate", "125", core::Cvar::Archive);
	cl_framerate->set_info("[int] client framerate in frames/sec");

	// player info variables
	core::Cvar *cvar = 0;

	cvar = core::Cvar::get("cl_name", "Player", core::Cvar::Archive | core::Cvar::Info);
	cvar->set_info("[str] player name");

	cvar = core::Cvar::get("cl_color", "1.0 1.0 1.0", core::Cvar::Archive | core::Cvar::Info);
	cvar->set_info("[r g b] player primary color");

	cvar = core::Cvar::get("cl_colorsecond", "1.0 1.0 1.0", core::Cvar::Archive | core::Cvar::Info);
	cvar->set_info("[r g b] player secondary color");

	cvar = core::Cvar::get("rconpassword", "", core::Cvar::Archive | core::Cvar::Info);
	cvar->set_info("[string] password for remote console access");
	
	snd_volume = core::Cvar::get("snd_volume", "0.8", core::Cvar::Archive);
	snd_volume->set_info("[float] master volume from 0 (mute) to 1 (max volume)");
	
	snd_engines = core::Cvar::get("snd_engines", "1", core::Cvar::Archive);
	snd_engines->set_info("[bool] enable or disable engine sounds");

	snd_doppler = core::Cvar::get("snd_doppler", "0", core::Cvar::Archive);
	snd_doppler->set_info("[bool] enable or disable doppler effect");
	
	// initialize SDL, but do not initialize any subsystems
	SDL_Init(0);

	// initialize user interface
	ui::init();
	client_worldview = new WorldView(ui::root());

	// Initialize the video subsystem
	if (!video::init()) {
		quit(1);
	}

	// initialize input
	input::init();

	// initialize audio
	audio::init();

	// add engine functions
	core::Func *func = 0;

	func = core::Func::add("r_restart", Client::func_r_restart);
	func->set_info("restart render subsystem");

	func = core::Func::add("snd_restart", Client::func_snd_restart);
	func->set_info("restart audio subsystem");

	func = core::Func::add("list_ui", func_list_ui);
	func->set_info("list user interface widgets");

	func = core::Func::add("list_menu", func_list_menu);
	func->set_info("list available menus");

	func = core::Func::add("ui", func_ui);
	func->set_info("[command] user interface functions");

	func = core::Func::add("ui_restart", func_ui_restart);
	func->set_info("reload user interface files");

	func = core::Func::add("ui_console", func_ui_console);
	func->set_info("toggle console");

	func = core::Func::add("ui_chat", Client::func_ui_chat);
	func->set_info("toggle chat window");

	func = core::Func::add("ui_chatbar", Client::func_ui_chatbar);
	func->set_info("toggle chat bar");

	func = core::Func::add("ui_map", Client::func_ui_map);
	func->set_info("toggle map");

	func = core::Func::add("ui_menu", Client::func_ui_menu);
	func->set_info("toggle main menu");

	func = core::Func::add("menu", func_menu);
	func->set_info("[command] menu functions");

	func = core::Func::add("view", func_view);
	func->set_info("[command] view menu functions");

	previous_timestamp = 0;
}

void Client::run()
{
	con_print << "^BRunning client..." << std::endl;

	// seed random generator
	unsigned int seed = (unsigned int) SDL_GetTicks();
	srand(seed);

	// default framerate 125fps, 8 milliseconds
	Uint32 client_frame_lenght = 8;

	Uint32 client_current_timestamp = 0;
	Uint32 client_previous_timestamp = 0;

	while (true) {
		// current time in microseconds
		client_current_timestamp = SDL_GetTicks();

		// calculate the desired frame length
		if (cl_framerate->value() < 0) {
			(*cl_framerate) = 0.0f;
		} else if (cl_framerate->value() > 1000.0f) {
			(*cl_framerate) = 1000.0f;
		}

		if (cl_framerate->value()) {
			client_frame_lenght = (Uint32) roundf(1000.0f / cl_framerate->value());
		} else {
			client_frame_lenght = 0;
		}

		// only advance per microsecond frame
		Uint32 d = client_current_timestamp - client_previous_timestamp;
		if ((d > 0)) {
			if (d >= client_frame_lenght) {
				frame(client_current_timestamp);
				client_previous_timestamp = client_current_timestamp;
			} else {
				SDL_Delay(client_frame_lenght - d);
			}
		} else {
			SDL_Delay(1);
		}
	}

}

void Client::frame(unsigned long timestamp)
{
	input::frame();

	core::Application::frame(timestamp);

	if (!connected()) {
		std::string module_label(core::Loader::label());

		// load the intro if nothing is running
		if (load("intro")) {
			connect("");
			if (module_label.size())
				load(module_label);
		}
		// show the console if everything fails
		if (!connected() && !ui::console()->visible()) {
			ui::console()->toggle();
		}
	} else if (!ui::root()->active()) {

		// show the main menu on non-interactive modules
		if (!core::game()->interactive()) {
			ui::root()->show_menu("main");

			// show the join menu when player does not control an entity
		} else if (core::game()->time() && !core::localcontrol()) {
			ui::root()->show_menu("join");
		}
	} else {
		if (core::localcontrol()) {

			// hide join menu
			if (ui::root()->active()->label().compare("join") == 0) {
				ui::root()->hide_menu();
			}
		}
	}

	video::frame((float)(timestamp - previous_timestamp) / 1000.0f);

	previous_timestamp = timestamp;
}

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

	if (connected()) disconnect();

	core::Func::remove("r_restart");
	core::Func::remove("snd_restart");
	core::Func::remove("list_menu");
	core::Func::remove("list_ui");
	core::Func::remove("ui");
	core::Func::remove("ui_restart");
	core::Func::remove("ui_console");
	core::Func::remove("ui_chat");
	core::Func::remove("ui_chatbar");
	core::Func::remove("ui_map");
	core::Func::remove("ui_menu");
	core::Func::remove("menu");
	core::Func::remove("view");

	audio::shutdown();

	input::shutdown();

	video::shutdown();

	ui::shutdown();

	core::Application::shutdown();

	quit(0);
}

/* -- notifications from core::Application ------------------------- */

void Client::notify_connect()
{
	video::set_loader_message();
	video::frame_loader();

	worldview()->clear();
	ui::root()->hide_menu();

	video::set_caption();
}

void Client::notify_disconnect()
{
	audio::reset();
	render::reset();
	input::reset();

	worldview()->clear();

	video::set_caption();
}

void Client::notify_zonechange()
{
	video::set_loader_message();
	video::frame_loader();

	// unload entity sounds
	for (core::Entity::Registry::iterator it = core::Entity::registry().begin(); it != core::Entity::registry().end(); it++) {
		core::Entity *entity = (*it).second;

		if (ext_sound(entity))
			delete ext_sound(entity);
	}

	render::unload();
}

void Client::notify_sound(const char *name)
{
	audio::play(name);
}

void Client::notify_message(const char *message)
{
	std::string text(message);
	notify_message(core::Message::Info, text);
}

void Client::notify_message(const std::string &message)
{
	notify_message(core::Message::Info, message);
}

void Client::notify_message(const core::Message::Channel channel, const std::string &message)
{

	switch (channel) {

		case core::Message::Info:	// Info message
			break;

		case core::Message::Local:	// Chat message in the local zone
			break;

		case core::Message::RCon:	// RCon message
			break;

		case core::Message::Public:	// Public chat message
			audio::play("com/chat");
			break;

		case core::Message::Private:	// Private chat message
			audio::play("com/priv");
			break;

		default:
			break;
	}

	if (worldview()) {
		worldview()->event_text(message);
	}

	con_print << message << std::endl;
}

void Client::notify_loader(const std::string &message)
{
	video::set_loader_message(message.c_str());
}

//--- engine functions --------------------------------------------

void Client::func_snd_restart(std::string const &args)
{
	// unload entity sounds
	for (core::Entity::Registry::iterator it = core::Entity::registry().begin(); it != core::Entity::registry().end(); it++) {
		core::Entity *entity = (*it).second;

		if (ext_sound(entity))
			delete ext_sound(entity);
	}

	audio::reset();
}

void Client::func_r_restart(std::string const &args)
{
	video::restart();
	video::set_loader_message();
	video::frame_loader();
}

/* ---- func_ui ---------------------------------------------------- */

void Client::func_list_ui(std::string const &args)
{
	if (ui::root()) {
		ui::root()->list();
	}
}

void Client::func_ui_restart(std::string const &args)
{
	if (ui::root()) {
		ui::root()->load_menus();
		ui::root()->load_settings();
		ui::root()->apply_render_options();
	}
}


void Client::func_ui_console(std::string const &args)
{
	ui::console()->toggle();
}

void Client::func_list_menu(std::string const &args)
{
	if (ui::root()) {
		ui::root()->list_menus();
	}
}

void Client::func_ui_help()
{
	con_print << "^BUser interface functions" << std::endl;
	con_print << "  ui help          show this help" << std::endl;
	con_print << "  ui debug         toggle debug mode" << std::endl;
	con_print << "  ui list          list widgets" << std::endl;
	con_print << "  ui visible       list visible widgets" << std::endl;
	con_print << "  ui restart       reload user interface files" << std::endl;
}

void Client::func_ui(std::string const &args)
{
	if (!ui::root()) {
		con_warn << "User Interface not available!" << std::endl;
		return;
	}

	if (!args.size()) {
		func_ui_help();
		return;
	}
	std::stringstream argstr(args);
	std::string command;
	argstr >> command;
	aux::to_label(command);

	if (command.compare("help") == 0) {
		func_ui_help();
	} else if (command.compare("debug") == 0) {
		ui::UI::ui_debug = !ui::UI::ui_debug;
	} else if (command.compare("list") == 0) {
		ui::root()->list();
	} else if (command.compare("visible") == 0) {
		ui::root()->list_visible();
	} else if (command.compare("restart") == 0) {
		ui::root()->load_menus();
		ui::root()->load_settings();
		ui::root()->apply_render_options();
	} else {
		func_ui_help();
	}
}

void Client::func_ui_chat(std::string const &args)
{
	if (client()->connected() && client()->worldview()->playerview()->visible()) {
		client()->worldview()->playerview()->toggle_chat();
	}
}

void Client::func_ui_chatbar(std::string const &args)
{
	if (client()->connected() && client()->worldview()->playerview()->visible()) {
		client()->worldview()->playerview()->toggle_chatbar();
	}
}

void Client::func_ui_map(std::string const &args)
{
	if (client()->connected() && client()->worldview()->playerview()->visible()) {
		client()->worldview()->playerview()->toggle_map();
	}
}

void Client::func_ui_menu(std::string const &args)
{
	if (client()->connected()) {
		if (ui::root()->active()) {
			ui::root()->hide_menu();
		} else {
			// show the main menu on non-interactive modules
			if (!core::game()->interactive()) {
				ui::root()->show_menu("main");
			} else {
				ui::root()->show_menu("game");
			}
		}
	} else {
		con_print << "Not connected." << std::endl;
	}
}

// entity menus
void Client::func_view(std::string const &args)
{
	if (client()->worldview()) {
		client()->worldview()->playerview()->show_menu(args);
	}
}

// global menus
void Client::func_menu(std::string const &args)
{
	if (!ui::root()) {
		con_warn << "User Interface not available!" << std::endl;
		return;
	}

	if (!args.size()) {
		con_print << "^Bmenu functions" << std::endl;
		con_print << "  menu help          show this help" << std::endl;
		con_print << "  menu list          list available menus" << std::endl;
		con_print << "  menu [name]        show a menu" << std::endl;
		con_print << "  menu back          return to the previous menu" << std::endl;
		con_print << "  menu previous      return to the previous menu" << std::endl;
		con_print << "  menu close         close the current menu" << std::endl;
		con_print << "  menu hide          hide the current menu" << std::endl;
		ui::root()->list_menus();
	}

	std::stringstream argstr(args);
	std::string command;
	argstr >> command;

	aux::to_label(command);

	if (command.compare("hide") == 0) {
		ui::root()->hide_menu();

	} else if (command.compare("close") == 0) {
		ui::root()->hide_menu();

	} else if (command.compare("back") == 0) {
		ui::root()->previous_menu();

	} else if (command.compare("previous") == 0) {
		ui::root()->previous_menu();

	} else if (command.compare("list") == 0) {
		ui::root()->list_menus();

	} else {
		ui::root()->show_menu(command.c_str());
	}
}


} // namespace client