/*
App looks complicated but all its doing is setting up the system to allow us to have graphics, keyboard/mouse and an instance of a game
we can then focus all our efforts on.

In this case the game is going to be Kamikaze



*/

#include "App.h"
#include "stb_image.h"
#include "Input.h"
#include <chrono>
#include <iomanip>
#include <sstream>
#include <iostream>
#include "IGraphics.h"
#include "Graphics.h" // more of a generic system of graphics
#include "IInput.h"
#include "Surface.h" // this is the important new class here, we can create surfaces
#include "Objects.h" // our broad general base class for all things we create in game

#include "MyFiles.h"





App::App(const Input* const input, IGraphics* graphics) :
	input(input),
	graphics(graphics)

{
	printf("This cross project was partly inspired by BUas Student Ferri de Lange\n");
	printf("This GPU supplied by  :%s\n", glGetString(GL_VENDOR));
	printf("This GPU supports GL  :%s\n", glGetString(GL_VERSION));
	printf("This GPU Renders with :%s\n", glGetString(GL_RENDERER));
	printf("This GPU Shaders are  :%s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));

}

App::~App()
{

}

/*****************************************
Draw a Rectangle with texture this is a hard coded
draw which is only good for the Rectangle
******************************************/
int App::DrawRectangle(GLuint FrameBufferID)
{

	GLfloat RectVertices[] = {
		-1.0f,		 // data has to be position then normalised texture coord
		1.0f,
		0.0f,		// Position 0

		0.0f,
		0.0f,       // TexCoord 0 

		-1.0f,
		-1.0f,
		0.0f,		// Position 1

		0.0f,
		1.0f,       // TexCoord 1

		1.0f,
		-1.0f,
		0.0f,		// Position 2

		1.0f,
		1.0f,       // TexCoord 2

		1.0f,
		1.0f,
		0.0f,		// Position 3

		1.0f,
		0.0f        // TexCoord 3
	};

	GLushort indices[] = { 0, 1, 2, 0, 2, 3 };


	if (PO == -1)
	{

		GLbyte vShaderStr[] =
			"attribute vec4 a_position;\n"
			"attribute vec2 a_texCoord;\n"
			"varying vec2 v_texCoord;\n"
			"void main()\n"
			"{gl_Position=a_position;\n"
			" v_texCoord=a_texCoord;}\n";

		GLbyte fShaderStr[] =
			"precision mediump float;\n"
			"varying vec2 v_texCoord;\n"
			"uniform sampler2D s_texture;\n"
			"void main()\n"
			"{\n"
			"gl_FragColor = texture2D( s_texture, v_texCoord );\n"
			"}\n";



		// Load and compile the vertex/fragment shaders
		GLuint vShader = GraphicFunctions.LoadShader(GL_VERTEX_SHADER, (const char*)vShaderStr);
		GLuint fShader = GraphicFunctions.LoadShader(GL_FRAGMENT_SHADER, (const char*)fShaderStr);

		// Create the program object	
		PO = glCreateProgram();

		// now we have the V and F shaders  attach them to the progam object
		glAttachShader(PO, vShader);
		glAttachShader(PO, fShader);

		// Link the program
		glLinkProgram(PO);
		// Check the link status
		GLint AreTheylinked;

		glGetProgramiv(PO, GL_LINK_STATUS, &AreTheylinked);
		if (!AreTheylinked)
		{ // they should link fine but just in case, lets report if there was an error
			GLint RetinfoLen = 0;
			// check and report any errors
			glGetProgramiv(PO, GL_INFO_LOG_LENGTH, &RetinfoLen);
			if (RetinfoLen > 1)
			{
				GLchar* infoLog = (GLchar*)malloc(sizeof(char) * RetinfoLen);
				glGetProgramInfoLog(PO, RetinfoLen, NULL, infoLog);
				fprintf(stderr, "Error linking program:\n%s\n", infoLog);
				free(infoLog);
			}
			glDeleteProgram(PO);
			return FALSE; //something bad happened
		}
		// finally get the sampler location		
		samplerLoc = glGetUniformLocation(PO, "s_texture");
	}

	glUseProgram(PO);
	// Load the vertex position
	glVertexAttribPointer(0,
		3,
		GL_FLOAT,
		GL_FALSE,
		5 * sizeof(GLfloat),
		RectVertices);
	// Load the texture coordinate
	glVertexAttribPointer(1,
		2,
		GL_FLOAT,
		GL_FALSE,
		5 * sizeof(GLfloat),
		&RectVertices[3]);
	// Set the sampler texture unit to 0


	glUniform1i(samplerLoc, 0);

	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);
	// Bind the texture
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, FrameBufferID);
	//actually draw the rect as 2 sets of 3 vertices (2 tris make a rect)
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);

	if (glGetError() != GL_NO_ERROR)
	{
		printf("Oh bugger");
	}

	return TRUE;
}


// start initialises things for our App then goes into the update render display cycle
void App::Start()
{
	InitializeOpenGLES(); // ok set up openGLES

	float deltatime;
	float totaltime = 0.0f;

	// Use chrono for cross-platform high-resolution timing
	auto t1 = std::chrono::high_resolution_clock::now();

	// now create a surface which will be our main screen
	m_Screen = new Surface(WINDOW_WIDTH, WINDOW_HEIGHT);
	m_Screen->SetPitch(WINDOW_WIDTH);

	SetupGame(); // if we have anything to load/setup/initialise, do it here

	// now we enter the loop which is all contained in the derived class Kamikaze.cpp we try not to play with this code now
	while (!quitting)
	{
		auto t2 = std::chrono::high_resolution_clock::now();
		std::chrono::duration<float> elapsed = t2 - t1;
		deltatime = elapsed.count(); // time in seconds as a float
		t1 = t2;

		// here is where we will build and create our basic content until the game sets the quitting flag which returns control to the main code.
		Update(0); // we normally will pass a delta time
		Render();
		PostRender();
	}



}






const Input& App::GetInput() const
{
	return *input;
}

void App::Quit()
{
	quitting = true;
}


//example of using the key and mouse
void App::ProcessInput()
{
	const Input& input = GetInput();
	const IMouse& mouse = GetInput().GetMouse();
	{
		if (input.GetKeyboard().GetKey(Key::W))
		{
			printf("we pressed W\n");
		}
		if (input.GetKeyboard().GetKey(Key::S))
		{
			printf("we pressed S\n");
		}
		if (input.GetKeyboard().GetKey(Key::A))
		{
			printf("we pressed A\n");
		}
		if (input.GetKeyboard().GetKey(Key::D))
		{
			printf("we pressed D\n");
		}

		if (input.GetKeyboard().GetKey(Key::ESCAPE))
		{
			printf("we pressed EscD\n");
			quitting = true;
		}

		if (input.GetMouse().GetButtonDown(MouseButtons::LEFT))
		{
			printf("we pressed mouse left\n");
		}



	}



}






void App::InitializeOpenGLES()
{

	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	glDepthFunc(GL_LEQUAL);
	glDepthRangef(0.0f, 1.0f);
	glClearDepthf(1.0f);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glBlendEquation(GL_FUNC_ADD);

	glCullFace(GL_BACK);
	glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
}

void App::ClearScreen()
{
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //black
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void App::Swap()
{
	if (FrameBufferID != -1) glDeleteTextures(1, &FrameBufferID); //delete the old texture (displayed 1 frame ago) BUT NOT IF ITS 1ST

	FrameBufferID = OurGraphics.CreateSimpleTexture2D(WINDOW_WIDTH, WINDOW_HEIGHT, (char*)m_Screen->GetBuffer());

	return;
}
