
#ifndef GRAPHICS_H
#define GRAPHICS_H

#include <iostream>
#include <fstream>
#include <string>
#include <regex>
#include <limits>
#include <algorithm>
#include <boost/any.hpp>
#include "strops.h"
#include "main.h"
#include <SDL.h>
#include <SDL_image.h> 
#include <stdio.h>
#include <chrono>
#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <string>
#include <any>

using namespace std;
using namespace boost;

int WINDOW_WIDTH = 1280;
int WINDOW_HEIGHT = 720;

unordered_map<string, int> KEYS =
{
	{"ESCAPE", 0},
	{"1", 0},
	{"2", 0},
	{"3", 0},
	{"4", 0},
	{"5", 0},
	{"6", 0},
	{"7", 0},
	{"8", 0},
	{"9", 0},
	{"TILDE", 0},
	{"MINUS", 0},
	{"EQUALS", 0},
	{"BACKSPACE", 0},
	{"TAB", 0},
	{"Q", 0},
	{"W", 0},
	{"E", 0},
	{"R", 0},
	{"T", 0},
	{"Y", 0},
	{"U", 0},
	{"I", 0},
	{"O", 0},
	{"P", 0},
	{"BRACKET_L", 0},
	{"BRACKET_R", 0},
	{"BACKSLASH", 0},
	{"A", 0},
	{"S", 0},
	{"D", 0},
	{"F", 0},
	{"G", 0},
	{"H", 0},
	{"J", 0},
	{"K", 0},
	{"L", 0},
	{"COLON", 0},
	{"QUOTE", 0},
	{"ENTER", 0},
	{"SHIFT_L", 0},
	{"Z", 0},
	{"X", 0},
	{"C", 0},
	{"V", 0},
	{"B", 0},
	{"N", 0},
	{"M", 0},
	{"COMMA", 0},
	{"PERIOD", 0},
	{"SLASH", 0},
	{"SHIFT_R", 0},
	{"CTRL_L", 0},
	{"ALT_L", 0},
	{"SPACE", 0},
	{"ALT_R", 0},
	{"CTRL_R", 0},
	{"LEFT", 0},
	{"UP", 0},
	{"RIGHT", 0},
	{"DOWN", 0}
};

enum Buttons
{
	PaddleOneUp = 0,
	PaddleOneDown,
	PaddleTwoUp,
	PaddleTwoDown,
};

const float PADDLE_SPEED = 1.0f;


//The window we'll be rendering to
SDL_Window* gWindow = NULL;

//The renderer we'll be rendering to
SDL_Renderer* gRenderer = NULL;

//The surface contained by the window
SDL_Surface* gScreenSurface = NULL;

bool running = true;
float dt = 0.0f;

float clamp(float v, float min, float max);


SDL_Surface* loadSurface(std::string path)
{
	//The final optimized image
	SDL_Surface* optimizedSurface = NULL;

	//Load image at specified path
	SDL_Surface* loadedSurface = IMG_Load(path.c_str());
	if (loadedSurface == NULL)
	{
		printf("Unable to load image %s! SDL_image Error: %s\n", path.c_str(), IMG_GetError());
	}
	else
	{
		//Convert surface to screen format
		optimizedSurface = SDL_ConvertSurface(loadedSurface, gScreenSurface->format, 0);
		if (optimizedSurface == NULL)
		{
			printf("Unable to optimize image %s! SDL Error: %s\n", path.c_str(), SDL_GetError());
		}

		//Get rid of old loaded surface
		SDL_FreeSurface(loadedSurface);
	}

	return optimizedSurface;
}

class Vec2
{
public:
	Vec2()
		: x(0.0f), y(0.0f)
	{}

	Vec2(float x, float y)
		: x(x), y(y)
	{}

	Vec2 operator+(Vec2 const& rhs)
	{
		return Vec2(x + rhs.x, y + rhs.y);
	}

	Vec2& operator+=(Vec2 const& rhs)
	{
		x += rhs.x;
		y += rhs.y;

		return *this;
	}

	Vec2 operator-(Vec2 const& rhs)
	{
		return Vec2(x - rhs.x, y - rhs.y);
	}

	Vec2& operator-=(Vec2 const& rhs)
	{
		x -= rhs.x;
		y -= rhs.y;

		return *this;
	}

	Vec2 operator*(float rhs)
	{
		return Vec2(x * rhs, y * rhs);
	}

	Vec2 operator*=(float rhs)
	{
		x *= rhs;
		y *= rhs;
		
		return *this;
	}

	Vec2 operator/(float rhs)
	{
		return Vec2(x / rhs, y / rhs);
	}

	Vec2 operator/=(float rhs)
	{
		x /= rhs;
		y /= rhs;

		return *this;
	}

	bool operator==(Vec2 other)
	{
		return x == other.x && y == other.y;
	}

	boost::any SubComponent(std::string componentName)
	{
		if (componentName == "x")
			return x;
		if (componentName == "y")
			return y;
		return 0;
	}

	Vec2 EditSubComponent(std::string componentName, std::string oper, boost::any otherVal)
	{
		if (componentName == "x")
		{
			if (oper == "=")
				x = AnyAsFloat(otherVal);
			else if (oper == "+=")
				x += AnyAsFloat(otherVal);
			else if (oper == "-=")
				x -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				x *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				x /= AnyAsFloat(otherVal);
		}
		if (componentName == "y")
		{
			if (oper == "=")
				y = AnyAsFloat(otherVal);
			else if (oper == "+=")
				y += AnyAsFloat(otherVal);
			else if (oper == "-=")
				y -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				y *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				y /= AnyAsFloat(otherVal);
		}
		return *this;
	}

	float x, y;
};

struct _RotRect {
	Vec2 C;
	Vec2 S;
	float ang;
};

inline void RotateVec2Clockwise(Vec2* v, float ang)
{
	float t,
		cosa = cos(ang),
		sina = sin(ang);
	t = v->x; v->x = t * cosa + v->y * sina; v->y = -t * sina + v->y * cosa;
}

// Rotated Rectangles Collision Detection, Oren Becker, 2001
int RotRectsCollision(_RotRect* rr1, _RotRect* rr2)
{
	Vec2 A, B,   // vertices of the rotated rr2
		C,      // center of rr2
		BL, TR; // vertices of rr2 (bottom-left, top-right)

	float ang = rr1->ang - rr2->ang, // orientation of rotated rr1
		cosa = cos(ang),           // precalculated trigonometic -
		sina = sin(ang);           // - values for repeated use

	float t, x, a;      // temporary variables for various uses
	float dx;           // deltaX for linear equations
	float ext1, ext2;   // min/max vertical values

	// move rr2 to make rr1 cannonic
	C = rr2->C;
	C -= rr1->C;

	// rotate rr2 clockwise by rr2->ang to make rr2 axis-aligned
	RotateVec2Clockwise(&C, rr2->ang);

	// calculate vertices of (moved and axis-aligned := 'ma') rr2
	BL = TR = C;
	BL -= rr2->S;
	TR += rr2->S;

	// calculate vertices of (rotated := 'r') rr1
	A.x = -rr1->S.y * sina; B.x = A.x; t = rr1->S.x * cosa; A.x += t; B.x -= t;
	A.y = rr1->S.y * cosa; B.y = A.y; t = rr1->S.x * sina; A.y += t; B.y -= t;

	t = sina * cosa;

	// verify that A is vertical min/max, B is horizontal min/max
	if (t < 0)
	{
		t = A.x; A.x = B.x; B.x = t;
		t = A.y; A.y = B.y; B.y = t;
	}

	// verify that B is horizontal minimum (leftest-vertex)
	if (sina < 0) { B.x = -B.x; B.y = -B.y; }

	// if rr2(ma) isn't in the horizontal range of
	// colliding with rr1(r), collision is impossible
	if (B.x > TR.x || B.x > -BL.x) return 0;

	// if rr1(r) is axis-aligned, vertical min/max are easy to get
	if (t == 0) { ext1 = A.y; ext2 = -ext1; }
	// else, find vertical min/max in the range [BL.x, TR.x]
	else
	{
		x = BL.x - A.x; a = TR.x - A.x;
		ext1 = A.y;
		// if the first vertical min/max isn't in (BL.x, TR.x), then
		// find the vertical min/max on BL.x or on TR.x
		if (a * x > 0)
		{
			dx = A.x;
			if (x < 0) { dx -= B.x; ext1 -= B.y; x = a; }
			else { dx += B.x; ext1 += B.y; }
			ext1 *= x; ext1 /= dx; ext1 += A.y;
		}

		x = BL.x + A.x; a = TR.x + A.x;
		ext2 = -A.y;
		// if the second vertical min/max isn't in (BL.x, TR.x), then
		// find the local vertical min/max on BL.x or on TR.x
		if (a * x > 0)
		{
			dx = -A.x;
			if (x < 0) { dx -= B.x; ext2 -= B.y; x = a; }
			else { dx += B.x; ext2 += B.y; }
			ext2 *= x; ext2 /= dx; ext2 -= A.y;
		}
	}

	// check whether rr2(ma) is in the vertical range of colliding with rr1(r)
	// (for the horizontal range of rr2)
	return !((ext1 < BL.y&& ext2 < BL.y) ||
		(ext1 > TR.y && ext2 > TR.y));
}

class Sprite
{
public:
	Sprite(std::string path, Vec2 position, Vec2 scale, double angle)
		: position(position), angle(angle), path(path), scale(scale)
	{
		rect.x = static_cast<int>(position.x);
		rect.y = static_cast<int>(position.y);
		rect.w = scale.x;
		rect.h = scale.y;
		// Centers
		rect.x = position.x - (rect.w / 2);
		rect.y = position.y - (rect.h / 2);

		Load();
	}

	int Load()
	{
		SDL_Surface* surface = loadSurface(path);
		texture = SDL_CreateTextureFromSurface(gRenderer, surface);
		SDL_FreeSurface(surface);
		return 0;
	}

	int Update()
	{
		SDL_Surface* surface = loadSurface(path);
		SDL_DestroyTexture(texture);
		texture = SDL_CreateTextureFromSurface(gRenderer, surface);
		SDL_FreeSurface(surface);
		return 0;
	}

	int Draw()
	{
		SDL_RenderCopy(gRenderer, texture, NULL, &rect);
		// Centers
		rect.x = position.x - (rect.w / 2);
		rect.y = position.y - (rect.h / 2);
		return 0;
	}

	bool operator==(Sprite other)
	{
		return position == other.position && angle == other.angle && scale == other.scale && texture == other.texture;
	}

	boost::any SubComponent(std::string componentName)
	{
		if (componentName == "position")
			return position;
		if (componentName == "position.x")
			return position.x;
		if (componentName == "position.y")
			return position.y;
		if (componentName == "scale")
			return scale;
		if (componentName == "scale.x")
			return scale.x;
		if (componentName == "scale.y")
			return scale.y;
		return 0;
	}

	Sprite EditSubComponent(std::string componentName, std::string oper, boost::any otherVal)
	{
		//cout << ("ComponentName = " + componentName + " Op = " + oper + " OtherVal = " + AnyAsString(otherVal)) << endl;
		if (componentName == "position")
		{
			if (oper == "=")
				position = any_cast<Vec2>(otherVal);
			else if (oper == "+=")
				position += any_cast<Vec2>(otherVal);
			else if (oper == "-=")
				position -= any_cast<Vec2>(otherVal);
			else if (oper == "*=")
				position *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				position /= AnyAsFloat(otherVal);
		}
		else if (componentName == "position.x")
		{
			if (oper == "=")
				position.x = AnyAsFloat(otherVal);
			else if (oper == "+=")
				position.x += AnyAsFloat(otherVal);
			else if (oper == "-=")
				position.x -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				position.x *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				position.x /= AnyAsFloat(otherVal);
		}
		else if (componentName == "position.y")
		{
			if (oper == "=")
				position.y = AnyAsFloat(otherVal);
			else if (oper == "+=")
				position.y += AnyAsFloat(otherVal);
			else if (oper == "-=")
				position.y -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				position.y *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				position.y /= AnyAsFloat(otherVal);
		}

		else if (componentName == "scale")
		{
			if (oper == "=")
				scale = any_cast<Vec2>(otherVal);
			else if (oper == "+=")
				scale += any_cast<Vec2>(otherVal);
			else if (oper == "-=")
				scale -= any_cast<Vec2>(otherVal);
			else if (oper == "*=")
				scale *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				scale /= AnyAsFloat(otherVal);
		}
		else if (componentName == "scale.x")
		{
			if (oper == "=")
				scale.x = AnyAsFloat(otherVal);
			else if (oper == "+=")
				scale.x += AnyAsFloat(otherVal);
			else if (oper == "-=")
				scale.x -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				scale.x *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				scale.x /= AnyAsFloat(otherVal);
		}
		else if (componentName == "scale.y")
		{
			if (oper == "=")
				scale.y = AnyAsFloat(otherVal);
			else if (oper == "+=")
				scale.y += AnyAsFloat(otherVal);
			else if (oper == "-=")
				scale.y -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				scale.y *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				scale.y /= AnyAsFloat(otherVal);
		}
		// Centers
		rect.x = position.x - (rect.w / 2);
		rect.y = position.y - (rect.h / 2);
		return *this;
	}

	Vec2 position;
	Vec2 scale;
	double angle;

	std::string path;
	SDL_Rect rect{};
	SDL_Texture* texture;
};

class Text
{
public:
	Text(std::string content, std::string pathToFont, Vec2 position, float fontSize, double angle, Uint8 r, Uint8 g, Uint8 b)
		: position(position), angle(angle), content(content), pathToFont(pathToFont), fontSize(fontSize), r(r), g(g), b(b)
	{
		rect.x = position.x;
		rect.y = position.y;
		
		font = TTF_OpenFont(pathToFont.c_str(), fontSize);

		Load();
	}

	int Load()
	{
		SDL_Color color = { r, g, b };

		SDL_Surface* surface = TTF_RenderText_Solid(font, content.c_str(), color);
		
		texture = SDL_CreateTextureFromSurface(gRenderer, surface);

		TTF_SizeText(font, content.c_str(), &rect.w, &rect.h);

		scale.x = rect.w;
		scale.y = rect.h;

		// Centers
		rect.x = position.x - (rect.w / 2);
		rect.y = position.y - (rect.h / 2);

		SDL_FreeSurface(surface);
		return 0;
	}

	int Update()
	{
		SDL_Color color = { r, g, b };

		SDL_Surface* surface = TTF_RenderText_Solid(font, content.c_str(), color);

		SDL_DestroyTexture(texture);
		texture = SDL_CreateTextureFromSurface(gRenderer, surface);

		TTF_SizeText(font, content.c_str(), &rect.w, &rect.h);

		scale.x = rect.w;
		scale.y = rect.h;

		// Centers
		rect.x = position.x - (rect.w / 2);
		rect.y = position.y - (rect.h / 2);

		SDL_FreeSurface(surface);
		return 0;
	}

	int Draw()
	{
		SDL_RenderCopy(gRenderer, texture, NULL, &rect);
		return 0;
	}

	boost::any SubComponent(std::string componentName)
	{
		//cerr << componentName << endl;
		if (componentName == "position")
			return position;
		if (componentName == "position.x")
			return position.x;
		if (componentName == "position.y")
			return position.y;
		if (componentName == "scale")
			return scale;
		if (componentName == "scale.x")
			return scale.x;
		if (componentName == "scale.y")
			return scale.y;
		if (componentName == "fontSize")
			return fontSize;
		if (componentName == "content")
			return content;
		if (componentName == "pathToFont")
			return pathToFont;
		return 0;
	}

	Text EditSubComponent(const std::string componentName, const std::string oper, const boost::any otherVal)
	{
		//cerr << componentName << " " << AnyAsString(otherVal) << endl;
		if (componentName == "position")
		{
			if (oper == "=")
				position = any_cast<Vec2>(otherVal);
			else if (oper == "+=")
				position += any_cast<Vec2>(otherVal);
			else if (oper == "-=")
				position -= any_cast<Vec2>(otherVal);
			else if (oper == "*=")
				position *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				position /= AnyAsFloat(otherVal);
		}
		else if (componentName == "position.x")
		{
			if (oper == "=")
				position.x = AnyAsFloat(otherVal);
			else if (oper == "+=")
				position.x += AnyAsFloat(otherVal);
			else if (oper == "-=")
				position.x -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				position.x *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				position.x /= AnyAsFloat(otherVal);
		}
		else if (componentName == "position.y")
		{
			if (oper == "=")
				position.y = AnyAsFloat(otherVal);
			else if (oper == "+=")
				position.y += AnyAsFloat(otherVal);
			else if (oper == "-=")
				position.y -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				position.y *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				position.y /= AnyAsFloat(otherVal);
		}

		else if (componentName == "fontSize")
		{
			if (oper == "=")
				fontSize = AnyAsFloat(otherVal);
			else if (oper == "+=")
				fontSize += AnyAsFloat(otherVal);
			else if (oper == "-=")
				fontSize -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				fontSize *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				fontSize /= AnyAsFloat(otherVal);
		}

		else if (componentName == "r")
		{
			if (oper == "=")
				r = AnyAsFloat(otherVal);
			else if (oper == "+=")
				r += AnyAsFloat(otherVal);
			else if (oper == "-=")
				r -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				r *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				r /= AnyAsFloat(otherVal);
		}
		else if (componentName == "g")
		{
			if (oper == "=")
				g = AnyAsFloat(otherVal);
			else if (oper == "+=")
				g += AnyAsFloat(otherVal);
			else if (oper == "-=")
				g -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				g *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				g /= AnyAsFloat(otherVal);
		}
		else if (componentName == "b")
		{
			if (oper == "=")
				b = AnyAsFloat(otherVal);
			else if (oper == "+=")
				b += AnyAsFloat(otherVal);
			else if (oper == "-=")
				b -= AnyAsFloat(otherVal);
			else if (oper == "*=")
				b *= AnyAsFloat(otherVal);
			else if (oper == "/=")
				b /= AnyAsFloat(otherVal);
		}

		else if (componentName == "content")
		{
			if (oper == "=")
				content = AnyAsString(otherVal);
			else if (oper == "+=")
				content += AnyAsString(otherVal);
		}

		// Updates changes to text
		Update();
		return *this;
	}

	Vec2 position;
	Vec2 scale;
	float fontSize;
	double angle;
	Uint8 r;
	Uint8 g;
	Uint8 b;
	std::string content;
	std::string pathToFont;

	TTF_Font* font;

	std::string path;
	SDL_Rect rect{};
	SDL_Texture* texture;
};

int cleanupGraphics()
{
	// Cleanup
	SDL_DestroyRenderer(gRenderer);
	SDL_DestroyWindow(gWindow);
	SDL_Quit();

	return 0;
}


int updateLoop()
{
	// Continue looping and processing events until user exits
	while (running)
	{
		auto startTime = std::chrono::high_resolution_clock::now();

		SDL_Event event;
		while (SDL_PollEvent(&event))
		{
			if (event.type == SDL_QUIT)
			{
				running = false;
			}
			else if (event.type == SDL_KEYDOWN)
			{
				if (event.key.keysym.sym == SDLK_ESCAPE)
					KEYS["ESCAPE"] = 1;
				else if (event.key.keysym.sym == SDLK_0)
					KEYS["0"] = 1;
				else if (event.key.keysym.sym == SDLK_1)
					KEYS["1"] = 1;
				else if (event.key.keysym.sym == SDLK_2)
					KEYS["2"] = 1;
				else if (event.key.keysym.sym == SDLK_3)
					KEYS["3"] = 1;
				else if (event.key.keysym.sym == SDLK_4)
					KEYS["4"] = 1;
				else if (event.key.keysym.sym == SDLK_5)
					KEYS["5"] = 1;
				else if (event.key.keysym.sym == SDLK_6)
					KEYS["6"] = 1;
				else if (event.key.keysym.sym == SDLK_7)
					KEYS["7"] = 1;
				else if (event.key.keysym.sym == SDLK_8)
					KEYS["8"] = 1;
				else if (event.key.keysym.sym == SDLK_MINUS)
					KEYS["MINUS"] = 1;
				else if (event.key.keysym.sym == SDLK_EQUALS)
					KEYS["EQUALS"] = 1;
				else if (event.key.keysym.sym == SDLK_AC_BACK)
					KEYS["BACKSPACE"] = 1;
				else if (event.key.keysym.sym == SDLK_TAB)
					KEYS["TAB"] = 1;
				else if (event.key.keysym.sym == SDLK_q)
					KEYS["Q"] = 1;
				else if (event.key.keysym.sym == SDLK_w)
					KEYS["W"] = 1;
				else if (event.key.keysym.sym == SDLK_e)
					KEYS["E"] = 1;
				else if (event.key.keysym.sym == SDLK_r)
					KEYS["R"] = 1;
				else if (event.key.keysym.sym == SDLK_t)
					KEYS["T"] = 1;
				else if (event.key.keysym.sym == SDLK_y)
					KEYS["Y"] = 1;
				else if (event.key.keysym.sym == SDLK_u)
					KEYS["U"] = 1;
				else if (event.key.keysym.sym == SDLK_i)
					KEYS["I"] = 1;
				else if (event.key.keysym.sym == SDLK_o)
					KEYS["O"] = 1;
				else if (event.key.keysym.sym == SDLK_p)
					KEYS["P"] = 1;
				else if (event.key.keysym.sym == SDLK_LEFTBRACKET)
					KEYS["BRACKET_L"] = 1;
				else if (event.key.keysym.sym == SDLK_RIGHTBRACKET)
					KEYS["BRACKET_R"] = 1;
				else if (event.key.keysym.sym == SDLK_BACKSLASH)
					KEYS["BACKSLASH"] = 1;
				else if (event.key.keysym.sym == SDLK_a)
					KEYS["A"] = 1;
				else if (event.key.keysym.sym == SDLK_s)
					KEYS["S"] = 1;
				else if (event.key.keysym.sym == SDLK_d)
					KEYS["D"] = 1;
				else if (event.key.keysym.sym == SDLK_f)
					KEYS["F"] = 1;
				else if (event.key.keysym.sym == SDLK_g)
					KEYS["G"] = 1;
				else if (event.key.keysym.sym == SDLK_h)
					KEYS["H"] = 1;
				else if (event.key.keysym.sym == SDLK_j)
					KEYS["J"] = 1;
				else if (event.key.keysym.sym == SDLK_k)
					KEYS["K"] = 1;
				else if (event.key.keysym.sym == SDLK_l)
					KEYS["L"] = 1;
				else if (event.key.keysym.sym == SDLK_COLON)
					KEYS["COLON"] = 1;
				else if (event.key.keysym.sym == SDLK_QUOTE)
					KEYS["QUOTE"] = 1;
				else if (event.key.keysym.sym == SDLK_RETURN)
					KEYS["ENTER"] = 1;
				else if (event.key.keysym.sym == SDLK_LSHIFT)
					KEYS["SHIFT_L"] = 1;
				else if (event.key.keysym.sym == SDLK_z)
					KEYS["Z"] = 1;
				else if (event.key.keysym.sym == SDLK_x)
					KEYS["X"] = 1;
				else if (event.key.keysym.sym == SDLK_c)
					KEYS["C"] = 1;
				else if (event.key.keysym.sym == SDLK_v)
					KEYS["V"] = 1;
				else if (event.key.keysym.sym == SDLK_b)
					KEYS["B"] = 1;
				else if (event.key.keysym.sym == SDLK_n)
					KEYS["N"] = 1;
				else if (event.key.keysym.sym == SDLK_m)
					KEYS["M"] = 1;
				else if (event.key.keysym.sym == SDLK_COMMA)
					KEYS["COMMA"] = 1;
				else if (event.key.keysym.sym == SDLK_PERIOD)
					KEYS["PERIOD"] = 1;
				else if (event.key.keysym.sym == SDLK_SLASH)
					KEYS["SLASH"] = 1;
				else if (event.key.keysym.sym == SDLK_RSHIFT)
					KEYS["SHIFT_R"] = 1;
				else if (event.key.keysym.sym == SDLK_LCTRL)
					KEYS["CTRL_L"] = 1;
				else if (event.key.keysym.sym == SDLK_LALT)
					KEYS["ALT_L"] = 1;
				else if (event.key.keysym.sym == SDLK_SPACE)
					KEYS["SPACE"] = 1;
				else if (event.key.keysym.sym == SDLK_RALT)
					KEYS["ALT_R"] = 1;
				else if (event.key.keysym.sym == SDLK_RCTRL)
					KEYS["CTRL_R"] = 1;
				else if (event.key.keysym.sym == SDLK_LEFT)
					KEYS["LEFT"] = 1;
				else if (event.key.keysym.sym == SDLK_UP)
					KEYS["UP"] = 1;
				else if (event.key.keysym.sym == SDLK_RIGHT)
					KEYS["RIGHT"] = 1;
				else if (event.key.keysym.sym == SDLK_DOWN)
					KEYS["DOWN"] = 1;
			}
			else if (event.type == SDL_KEYUP)
			{
				if (event.key.keysym.sym == SDLK_ESCAPE)
					KEYS["ESCAPE"] = 0;
				else if (event.key.keysym.sym == SDLK_0)
					KEYS["0"] = 0;
				else if (event.key.keysym.sym == SDLK_1)
					KEYS["1"] = 0;
				else if (event.key.keysym.sym == SDLK_2)
					KEYS["2"] = 0;
				else if (event.key.keysym.sym == SDLK_3)
					KEYS["3"] = 0;
				else if (event.key.keysym.sym == SDLK_4)
					KEYS["4"] = 0;
				else if (event.key.keysym.sym == SDLK_5)
					KEYS["5"] = 0;
				else if (event.key.keysym.sym == SDLK_6)
					KEYS["6"] = 0;
				else if (event.key.keysym.sym == SDLK_7)
					KEYS["7"] = 0;
				else if (event.key.keysym.sym == SDLK_8)
					KEYS["8"] = 0;
				else if (event.key.keysym.sym == SDLK_MINUS)
					KEYS["MINUS"] = 0;
				else if (event.key.keysym.sym == SDLK_EQUALS)
					KEYS["EQUALS"] = 0;
				else if (event.key.keysym.sym == SDLK_AC_BACK)
					KEYS["BACKSPACE"] = 0;
				else if (event.key.keysym.sym == SDLK_TAB)
					KEYS["TAB"] = 0;
				else if (event.key.keysym.sym == SDLK_q)
					KEYS["Q"] = 0;
				else if (event.key.keysym.sym == SDLK_w)
					KEYS["W"] = 0;
				else if (event.key.keysym.sym == SDLK_e)
					KEYS["E"] = 0;
				else if (event.key.keysym.sym == SDLK_r)
					KEYS["R"] = 0;
				else if (event.key.keysym.sym == SDLK_t)
					KEYS["T"] = 0;
				else if (event.key.keysym.sym == SDLK_y)
					KEYS["Y"] = 0;
				else if (event.key.keysym.sym == SDLK_u)
					KEYS["U"] = 0;
				else if (event.key.keysym.sym == SDLK_i)
					KEYS["I"] = 0;
				else if (event.key.keysym.sym == SDLK_o)
					KEYS["O"] = 0;
				else if (event.key.keysym.sym == SDLK_p)
					KEYS["P"] = 0;
				else if (event.key.keysym.sym == SDLK_LEFTBRACKET)
					KEYS["BRACKET_L"] = 0;
				else if (event.key.keysym.sym == SDLK_RIGHTBRACKET)
					KEYS["BRACKET_R"] = 0;
				else if (event.key.keysym.sym == SDLK_BACKSLASH)
					KEYS["BACKSLASH"] = 0;
				else if (event.key.keysym.sym == SDLK_a)
					KEYS["A"] = 0;
				else if (event.key.keysym.sym == SDLK_s)
					KEYS["S"] = 0;
				else if (event.key.keysym.sym == SDLK_d)
					KEYS["D"] = 0;
				else if (event.key.keysym.sym == SDLK_f)
					KEYS["F"] = 0;
				else if (event.key.keysym.sym == SDLK_g)
					KEYS["G"] = 0;
				else if (event.key.keysym.sym == SDLK_h)
					KEYS["H"] = 0;
				else if (event.key.keysym.sym == SDLK_j)
					KEYS["J"] = 0;
				else if (event.key.keysym.sym == SDLK_k)
					KEYS["K"] = 0;
				else if (event.key.keysym.sym == SDLK_l)
					KEYS["L"] = 0;
				else if (event.key.keysym.sym == SDLK_COLON)
					KEYS["COLON"] = 0;
				else if (event.key.keysym.sym == SDLK_QUOTE)
					KEYS["QUOTE"] = 0;
				else if (event.key.keysym.sym == SDLK_RETURN)
					KEYS["ENTER"] = 0;
				else if (event.key.keysym.sym == SDLK_LSHIFT)
					KEYS["SHIFT_L"] = 0;
				else if (event.key.keysym.sym == SDLK_z)
					KEYS["Z"] = 0;
				else if (event.key.keysym.sym == SDLK_x)
					KEYS["X"] = 0;
				else if (event.key.keysym.sym == SDLK_c)
					KEYS["C"] = 0;
				else if (event.key.keysym.sym == SDLK_v)
					KEYS["V"] = 0;
				else if (event.key.keysym.sym == SDLK_b)
					KEYS["B"] = 0;
				else if (event.key.keysym.sym == SDLK_n)
					KEYS["N"] = 0;
				else if (event.key.keysym.sym == SDLK_m)
					KEYS["M"] = 0;
				else if (event.key.keysym.sym == SDLK_COMMA)
					KEYS["COMMA"] = 0;
				else if (event.key.keysym.sym == SDLK_PERIOD)
					KEYS["PERIOD"] = 0;
				else if (event.key.keysym.sym == SDLK_SLASH)
					KEYS["SLASH"] = 0;
				else if (event.key.keysym.sym == SDLK_RSHIFT)
					KEYS["SHIFT_R"] = 0;
				else if (event.key.keysym.sym == SDLK_LCTRL)
					KEYS["CTRL_L"] = 0;
				else if (event.key.keysym.sym == SDLK_LALT)
					KEYS["ALT_L"] = 0;
				else if (event.key.keysym.sym == SDLK_SPACE)
					KEYS["SPACE"] = 0;
				else if (event.key.keysym.sym == SDLK_RALT)
					KEYS["ALT_R"] = 0;
				else if (event.key.keysym.sym == SDLK_RCTRL)
					KEYS["CTRL_R"] = 0;
				else if (event.key.keysym.sym == SDLK_LEFT)
					KEYS["LEFT"] = 0;
				else if (event.key.keysym.sym == SDLK_UP)
					KEYS["UP"] = 0;
				else if (event.key.keysym.sym == SDLK_RIGHT)
					KEYS["RIGHT"] = 0;
				else if (event.key.keysym.sym == SDLK_DOWN)
					KEYS["DOWN"] = 0;
			}
		}

		SDL_RenderClear(gRenderer);

		ExecuteFunction("Update", vector<boost::any> {dt});

		// Present the backbuffer
		SDL_RenderPresent(gRenderer);

		// Calculate frame time
		auto stopTime = std::chrono::high_resolution_clock::now();
		dt = std::chrono::duration<float, std::chrono::milliseconds::period>(stopTime - startTime).count() / 1000.0f;
		dt = clamp(dt, 0, 1000);
	}
	return 0;
}

int initGraphics(std::string windowTitle, int width, int height)
{
	WINDOW_WIDTH = width;
	WINDOW_HEIGHT = height;

	// Initialize SDL components
	SDL_Init(SDL_INIT_VIDEO);
	TTF_Init();

	gWindow = SDL_CreateWindow(windowTitle.c_str(), 40, 40, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN | SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	gRenderer = SDL_CreateRenderer(gWindow, -1, 0);

	//Get window surface
	gScreenSurface = SDL_GetWindowSurface(gWindow);

	ExecuteFunction("Start", vector<boost::any> {});

	updateLoop();

	cleanupGraphics();

	return 0;
}

#endif
