#include "ViewBase.h"

#include "ModelBase.h"
#include "ControllerBase.h"
#include "CameraBase.h"
#include "OverlayBase.h"

#include "SimpleConsoleOverlay.h"
#include "OGLFT.h"

#include <GL/gl.h>
#include <GL/glu.h>

#include "DrawingsGL.h"

using namespace std;

namespace Engine {

static ViewBase* ViewInstance = NULL;

void InitGL () {
	glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
	glClearDepth(1.0);
	glDepthFunc(GL_LEQUAL);
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_SMOOTH);
	glEnable (GL_CULL_FACE);
	glDisable (GL_FOG);

	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity ();
}

/*
 * Inherited Module functions
 */
int ViewBase::OnInit (int argc, char* argv[]) {
	LogMessage ("View Init");

	mWindowHeight = VIEW_DEFAULT_HEIGHT;
	mWindowWidth = VIEW_DEFAULT_WIDTH;

	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

	if( SDL_SetVideoMode( mWindowWidth, mWindowHeight, 16, SDL_OPENGL | SDL_RESIZABLE ) == 0 ) {
		LogError ("Video mode set failed: %s", SDL_GetError ());
		exit (-1);
	}

	InitGL ();
	Resize (mWindowWidth, mWindowHeight);
	
	LoadFont ("console.ttf");
	mCurrentFont = mFonts["console.ttf"];

	// Overlays
	OverlayBasePtr console_overlay(new SimpleConsoleOverlay);
	mOverlayManager.Register (console_overlay, 0);
	//AddOverlay (console_overlay);

	mDrawGrid = false;
	mGridSizeX = 8;
	mGridSizeZ = 8;

	ViewInstance = this;

	return 0;
}

void ViewBase::OnDestroy () {
	std::map<std::string, OGLFT::Monochrome*>::iterator iter;

	for (iter = mFonts.begin(); iter != mFonts.end(); ++iter) {
		delete iter->second;
	}
	mFonts.clear();

	ViewInstance = NULL;

	LogDebug ("View Destroy");
}

void ViewBase::CalcWorldCoordinates (int screen_x, int screen_y, float world_y, float *pos_out) {
	GLdouble modelMatrix[16], projMatrix[16];
	GLint viewport[4];
	GLdouble wx, wy, wz;

	glGetIntegerv (GL_VIEWPORT, viewport);
	glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);

	int realy = viewport[3] - screen_y - 1;

	gluUnProject ((GLdouble) screen_x, (GLdouble) realy, 1.,
			modelMatrix, projMatrix, viewport, &wx, &wy, &wz);

	GLdouble t;
	GLdouble d[3];
	float	eye[3];

	mCamera->GetEye (&eye[0]);

	d[0] = wx - eye[0];
	d[1] = wy - eye[1];
	d[2] = wz - eye[2];

	assert (fabs (d[1]) >= 1.0e-3);
	t = -eye[1]/d[1]  + world_y;

	pos_out[0] = eye[0] + t * d[0];
	pos_out[1] = eye[1] + t * d[1];
	pos_out[2] = eye[2] + t * d[2];
}

/*
 * Module specific functions
 */
void ViewBase::UpdateCamera () {
	EntityPhysicState* player_ent = GetEntityPhysicState (GetPlayerEntityId());

	if (!player_ent) {
		LogError ("Could not call Model::PositionCamera(): player entity not found!");
		exit (-1);
	}
	vector3d entity_camera_distance (-2, 3, 0);
	vector3d entity_position = player_ent->GetPosition();
	player_ent->Globalize (entity_camera_distance);

	mCamera->SetEye (
			entity_camera_distance[0],
			entity_camera_distance[1],
			entity_camera_distance[2]
			);
	mCamera->SetPointOfIntrest (
			entity_position[0],
			entity_position[1],
			entity_position[2]
			);

	mCamera->Update ();
}

void ViewBase::PreDraw() {
	// Clear the screen
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// update the frame rate counter
	static Uint32 this_frame_ticks;
	static Uint32 last_frame_ticks = 0;
	static Uint32 last_fps_update = 0;
	static int frame_counter = 0;

  this_frame_ticks = SDL_GetTicks ();
	last_fps_update += this_frame_ticks - last_frame_ticks;
	last_frame_ticks = this_frame_ticks;
	frame_counter++;

	if (last_fps_update > 1000) {
		mFrameRate = frame_counter;
		last_fps_update = 0;
		frame_counter = 0;
	}
}

void ViewBase::Draw () {
	// Perform pre-Draw actions
	PreDraw ();

	// Actual Drawing
	UpdateCamera ();

	if (mDrawGrid)
		DrawGrid ();

	DrawWorld ();

	mOverlayManager.Draw();

	// Perform post-Draw actions
	PostDraw();
}

void ViewBase::PostDraw() {
	SDL_GL_SwapBuffers ();
}

/* Fonts */

/** \brief Parses font specifications strings into its values
 *
 * Fonts can be specified by strings such as
 *   name=default.ttf color=#ff0000 size=12
 *   color=#ff00ff size=12 otherfont.ttf
 *   myfont.ttf
 * for which the appropriate values will be parsed. Default size is 12 and
 * default color is white (#ffffff)
 */
void parse_font_spec_string (const std::string &spec_string,
		std::string &font_name,
		float font_color[3],
		float *font_size) {
	// set default values
	font_name = "default.ttf";
	font_color[0] = 1.f;
	font_color[1] = 1.f;
	font_color[2] = 1.f;
	*font_size = 12.f;

	// perform the actual parsing
	std::string::size_type pos = 0;

	while (pos < spec_string.size()) {
		std::string::size_type next_start = spec_string.find_first_not_of(" \t", pos);
		// if we don't find anything we break out of this loop
		if (next_start == std::string::npos)
			break;

		// find the next token
		std::string::size_type next_end = spec_string.find_first_of("\t ", next_start);
		std::string token (spec_string.substr(next_start, next_end - next_start));

		// If no id was given it is automatically set to name
		std::string id("name");
		std::string value(token);

		// Split the token into id and value if there is an '=' sign.
		if (token.find('=', 0) != std::string::npos) {
			id = token.substr(0, token.find('=',0));
			value = token.substr(id.size() + 1, token.size());
		}

		// std::cout << "id = " << id << " value = " << value << std::endl;

		if (id == "name")
			font_name = value;
		else if (id == "size") {
			std::istringstream istr(value);
			istr >> *font_size;	
		} else if (id == "color") {
			// we assume #RRGGBB specification
			if (value[0] != '#' || value.size() != 7)
				LogError ("Invalid font color specification '%s'. Please specify as #rrggbb!", value.c_str());

			// convert the color string to float values
			int num;
			std::istringstream istr(value.substr(1, 2));
			istr >> std::hex >> num;
			font_color[0] = static_cast<float>(num) / 255.;

			istr.clear();
			istr.str(value.substr(3,2));
			istr >> std::hex >> num;
			font_color[1] = static_cast<float>(num) / 255.;

			istr.clear();
			istr.str(value.substr(5,2));
			istr >> std::hex >> num;
			font_color[2] = static_cast<float>(num) / 255.;
		}

		pos = next_end;
	}
}

bool ViewBase::LoadFont (const std::string &font_spec_string) {
	// font_spec_string can be of the form
	// (name=)<name> (color=<color>) (size=<size>)

	std::string font_name;
	float font_size = 12.;
	float font_color[3] = {1., 1., 1.};

	parse_font_spec_string(font_spec_string, font_name, font_color, &font_size);	

	std::string font_path ("./data/fonts/");
	font_path += font_name;

	LogDebug ("Loading font %s color (%1.2f, %1.2f, %1.2f) size %f from %s", font_name.c_str(), font_color[0], font_color[1], font_color[2], font_size, font_path.c_str());

	OGLFT::Monochrome *font = new OGLFT::Monochrome (font_path.c_str(), font_size);
	if ( font == 0 || !font->isValid() ) {
		LogError ("Could not load font %s!", font_path.c_str());
		return false;
	}

	font->setForegroundColor(font_color[0], font_color[1], font_color[2]);

	mFonts.insert(std::make_pair<std::string, OGLFT::Monochrome*>(font_spec_string, font));

	return true;
}

void ViewBase::SelectFont (const char *font) {
	std::map<std::string, OGLFT::Monochrome*>::iterator font_iter;

	font_iter = mFonts.find(font);

	if (font_iter != mFonts.end()) {
		mCurrentFont = font_iter->second;
		return;
	}

	LogDebug ("Selecting font %s failed, trying to load it", font);
	LogDebug ("font count = %d", mFonts.size());
	if (LoadFont (font)) {
	LogDebug ("font count = %d", mFonts.size());
		font_iter = mFonts.find(font);

		assert (mFonts.find(font) != mFonts.end());

		mCurrentFont = font_iter->second;

		return;
	}

	LogError("Error trying to load font %s", font);
}

void ViewBase::SetFontJustification (FontJustification justification) {
	assert (mCurrentFont != NULL);

	if (justification == FontJustificationRight)
		mCurrentFont->setHorizontalJustification(OGLFT::Face::RIGHT);
	else if (justification == FontJustificationCenter) 
		mCurrentFont->setHorizontalJustification(OGLFT::Face::CENTER);
	else mCurrentFont->setHorizontalJustification(OGLFT::Face::LEFT);

}

void ViewBase::DrawGLString (float x, float y, const char* str) {
	glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
	mCurrentFont->draw (x, y, str);
}

void ViewBase::DrawGLStringMeasure (const char* str, float *width, float *height) {
	OGLFT::BBox bbox = mCurrentFont->measure (str);
	*width = bbox.x_max_ - bbox.x_min_;
	*height = bbox.y_max_ - bbox.y_min_;
//	LogDebug ("measure bbox '%s' = %f,%f %f,%f",str, bbox.x_min_, bbox.y_min_, bbox.x_max_, bbox.y_max_);
}

void ViewBase::GetCamereEye (float *eye_out) {
	assert (mCamera);
	mCamera->GetEye (eye_out);
}

void ViewBase::DrawGrid () {
	float xmin, xmax, xstep, zmin, zmax, zstep;
	int i, count_x, count_z;

	xmin = -mGridSizeX;
	xmax = mGridSizeX;
	zmin = -mGridSizeZ;
	zmax = mGridSizeZ;

	count_x = mGridSizeX * 2;
	count_z = mGridSizeZ * 2;

	xstep = 1.;
	zstep = 1.;

	glColor3f (1., 1., 1.);
	glBegin (GL_LINES);
	for (i = 0; i <= count_x; i++) {
		glVertex3f (i * xstep + xmin, 0., zmin);
		glVertex3f (i * xstep + xmin, 0., zmax);
	}
	for (i = 0; i <= count_z; i++) {
		glVertex3f (xmin, 0, i * zstep + zmin);
		glVertex3f (xmax, 0, i * zstep + zmin);
	}
	glEnd ();

}

void ViewBase::DrawWorld () {
}

void ViewBase::Resize (int width, int height) {
	if (height == 0)
		height = 1;

	mWindowWidth = static_cast<unsigned int> (width);
	mWindowHeight = static_cast<unsigned int> (height);

	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(mCamera->GetFOVY (), float (width) / float (height), 0.1, 100);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity ();

	LogDebug ("Resize to: %d x %d", mWindowWidth,mWindowHeight);

	/** \warning
	 * This call has to be made for SDL 1.2 for 1.3 there seems to be a
	 * workaround, however since I do not yet run SDL 1.3 I hold on to this.
	 * See http://lists.libsdl.org/pipermail/sdl-libsdl.org/2008-November/067306.html
	 */
	if( SDL_SetVideoMode( mWindowWidth, mWindowHeight, 16, SDL_OPENGL | SDL_RESIZABLE ) == 0 ) {
		LogError ("Video mode set failed: %s", SDL_GetError ());
		exit (-1);
	}
}

/*
bool ViewBase::SendKeyDown (const SDL_keysym &keysym) {
	std::vector<OverlayBasePtr>::iterator overlay_iter;
	for (overlay_iter = mOverlays.begin(); overlay_iter != mOverlays.end(); overlay_iter++) {
		if ( (*overlay_iter)->OnKeyDown (keysym))
			return true;
	}

	return false;
}

bool ViewBase::SendKeyUp (const SDL_keysym &keysym) {
	std::vector<OverlayBasePtr>::iterator overlay_iter;
	for (overlay_iter = mOverlays.begin(); overlay_iter != mOverlays.end(); overlay_iter++) {
		if ( (*overlay_iter)->OnKeyUp (keysym))
			return true;
	}

	return false;
}

bool ViewBase::SendMouseButtonUp (Uint8 button, Uint16 xpos, Uint16 ypos) {
	std::vector<OverlayBasePtr>::iterator overlay_iter;
	for (overlay_iter = mOverlays.begin(); overlay_iter != mOverlays.end(); overlay_iter++) {
		if ( (*overlay_iter)->OnMouseButtonUp (button, xpos, ypos))
			return true;
	}

	return false;
}

bool ViewBase::SendMouseButtonDown (Uint8 button, Uint16 xpos, Uint16 ypos) {
	std::vector<OverlayBasePtr>::iterator overlay_iter;
	for (overlay_iter = mOverlays.begin(); overlay_iter != mOverlays.end(); overlay_iter++) {
		if ( (*overlay_iter)->OnMouseButtonDown (button, xpos, ypos))
			return true;
	}

	return false;
}
*/

/*
 * Global functions
 */
void DrawGLString (float x, float y, const char* str) {
	if (!ViewInstance) {
		LogError ("Cannot Draw GL String: View not yet initialized!");
		return;
	}
	ViewInstance->DrawGLString (x, y, str);
}

void SelectFont (const char* font) {
	if (!ViewInstance) {
		LogError ("Cannot select font: View not yet initialized!");
		return;
	}
	ViewInstance->SelectFont(font);
}

void SetFontJustification (FontJustification justification) {
	if (!ViewInstance) {
		LogError ("Cannot select font: View not yet initialized!");
		return;
	}
	ViewInstance->SetFontJustification (justification);
}

unsigned int GetWindowWidth() {
	return ViewInstance->GetWindowWidth ();
}

unsigned int GetWindowHeight() {
	return ViewInstance->GetWindowHeight ();
}

int GetFrameRate () {
	return ViewInstance->GetFrameRate ();
}

}
