/*
   client/video.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 "core/core.h"
#include "core/gameserver.h"
#include "client/video.h"
#include "client/input.h"
#include "client/client.h"
#include "client/targets.h"
#include "render/render.h"
#include "render/gl.h"
#include "filesystem/filesystem.h"
#include "sys/sys.h"
#include "ui/ui.h"
#include "ui/paint.h"

#include <SDL/SDL.h>

using namespace render;

namespace client
{

/* -- engine variables --------------------------------------------- */

core::Cvar 			*r_width = 0;
core::Cvar 			*r_height = 0;
core::Cvar 			*r_windowwidth = 0;
core::Cvar 			*r_windowheight = 0;
core::Cvar 			*r_fullscreen = 0;

core::Cvar 			*draw_ui = 0;
core::Cvar 			*draw_stats = 0;
core::Cvar 			*draw_devinfo = 0;
core::Cvar 			*draw_keypress = 0;
core::Cvar 			*draw_clock = 0;

namespace video
{

float fullscreen = 0;

int bpp = 0;
int flags = 0;

int width =  0;
int height = 0;

int width_prev = 0;
int height_prev = 0;

// default resolution and window size
const int width_default = 1024;
const int height_default = 768;

std::string			loader_message;
bool				is_loading = false;

bool init()
{
	con_print << "^BInitializing video..." << std::endl;

	// initialize engine variables
	r_width = core::Cvar::get("r_width", width_default, core::Cvar::Archive);
	r_width->set_info("[int] video resolution width");

	r_height = core::Cvar::get("r_height", height_default, core::Cvar::Archive);
	r_height->set_info("[int] video resolution height");

	r_windowwidth = core::Cvar::get("r_windowwidth", width_default, core::Cvar::Archive);
	r_windowwidth->set_info("[int] window width in windowed mode");

	r_windowheight = core::Cvar::get("r_windowheight", height_default, core::Cvar::Archive);
	r_windowheight->set_info("[int] window height in windowed mode");
	
	r_fullscreen = core::Cvar::get("r_fullscreen", "0", core::Cvar::Archive);
	r_fullscreen->set_info("[bool] enable or disable fullscreen video");

	draw_devinfo = core::Cvar::get("draw_devinfo", "0", core::Cvar::Archive);
	draw_devinfo->set_info("[bool] draw developer information");

	draw_stats = core::Cvar::get("draw_stats", "0", core::Cvar::Archive);
	draw_stats->set_info("[bool] draw network and render statistics");

	draw_keypress = core::Cvar::get("draw_keypress", "0", core::Cvar::Archive);
	draw_keypress->set_info("[bool] draw keypress key names");
	
	draw_clock = core::Cvar::get("draw_clock", "0", core::Cvar::Archive);
	draw_clock->set_info("[int] draw clock (0=Off, 1=24hr, 2=12hr)");

	draw_ui = core::Cvar::get("draw_ui", "1", core::Cvar::Archive);
	draw_ui->set_info("[bool] draw the user interface");

	if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
		con_error << "SDL_InitSubSystem() failed: " << SDL_GetError() << std::endl;
		return false;
	}


	// set the window icon
	/*
	 * 	FIXME
	 * 	store the icon as binary data 
	 * 	and use SDL_CreateRGBSurfaceFrom to create the icon
	 */
	filesystem::File *iconfile = filesystem::open("bitmaps/icon.bmp");
	if (iconfile) {
		std::string iconfilename = iconfile->path();
		iconfilename.append(iconfile->name());
		filesystem::close(iconfile);
		con_debug << "  setting window icon " << iconfilename << std::endl;

		SDL_Surface *image = SDL_LoadBMP(iconfilename.c_str());
		if (image) {
			Uint32 colorkey = SDL_MapRGB(image->format, 255, 0, 255);
			SDL_SetColorKey(image, SDL_SRCCOLORKEY, colorkey);
			SDL_WM_SetIcon(image, NULL);
		}
	}

	const SDL_VideoInfo* sdl_videoinfo = SDL_GetVideoInfo();
	if (!sdl_videoinfo) {
		con_error << "SDL_GetVideoInfo() failed: " << SDL_GetError() << std::endl;
		return false;
	}

	bpp = sdl_videoinfo->vfmt->BitsPerPixel;
	if (bpp == 32) {
		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
		SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
	} else if (bpp == 24) {
		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 6);
		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 6);
		SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 6);
	} else if (bpp == 16) {
		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 4);
		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 4);
		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 4);
		SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 4);
	} else {
		con_warn << "Display depth " << bpp << " is not supported!" << std::endl;
	}

	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	
	width_prev = width;
	height_prev = height;

	width = (int) r_width->value();
	height = (int) r_height->value();
	
	fullscreen = r_fullscreen->value();
	
	if (r_fullscreen->value()) {
		flags = SDL_OPENGL | SDL_FULLSCREEN;
	} else  {
		if (r_windowwidth->value() && r_windowheight->value()) {
			width = (int) r_windowwidth->value();
			height = (int) r_windowheight->value();
		}
		flags = SDL_OPENGL;
#ifndef _WIN32
		flags |= SDL_RESIZABLE;
#endif
	}

	if (!SDL_SetVideoMode(width, height, bpp, flags)) {
		con_warn << "Failed to set video mode " << width << "x" << height << "x" << bpp << "bpp" << std::endl;
		if (width_prev && height_prev) {
			width = width_prev;
			height = height_prev;
			if (!SDL_SetVideoMode(width, height, bpp, flags)) {
				con_error << "Failed to restore video mode " << width << "x" << height << "x" << bpp << "bpp" << std::endl;
				return false;
			}
		} else
			return false;
	}
	con_print << "  video mode " << width << "x" << height << "x" << bpp << "bpp " << (fullscreen ? "fullscreen " : "window") << std::endl;

#ifdef HAVE_DEBUG_MESSAGES

	int red, green, blue, alpha, depth;

	SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &red);
	SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &green);
	SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &blue);
	SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &alpha);
	SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depth);

	con_debug << "  visual r: " << red << " g: " << green << " blue: " << blue << " alpha: " << alpha << " depth: " << depth << std::endl;

#endif // HAVE_DEBUG_MESSAGES

	// set window caption
	set_caption();

	// save window width and height
	if (fullscreen) {
		(*r_width) = width;
		(*r_height) = height;
	} else {
		(*r_windowwidth) = width;
		(*r_windowheight) = height;
		
	}
	// resize user interface
	ui::root()->set_size((float) width, (float) height);
	ui::root()->event_resize();

	// to grab or not to grab
	core::Cvar *input_grab = core::Cvar::find("input_grab");
	if (ui::console()->visible() || (input_grab && input_grab->value())) {
		SDL_WM_GrabInput(SDL_GRAB_OFF);
		SDL_ShowCursor(SDL_ENABLE);
	} else {
		SDL_WM_GrabInput(SDL_GRAB_ON);
		SDL_ShowCursor(SDL_DISABLE);
	}

	// initialize renderer
	render::init(width,  height);
	
	// apply render options
	ui::root()->load_settings();

	// initialize target drawer
	targets::init();

	return true;
}

void set_caption()
{
	// set window caption
	std::string window_title(core::name());
	window_title += ' ';
	window_title.append(core::version());

	SDL_WM_SetCaption(window_title.c_str(), core::name().c_str());
}

void resize(int w, int h)
{
	if (fullscreen)
		return;

	if (w < 320) w = 320;
	if (h < 200) h = 200;

	if (SDL_SetVideoMode(w, h, bpp, flags)) {
		render::resize(w,  h);
		ui::root()->set_size(w, h);
		ui::root()->event_resize();
	} else {
		con_warn << "Could not resize window!" << std::endl;
	}
}

void restart()
{
	shutdown();
	
	// clear models and materials	
	/* resetting the rednder subsystem will force a reload of all materials
	 */
	model::Model::clear();
	
	model::Material::clear();
		
	if (!init()) {
		client()->quit(1);
	}
	
	model::Material::init();

	render::load();
	
	input::reset();
}

void set_cursor()
{
	if (ui::console()->visible()) {
		ui::root()->set_pointer();

	} else if (core::localplayer()->view() || ui::root()->active()) {

		ui::root()->set_pointer("pointer");

	} else if (!core::localcontrol()) {

		ui::root()->set_pointer();

	} else if (client()->worldview()->playerview()->map()->hover()) {

		ui::root()->set_pointer("pointer");

	} else if (render::Camera::mode() == render::Camera::Overview) {

		ui::root()->set_pointer("aim");

	} else if (targets::hover()) {

		ui::root()->set_pointer("target", ui::Palette::Active, true);

		if (input::joystick_lastmoved_time() > input::mouse_lastmoved_time()) {
			ui::root()->input_mouse(render::State::width() / 2, render::State::height() / 2);
		}

	} else if (input::mouse_control) {

		ui::root()->set_pointer("control", ui::Palette::Pointer);

	} else 	if ((input::joystick_lastmoved_time() > input::mouse_lastmoved_time()) &&
			(render::Camera::mode() == render::Camera::Cockpit || render::Camera::mode() == render::Camera::Track)) {

		ui::root()->set_pointer();

	} else {

		ui::root()->set_pointer("aim", ui::Palette::Foreground);
	}
}

void set_loader_message(const std::string message)
{
	loader_message.assign(message);

	if (is_loading)
		frame_loader();
}

void set_loader_message(const char *message)
{
	if (message)
		loader_message.assign(message);
	else
		loader_message.clear();

	if (is_loading)
		frame_loader();
}

void draw_loader()
{
	render::Camera::ortho();

	gl::enable(GL_BLEND);

	gl::color(1.0f, 1.0f, 1.0f, 1.0f);
	math::Vector2f pos;
	math::Vector2f size(render::State::width(), render::State::height());
	ui::Paint::draw_bitmap(pos, size, "bitmaps/loader");

	if (loader_message.size()) {
		using render::Text;
		gl::enable(GL_TEXTURE_2D);
		Text::setfont("gui", 12, 18);
		Text::setcolor('N'); //set normal color
		Text::draw(Text::fontwidth(), Text::fontheight(), loader_message);
		gl::disable(GL_TEXTURE_2D);
	}

	gl::disable(GL_BLEND);

	is_loading = true;
}

void frame_loader()
{
	// Clear the color and depth buffers.
	gl::clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	draw_loader();

	SDL_GL_SwapBuffers();
}

void frame(float elapsed)
{
	// detect fullscreen/windowed mode switch
	if (fullscreen != r_fullscreen->value())
		restart();

	using namespace render;

	is_loading = false;

	// Clear the color and depth buffers.
	gl::clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	render::Stats::clear();
	if (core::application()->connected()) {

		if (core::game()->time() && core::localplayer()->zone()) {
			render::Camera::frame(elapsed);
			render::Camera::frustum();

			render::draw(elapsed);		// draw the world
			targets::frame();		// validate current target, render sound

			if (!core::localplayer()->view() && targets::current())		// draw target docks etc
				render::draw_target(targets::current());

			render::Camera::ortho();
			client()->worldview()->show();

		} else {
			draw_loader();
			client()->worldview()->hide();
		}

	} else {
		client()->worldview()->hide();
		render::Camera::ortho();
	}

	gl::color(1.0f, 1.0f, 1.0f, 1.0f);

	gl::disable(GL_TEXTURE_2D);
	gl::enable(GL_BLEND);

	// draw the user interface
	if (draw_ui->value()) {

		set_cursor();
		ui::root()->frame();

	} else 	if (ui::console()->visible()) {

		ui::console()->event_draw();
	}

	gl::disable(GL_TEXTURE_2D);
	gl::disable(GL_BLEND);

	SDL_GL_SwapBuffers();
}

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

	targets::shutdown();

	render::shutdown();

	width = 0;
	height = 0;

	SDL_QuitSubSystem(SDL_INIT_VIDEO);
}

} // namespace video

} // namespace client