#include "App.h"
#include "stb_image.h"
#include "Input.h"
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <iostream>
#include "IGraphics.h"
#include "Graphics.h" // more of a generic system of graphics
#include "IInput.h"
#include "MyFiles.h"

Graphics GraphicFunctions;

// this is how it is in the book, but... its a bit flawed
typedef struct  
{
	GLint programObject; // the main issue is that here were are not allowed to init the values...
	GLint positionLoc ;
	GLint texCoordLoc ;
	GLint samplerLoc ;
	GLuint textureId ; 
} UserData;

// enter the code for our actual shaders fill in the missing
GLbyte vShaderStr[] =
" v_texCoord=a_texCoord;}\n";

GLbyte fShaderStr[] =
"precision mediump float;\n"

"}\n";



#define NUMBEROFPICS  2 /* how many entries have we */
#define TIMETOPAUSE 5.0f

/*This is a bit of a throwback to the very old original hello triangle on early OpenGLES2.0 tutorial books, we will use a cleaner system in later versions
but for compatability with book 1 we will keep this structure here, its used to contain useful data for our rendering.
*/

UserData* user_data = (UserData*)malloc(sizeof(UserData)); // a very clunky way to allocate space...but.. its what we did back then I have removed the p_state stuff though.

char* Images[] = {  //place your pics in the Assets folder, add them to the solution, so the Pi version can send them to the Pi
	(char*)"../Common/Assets/yourfile1.jpg", 
	(char*)"../Common/Assets/yourfile2.png"
};

Game::Game(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));

// now before we do anything we need to make sure the user_data is all -1... this can be done much easier but lets just make sure
	user_data->positionLoc = -1;
	user_data->programObject = -1;
	user_data->samplerLoc = -1;
	user_data->texCoordLoc = -1;
	user_data->textureId = -1;
// now this really is very oldschool and nasty, can you think of a betterway to create a userdata struct and have it initialised?
}

Game::~Game()
{

}

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

	// now we've included the vertices here... which honestly seems to make sense as only this method uses it, but its a bit untidy.
	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 };






	glUseProgram(user_data->programObject);
	// Load the vertex position
	glVertexAttribPointer(user_data->positionLoc,
		3,
		GL_FLOAT,
		GL_FALSE,
		5 * sizeof(GLfloat),
		RectVertices);
	// Load the texture coordinate
	glVertexAttribPointer(user_data->texCoordLoc,
		2,
		GL_FLOAT,
		GL_FALSE,
		5 * sizeof(GLfloat),
		&RectVertices[3]);

	glEnableVertexAttribArray(user_data->positionLoc);
	glEnableVertexAttribArray(user_data->texCoordLoc);
	// Bind the texture
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, user_data->textureId);
	//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;
}



void Game::Start()
{
	InitializeOpenGLES();
	MyFiles FileHandler;

	// Timing
	auto startTime = std::chrono::system_clock::now();
	auto lastTime = startTime;

	float averageFPS{ 0 };
	glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

	// now go do the graphic loop	
	int	WhichImage = 0;
	int how_many_loops = 1;

	while(!quitting)
	{
		ProcessInput();
		auto time = std::chrono::system_clock::now();
		std::chrono::duration<float> delta = time - lastTime;

		gameDeltaTime = delta.count();

		std::chrono::duration<float> elapsed = time - startTime;
		if (elapsed.count() > TIMETOPAUSE  || how_many_loops == 1)
		{
			startTime = time;
			frameCount = 0;

			int Width, Height;
			char* OurRawData = FileHandler.Load(Images[WhichImage], &Width, &Height);
			
			if (OurRawData == NULL)
			{
				printf("We failed to load an image\n"); // we might not get the print out of this, so put a breakpoint on it.
				quitting = true;
				break; // don't continue, this allows us to do a safe exit if there's an error, rather than a full system lock which is not wise
			}
			user_data->textureId = GraphicFunctions.CreateTexture2D(Width, Height, OurRawData);

			if (user_data->textureId == -1)
			{
				printf("We failed to to create a texture\n"); // can also consider a beakpoint
				quitting = true;
				break; // don't continue, this allows us to do a safe exit if there's an error, rather than a full system lock which is not wise			
			}
			ClearScreen();
			DrawRectangle();
			graphics->SwapBuffer();


			glDeleteTextures(1, &user_data->textureId);
			WhichImage++;
			if (WhichImage == NUMBEROFPICS) WhichImage = 0;
			printf("We've done %i loops \n", how_many_loops);
			how_many_loops++;
		

		}


		lastTime = time;
		++frameCount;
	}

	graphics->Quit();
}



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

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


//example of using the key and mouse
void Game::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 Game::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 Game::ClearScreen()
{
	glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
