#include "mygame.h"

#include "vertextshader.h"
#include "fragmentshader.h"
#include "geometryshader.h"

#include <sstream> // for std::stringstream
#include <iostream> // for std::cout
#include <fstream> // for std::fstream
#include <iomanip> // for std::setprecison

using namespace Gx;

static char logBuffer[1024];

#define BUFFER_OFFSET(bytes) ((GLubyte*) NULL+(bytes))

struct vertex {
	float x, y, z;
	float s, t;
};

// quad
static vertex position[] = {
	{ }, // 1
	{ }, // 2
	{ }, // 3

	{ }, // 1
	{ }, // 3
	{ }  // 4
};

GLuint loadTexture(const char * filename, unsigned int width, unsigned int height)
{
	std::fstream file(filename, std::fstream::in | std::fstream::binary);
	if ( file.is_open() )
	{
		const unsigned int count = width * height * 3;
		char * data = new char[count]; // RGB

		file.read(data, count);

		GLuint texture;

		delete [] data;

		return texture;
	}
	else
	{
		std::cout << "Nie mozna odnalezc pliku: " << filename << std::endl;
	}

	return -1;
}

void releaseTexture(GLuint texture) {
	glDeleteTextures(1, &texture);
}



/*
 *	Kontruktor: tutaj inicjalizujemy format pixeli i kontekst renderingu.
 *	Warto także pokazać okno :). To nie jest miejsce na inicjalizacje funkcji gl*.
 *	Oraz cała reszta co musi być w konstruktorze.
 */
MyGame::MyGame()
{
	TRACE();

	PixelFormat pixelFormat; // domyślne parametry, podwójne buforowanie, bez antialisingu
	getGraphicsDevice().setPixelFormat(pixelFormat);

	GraphicsContext graphicsContext; // domyślnie opengl 3.3 core forward
	getGraphicsDevice().setGraphicsContext(graphicsContext);

	//getGraphicsDevice().setVerticalSync(false); // wyłącz synchronizacje pionową

	getWindow().setDimensions(600, 600); // ustaw rozmiar okna
	getWindow().show(); // pokaż okno
	getWindow().update(); // odśwież okno
}

/*
 *	Metoda odpalana jest raz tuż po zainicjalizowaniu całej aplikacji - gotowy kontekst openGL etc.
 *	Tutaj inicjalizujemy wszystko co związane z OpenGL. Ładowanie tekstur, modeli etc.
 */
void MyGame::initialize()
{
	TRACE();
	glClearColor(0.3f, 0.5f, 0.9f, 0.0f); // ustawiamy 'kolor tła'

	glEnable(GL_DEPTH_TEST);
	//glEnable(GL_CULL_FACE);

	//glPointSize(5.0f); // rozmiar wyswietlanego punktu
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe


	m_normalTexture = loadTexture("..\\Content\\normal_map.raw", 256, 256);
	ASSERT(m_normalTexture != -1);
	m_colorTexture = loadTexture("..\\Content\\colour_map.raw", 256, 256);
	ASSERT(m_colorTexture != -1);

	this->initShaders();
	this->initBuffers();
	this->initVertexPositionBuffer();

	m_camera.position = Vector3(0.0f, 2.0f, 25.0f);
	m_projection = Matrix4::createPerspective(60.0f * Math::PiDiv180, getGraphicsDevice().getViewport().getAspectRatio(), 1.0f, 10000.0f);
	m_mvpLocation = glGetUniformLocation(m_identityProgram.getHandle(), "mvp");
	m_normalTextureLoc = glGetUniformLocation(m_identityProgram.getHandle(), "normalTexture");
	m_colorTextureLoc = glGetUniformLocation(m_identityProgram.getHandle(), "colorTexture");
}

void MyGame::initShaders()
{
	VertexShader vertexShader;
	if ( !vertexShader.loadFromFile("..\\Shaders\\vertexshader.vs") )
	{
		std::printf("Vertex Shader File Error\n");
	}
	if ( !vertexShader.compile() )
	{
		vertexShader.getLog(logBuffer, 1024);
		std::printf("Vertex Shader Compile Error: %s\n", logBuffer);
	}

	/*GeometryShader geometryShader;
	if ( !geometryShader.loadFromFile("..\\Shaders\\geometryshader.gs") )
	{
		std::printf("Geometry Shader File Error\n");
	}

	if ( !geometryShader.compile() )
	{
		geometryShader.getLog(logBuffer, 1024);
		std::printf("Geometry Shader Compile Error: %s\n", logBuffer);
	}*/

	FragmentShader fragmentShader;
	if ( !fragmentShader.loadFromFile("..\\Shaders\\fragmentshader.fs") )
	{
		std::printf("Fragment Shader File Error\n");
	}

	if ( !fragmentShader.compile() )
	{
		fragmentShader.getLog(logBuffer, 1024);
		std::printf("Fragment Shader Compile Error: %s\n", logBuffer);
	}

	if ( !m_identityProgram.create() )
	{
		std::printf("Cannot create a shader program.\n");
	}

	m_identityProgram.attach(vertexShader);
	//m_identityProgram.attach(geometryShader);
	m_identityProgram.attach(fragmentShader);

	if ( !m_identityProgram.link() )
	{
		m_identityProgram.getLog(logBuffer, 1024);
		std::printf("Shader Program Linker Error: %s\n", logBuffer);
	}

	if ( !m_identityProgram.validate() )
	{
		std::printf("Shader Program Validate Error.\n");
	}
}

void MyGame::initBuffers()
{
	// wygeneruj obiekty buforów
	glGenBuffers(buffersCount, m_buffers);

	// podczep bufor, zaalokuj pamięć, skopiuj dane, odepnij bufor
	glBindBuffer(GL_ARRAY_BUFFER, m_buffers[0]);
		glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void MyGame::initVertexPositionBuffer()
{
	glGenVertexArrays(1, &m_vertexPositionBuffer);

	glBindVertexArray(m_vertexPositionBuffer);
		glBindBuffer(GL_ARRAY_BUFFER, m_buffers[0]);

		m_positionLocation = glGetAttribLocation(m_identityProgram.getHandle(), "position");
		ASSERT(m_positionLocation != -1);
		m_texcoordLocation = glGetAttribLocation(m_identityProgram.getHandle(), "texcoord");
		ASSERT(m_texcoordLocation != -1);

		glEnableVertexAttribArray(m_positionLocation);
		glEnableVertexAttribArray(m_texcoordLocation);

		glVertexAttribPointer(m_positionLocation, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), BUFFER_OFFSET(NULL));
		glVertexAttribPointer(m_texcoordLocation, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), BUFFER_OFFSET(3*sizeof(float)));

		glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}

/*
 *	Metoda odpalana jest raz tuż przed zamknięciem aplikacji.
 *	Tutaj zwalniamy wszystko co zainicjalizowaliśmy w MyGame::initialize().
 */
void MyGame::release()
{
	releaseTexture(m_colorTexture);
	releaseTexture(m_normalTexture);

	glDeleteVertexArrays(1, &m_vertexPositionBuffer);
	glDeleteBuffers(buffersCount, m_buffers);

	TRACE();
}

/*
 *	Metoda jest odpalana za każdym razem gdy zmieni się rozmiar okna.
 *	Każdy komunikat WM_SIZE od Windows spowoduję wywołanie tej metody.
 *	Dobre miejsce na np: glViewport, macierz projekcji (viewport.getAspectRatio() może się przydać)
 */
void MyGame::reshape( const Viewport& viewport )
{
	glViewport(0, 0, viewport.getWidth(), viewport.getHeight());
	m_projection = Matrix4::createPerspective(60.0f * Math::PiDiv180, viewport.getAspectRatio(), 1.0f, 100.0f);
}

/*
 *	Metoda odpalana jest raz na każdą klatkę.
 *	Bardzo dobre miejsce do sprawdzania stanu myszki, klawiatury etc.
 *	Także mogą być wszystkie inne rzeczy, które nie pasują do update i render, a wymagają
 *	odświerzania co klatkę.
 *	elapsedTime - czas który upłynął od ostatniej klatki, liczony w sekundach
 */
void MyGame::input( float elapsedTime )
{
	if ( getKeyboard().isKeyDown(Keys::W) ) {
		m_camera.walk(1.0f, elapsedTime);
	}

	if ( getKeyboard().isKeyDown(Keys::S) ) {
		m_camera.walk(-1.0f, elapsedTime);
	}

	if ( getKeyboard().isKeyDown(Keys::A) ) {
		m_camera.strafe(-1.0f, elapsedTime);
	}

	if ( getKeyboard().isKeyDown(Keys::D) ) {
		m_camera.strafe(1.0f, elapsedTime);
	}

	if ( getKeyboard().isKeyDown(Keys::R) ) {
		m_camera.fly(1.0f, elapsedTime);
	}

	if ( getKeyboard().isKeyDown(Keys::F) ) {
		m_camera.fly(-1.0f, elapsedTime);
	}

	if ( getKeyboard().isKeyDown(Keys::Q) ) {
		m_camera.yaw(1.0f);
	}

	if ( getKeyboard().isKeyDown(Keys::E) ) {
		m_camera.yaw(-1.0f);
	}

	if ( getKeyboard().isKeyDown(Keys::Up) ) {
		m_camera.pitch(1.0f);
	}

	if ( getKeyboard().isKeyDown(Keys::Down) ) {
		m_camera.pitch(-1.0f);
	}

	if ( getKeyboard().isKeyDown(Keys::Left) ) {
		m_camera.yaw(-1.0f);
	}

	if ( getKeyboard().isKeyDown(Keys::Right) ) {
		m_camera.yaw(1.0f);
	}

	if ( getMouse().isButtonDown(MouseButtons::Right) )
	{
		m_camera.yaw( getMouse().getRelativePosX() );
		m_camera.pitch( getMouse().getRelativePosY() );
	}

	m_view = m_camera.getViewMatrix();
	m_model = Matrix4::Identity;
	m_mvp = m_projection * m_view * m_model;
}

/*
 *	Metoda odpalana jest od jednego do kilku razy na klatkę - tzw. symulacja stałokrokowa.
 *	Tutaj jedynie fizyka aktorów - np. PhysX, Bullet etc.
 *	elapsedTime - stały czas, domyślnie 1/60 sekundy
 */
void MyGame::update( float elapsedTime )
{
}

/*
 *	Metoda odpalana jest raz na każdą klatkę.
 *	Tutaj jedynie renderowanie aktorów i innych obiektów.
 *	elapsedTime - czas który upłynął od ostatniej klatki, liczony w sekundach
 */
void MyGame::render( float elapsedTime )
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glUseProgram( m_identityProgram.getHandle() );

	glUniformMatrix4fv(m_mvpLocation, 1, GL_FALSE, m_mvp.begin());
	

	

	glUseProgram( 0 );

	glFlush();
}