#include "opengl_tutorials_win32_framework.h"

// ----------------------------------------------------------------------------------------------------------------------------

CString ModuleDirectory, ErrorLog;

bool wgl_context_forward_compatible = false;

int gl_version = 0, use_gl_version = 21, gl_max_texture_size = 0, gl_max_texture_max_anisotropy_ext = 0;

// ----------------------------------------------------------------------------------------------------------------------------

CTexture::CTexture()
{
	TextureID = 0;
}

CTexture::~CTexture()
{
}

CTexture::operator GLuint ()
{
	return TextureID;
}

void CTexture::Delete()
{
	glDeleteTextures(1, &TextureID);
	TextureID = 0;
}

bool CTexture::LoadTexture2D(char *Texture2DFileName)
{
	CString FileName = ModuleDirectory + Texture2DFileName;
	CString ErrorText = "Error loading file " + FileName + "! -> ";

	FREE_IMAGE_FORMAT fif = FreeImage_GetFileType(FileName);

	if(fif == FIF_UNKNOWN)
	{
		fif = FreeImage_GetFIFFromFilename(FileName);
	}

	if(fif == FIF_UNKNOWN)
	{
		ErrorLog.Append(ErrorText + "fif is FIF_UNKNOWN" + "\r\n");
		return false;
	}

	FIBITMAP *dib = NULL;

	if(FreeImage_FIFSupportsReading(fif))
	{
		dib = FreeImage_Load(fif, FileName);
	}

	if(dib == NULL)
	{
		ErrorLog.Append(ErrorText + "dib is NULL" + "\r\n");
		return false;
	}

	int Width = FreeImage_GetWidth(dib), oWidth = Width;
	int Height = FreeImage_GetHeight(dib), oHeight = Height;
	int Pitch = FreeImage_GetPitch(dib);
	int BPP = FreeImage_GetBPP(dib);

	if(Width == 0 || Height == 0)
	{
		ErrorLog.Append(ErrorText + "Width or Height is 0" + "\r\n");
		return false;
	}

	if(Width > gl_max_texture_size) Width = gl_max_texture_size;
	if(Height > gl_max_texture_size) Height = gl_max_texture_size;

	if(!GLEW_ARB_texture_non_power_of_two)
	{
		Width = 1 << (int)floor((log((float)Width) / log(2.0f)) + 0.5f); 
		Height = 1 << (int)floor((log((float)Height) / log(2.0f)) + 0.5f);
	}

	if(Width != oWidth || Height != oHeight)
	{
		FIBITMAP *rdib = FreeImage_Rescale(dib, Width, Height, FILTER_BICUBIC);

		FreeImage_Unload(dib);

		if((dib = rdib) == NULL)
		{
			ErrorLog.Append(ErrorText + "rdib is NULL" + "\r\n");
			return false;
		}

		Pitch = FreeImage_GetPitch(dib);
	}

	BYTE *Data = FreeImage_GetBits(dib);

	if(Data == NULL)
	{
		ErrorLog.Append(ErrorText + "Data is NULL" + "\r\n");
		return false;
	}

	GLenum Format = 0;

	if(BPP == 32) Format = GL_BGRA;
	if(BPP == 24) Format = GL_BGR;

	if(Format == 0)
	{
		FreeImage_Unload(dib);
		ErrorLog.Append(ErrorText + "Format is 0" + "\r\n");
		return false;
	}

	if(gl_version < 12)
	{
		if(Format == GL_BGRA) Format = GL_RGBA;
		if(Format == GL_BGR) Format = GL_RGB;

		int bpp = BPP / 8;

		BYTE *line = Data;

		for(int y = 0; y < Height; y++)
		{
			BYTE *pixel = line;

			for(int x = 0; x < Width; x++)
			{
				BYTE Temp = pixel[0];
				pixel[0] = pixel[2];
				pixel[2] = Temp;

				pixel += bpp;
			}

			line += Pitch;
		}
	}

	glDeleteTextures(1, &TextureID);

	glGenTextures(1, &TextureID);

	glBindTexture(GL_TEXTURE_2D, TextureID);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_version >= 14 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	if(GLEW_EXT_texture_filter_anisotropic)
	{
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_max_texture_max_anisotropy_ext);
	}

	if(gl_version >= 14 && gl_version <= 21)
	{
		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
	}

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Width, Height, 0, Format, GL_UNSIGNED_BYTE, Data);

	if(gl_version >= 30)
	{
		glGenerateMipmap(GL_TEXTURE_2D);
	}

	glBindTexture(GL_TEXTURE_2D, 0);

	FreeImage_Unload(dib);

	return true;
}

// ----------------------------------------------------------------------------------------------------------------------------

#pragma warning(disable : 4996)

CObject::CObject()
{
	SetDefaults();
}

CObject::~CObject()
{
}

void CObject::CreateSphere(float Radius, int Resolution, bool InvertNormals)
{
	if(Resolution < 16) Resolution = 16;

	if(Resolution % 4) Resolution += 4 - Resolution % 4;

	TrianglesCount = Resolution * (Resolution / 4  - 1) * 4 + Resolution * 2;

	AllocateMemory();

	float angle = (float)M_PI * 2.0f / Resolution;

	vec3 a, b, c, d;
	vec2 tca, tcb, tcc, tcd;

	int i = 0;

	float r = (float)Resolution, r4 = (float)Resolution / 2.0f;

	for(int y = 0; y < Resolution / 4; y++)
	{
		for(int xz = 0; xz < Resolution; xz++)
		{
			if(y < Resolution / 4 - 1)
			{
				a = vec3(- sin(angle * (xz + 0)) * cos(angle * (y + 0)), sin(angle * (y + 0)), - cos(angle * (xz + 0)) * cos(angle * (y + 0)));
				b = vec3(- sin(angle * (xz + 1)) * cos(angle * (y + 0)), sin(angle * (y + 0)), - cos(angle * (xz + 1)) * cos(angle * (y + 0)));
				c = vec3(- sin(angle * (xz + 1)) * cos(angle * (y + 1)), sin(angle * (y + 1)), - cos(angle * (xz + 1)) * cos(angle * (y + 1)));
				d = vec3(- sin(angle * (xz + 0)) * cos(angle * (y + 1)), sin(angle * (y + 1)), - cos(angle * (xz + 0)) * cos(angle * (y + 1)));

				tca = TexCoords[i] = vec2((xz + 0) / r, 0.5f + (y + 0) / r4);
				tcb = TexCoords[i] = vec2((xz + 1) / r, 0.5f + (y + 0) / r4);
				tcc = TexCoords[i] = vec2((xz + 1) / r, 0.5f + (y + 1) / r4);
				tcd = TexCoords[i] = vec2((xz + 0) / r, 0.5f + (y + 1) / r4);

				TexCoords[i] = tca; Normals[i] = a; Vertices[i++] = a * Radius;
				TexCoords[i] = tcb; Normals[i] = b; Vertices[i++] = b * Radius;
				TexCoords[i] = tcc; Normals[i] = c; Vertices[i++] = c * Radius;

				TexCoords[i] = tcc; Normals[i] = c; Vertices[i++] = c * Radius;
				TexCoords[i] = tcd; Normals[i] = d; Vertices[i++] = d * Radius;
				TexCoords[i] = tca; Normals[i] = a; Vertices[i++] = a * Radius;

				a.y = -a.y;
				b.y = -b.y;
				c.y = -c.y;
				d.y = -d.y;

				tca.y = 1.0f - tca.y;
				tcb.y = 1.0f - tcb.y;
				tcc.y = 1.0f - tcc.y;
				tcd.y = 1.0f - tcd.y;

				TexCoords[i] = tcd; Normals[i] = d; Vertices[i++] = d * Radius;
				TexCoords[i] = tcc; Normals[i] = c; Vertices[i++] = c * Radius;
				TexCoords[i] = tcb; Normals[i] = b; Vertices[i++] = b * Radius;

				TexCoords[i] = tcb; Normals[i] = b; Vertices[i++] = b * Radius;
				TexCoords[i] = tca; Normals[i] = a; Vertices[i++] = a * Radius;
				TexCoords[i] = tcd; Normals[i] = d; Vertices[i++] = d * Radius;
			}
			else
			{
				a = vec3(- sin(angle * (xz + 0)) * cos(angle * (y + 0)), sin(angle * (y + 0)), - cos(angle * (xz + 0)) * cos(angle * (y + 0)));
				b = vec3(- sin(angle * (xz + 1)) * cos(angle * (y + 0)), sin(angle * (y + 0)), - cos(angle * (xz + 1)) * cos(angle * (y + 0)));
				c = vec3(0.0f, 1.0f, 0.0f);

				tca = TexCoords[i] = vec2((xz + 0) / r, 0.5f + (y + 0) / r4);
				tcb = TexCoords[i] = vec2((xz + 1) / r, 0.5f + (y + 0) / r4);
				tcc = TexCoords[i] = vec2((xz + 0.5f) / r, 1.0f);

				TexCoords[i] = tca; Normals[i] = a; Vertices[i++] = a * Radius;
				TexCoords[i] = tcb; Normals[i] = b; Vertices[i++] = b * Radius;
				TexCoords[i] = tcc; Normals[i] = c; Vertices[i++] = c * Radius;

				a.y = -a.y;
				b.y = -b.y;
				c.y = -c.y;

				tca.y = 1.0f - tca.y;
				tcb.y = 1.0f - tcb.y;
				tcc.y = 1.0f - tcc.y;

				TexCoords[i] = tca; Normals[i] = a; Vertices[i++] = a * Radius;
				TexCoords[i] = tcc; Normals[i] = c; Vertices[i++] = c * Radius;
				TexCoords[i] = tcb; Normals[i] = b; Vertices[i++] = b * Radius;
			}
		}
	}

	if(InvertNormals)
	{
		for(int i = 0; i < TrianglesCount * 3; i++)
		{
			Normals[i] = -Normals[i];
		}
	}

	Min = vec3(-Radius, -Radius, -Radius);
	Max = vec3(Radius, Radius, Radius);
}

void CObject::Destroy()
{
	if(gl_version >= 15)
	{
		glDeleteBuffers(3, VBO);
	}

	Texture.Delete();

	delete [] TexCoords;
	delete [] Normals;
	delete [] Vertices;

	SetDefaults();
}

void CObject::InitVertexBuffers()
{
	if(gl_version >= 15)
	{
		glDeleteBuffers(3, VBO);

		glGenBuffers(3, VBO);

		glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
		glBufferData(GL_ARRAY_BUFFER, TrianglesCount * 3 * 2 * 4, TexCoords, GL_STATIC_DRAW);

		glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
		glBufferData(GL_ARRAY_BUFFER, TrianglesCount * 3 * 3 * 4, Normals, GL_STATIC_DRAW);

		glBindBuffer(GL_ARRAY_BUFFER, VBO[2]);
		glBufferData(GL_ARRAY_BUFFER, TrianglesCount * 3 * 3 * 4, Vertices, GL_STATIC_DRAW);

		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}
}

bool CObject::Load(char *Directory, char *ObjFileName)
{
	char *ObjSource;
	long ObjLength;

	if(!ReadSource(Directory, ObjFileName, &ObjSource, ObjLength)) return false;

	char *Line, *End = ObjSource + ObjLength;
	float x, y, z;
	int texcoordscount = 0, normalscount = 0, verticescount = 0;
	int i1, i2, i3, i4, i5, i6, i7, i8, i9;
	int v = 0, n = 0, t = 0, T = 0;

	Line = ObjSource;

	while(Line < End)
	{
		while(Line < End && (*Line == ' ' || *Line == '\t')) Line++;

		if(Line[0] == 'm' && Line[1] == 't' && Line[2] == 'l' && Line[3] == 'l' && Line[4] == 'i' && Line[5] == 'b' && (Line[6] == ' ' || Line[6] == '\t'))
		{
			char *MtlFileName = Line + 6;

			while(MtlFileName < End && (*MtlFileName == ' ' || *MtlFileName == '\t')) MtlFileName++;

			if(!ParseMtl(Directory, MtlFileName))
			{
				delete [] ObjSource;
				return false;
			}
		}
		else if(sscanf(Line, "vt %f %f", &x, &y) == 2)
		{
			texcoordscount++;
		}
		else if(sscanf(Line, "vn %f %f %f", &x, &y, &z) == 3)
		{
			normalscount++;
		}
		else if(sscanf(Line, "v %f %f %f", &x, &y, &z) == 3)
		{
			verticescount++;
		}
		else if(sscanf(Line, "f %d/%d/%d %d/%d/%d %d/%d/%d", &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8, &i9) == 9)
		{
			TrianglesCount++;
		}
		else if(sscanf(Line, "f %d//%d %d//%d %d//%d", &i1, &i2, &i3, &i4, &i5, &i6) == 6)
		{
			TrianglesCount++;
		}
		else if(sscanf(Line, "f %d/%d %d/%d %d/%d", &i1, &i2, &i3, &i4, &i5, &i6) == 6)
		{
			TrianglesCount++;
		}
		else if(sscanf(Line, "f %d %d %d", &i1, &i2, &i3) == 3)
		{
			TrianglesCount++;
		}

		while(Line < End && *Line != 0) Line++;
		while(Line < End && *Line == 0) Line++;
	}

	vec2 *texcoords = NULL;
	vec3 *normals = NULL;
	vec3 *vertices = NULL;

	if(texcoordscount > 0) texcoords = new vec2[texcoordscount];
	if(normalscount > 0) normals = new vec3[normalscount];
	if(verticescount > 0) vertices = new vec3[verticescount];

	AllocateMemory();

	Line = ObjSource;

	while(Line < End)
	{
		while(Line < End && (*Line == ' ' || *Line == '\t')) Line++;

		if(sscanf(Line, "vt %f %f", &x, &y) == 2)
		{
			texcoords[t++] = vec2(x, y);
		}
		else if(sscanf(Line, "vn %f %f %f", &x, &y, &z) == 3)
		{
			normals[n++] = vec3(x, y ,z);
		}
		else if(sscanf(Line, "v %f %f %f", &x, &y, &z) == 3)
		{
			vertices[v++] = vec3(x, y ,z);
		}
		else if(sscanf(Line, "f %d/%d/%d %d/%d/%d %d/%d/%d", &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8, &i9) == 9)
		{
			TexCoords[T] = texcoords[i2 - 1];
			Normals[T] = normals[i3 - 1];
			Vertices[T++] = vertices[i1 - 1];
			TexCoords[T] = texcoords[i5 - 1];
			Normals[T] = normals[i6 - 1];
			Vertices[T++] = vertices[i4 - 1];
			TexCoords[T] = texcoords[i8 - 1];
			Normals[T] = normals[i9 - 1];
			Vertices[T++] = vertices[i7 - 1];
		}
		else if(sscanf(Line, "f %d//%d %d//%d %d//%d", &i1, &i2, &i3, &i4, &i5, &i6) == 6)
		{
			Normals[T] = normals[i2 - 1];
			Vertices[T++] = vertices[i1 - 1];
			Normals[T] = normals[i4 - 1];
			Vertices[T++] = vertices[i3 - 1];
			Normals[T] = normals[i6 - 1];
			Vertices[T++] = vertices[i5 - 1];
		}
		else if(sscanf(Line, "f %d/%d %d/%d %d/%d", &i1, &i2, &i3, &i4, &i5, &i6) == 6)
		{
			if(texcoords != NULL && i1 - 1 < texcoordscount) TexCoords[T] = texcoords[i1 - 1];
			if(texcoords != NULL && i2 - 1 < texcoordscount) TexCoords[T] = texcoords[i2 - 1];
			if(normals != NULL && i1 - 1 < normalscount) Normals[T] = normals[i1 - 1];
			if(normals != NULL && i2 - 1 < normalscount) Normals[T] = normals[i2 - 1];
			Vertices[T++] = vertices[i1 - 1];
			if(texcoords != NULL && i3 - 1 < texcoordscount) TexCoords[T] = texcoords[i3 - 1];
			if(texcoords != NULL && i4 - 1 < texcoordscount) TexCoords[T] = texcoords[i4 - 1];
			if(normals != NULL && i3 - 1 < normalscount) Normals[T] = normals[i3 - 1];
			if(normals != NULL && i4 - 1 < normalscount) Normals[T] = normals[i4 - 1];
			Vertices[T++] = vertices[i3 - 1];
			if(texcoords != NULL && i5 - 1 < texcoordscount) TexCoords[T] = texcoords[i5 - 1];
			if(texcoords != NULL && i6 - 1 < texcoordscount) TexCoords[T] = texcoords[i6 - 1];
			if(normals != NULL && i5 - 1 < normalscount) Normals[T] = normals[i5 - 1];
			if(normals != NULL && i6 - 1 < normalscount) Normals[T] = normals[i6 - 1];
			Vertices[T++] = vertices[i5 - 1];
		}
		else if(sscanf(Line, "f %d %d %d", &i1, &i2, &i3) == 3)
		{
			if(texcoords != NULL && i1 - 1 < texcoordscount) TexCoords[T] = texcoords[i1 - 1];
			if(normals != NULL && i1 - 1 < normalscount) Normals[T] = normals[i1 - 1];
			Vertices[T++] = vertices[i1 - 1];
			if(texcoords != NULL && i2 - 1 < texcoordscount) TexCoords[T] = texcoords[i2 - 1];
			if(normals != NULL && i2 - 1 < normalscount) Normals[T] = normals[i2 - 1];
			Vertices[T++] = vertices[i2 - 1];
			if(texcoords != NULL && i3 - 1 < texcoordscount) TexCoords[T] = texcoords[i3 - 1];
			if(normals != NULL && i3 - 1 < normalscount) Normals[T] = normals[i3 - 1];
			Vertices[T++] = vertices[i3 - 1];
		}

		while(Line < End && *Line != 0) Line++;
		while(Line < End && *Line == 0) Line++;
	}

	delete [] texcoords;
	delete [] normals;
	delete [] vertices;

	delete [] ObjSource;

	if(normalscount == 0)
	{
		for(int i = 0; i < TrianglesCount * 3; i += 3)
		{
			vec3 a = Vertices[i + 1] - Vertices[i];
			vec3 b = Vertices[i + 2] - Vertices[i];

			vec3 normal = normalize(cross(a, b));

			Normals[i + 0] = normal;
			Normals[i + 1] = normal;
			Normals[i + 2] = normal;
		}
	}

	GetMinMax();

	return true;
}

void CObject::Rotate(float Angle, const vec3 &Axis)
{
	mat4x4 Rotation = RotationMatrix(Angle, Axis);

	for(int i = 0; i < TrianglesCount * 3; i++)
	{
		Normals[i] = Rotation * Normals[i];
		Vertices[i] = Rotation * Vertices[i];
	}

	GetMinMax();
}

void CObject::Scale(float ScaleFactor)
{
	for(int i = 0; i < TrianglesCount * 3; i++)
	{
		Vertices[i] *= ScaleFactor;
	}

	Min *= ScaleFactor;
	Max *= ScaleFactor;
}

void CObject::Translate(const vec3 &Translation)
{
	for(int i = 0; i < TrianglesCount * 3; i++)
	{
		Vertices[i] += Translation;
	}

	Min += Translation;
	Max += Translation;
}

void CObject::AllocateMemory()
{
	delete [] TexCoords;
	delete [] Normals;
	delete [] Vertices;

	TexCoords = new vec2[TrianglesCount * 3];
	Normals = new vec3[TrianglesCount * 3];
	Vertices = new vec3[TrianglesCount * 3];
}

void CObject::GetMinMax()
{
	for(int i = 0; i < TrianglesCount * 3; i++)
	{
		if(i == 0)
		{
			Min = Max = Vertices[i];
		}
		else
		{
			if(Min.x > Vertices[i].x) Min.x = Vertices[i].x;
			if(Min.y > Vertices[i].y) Min.y = Vertices[i].y;
			if(Min.z > Vertices[i].z) Min.z = Vertices[i].z;
			if(Max.x < Vertices[i].x) Max.x = Vertices[i].x;
			if(Max.y < Vertices[i].y) Max.y = Vertices[i].y;
			if(Max.z < Vertices[i].z) Max.z = Vertices[i].z;
		}
	}
}

bool CObject::ParseMtl(char *Directory, char *MtlFileName)
{
	char *MtlSource;
	long MtlLength;

	if(!ReadSource(Directory, MtlFileName, &MtlSource, MtlLength)) return false;

	char *Line = MtlSource, *End = MtlSource + MtlLength;

	bool Error = false;

	while(Line < End)
	{
		while(Line < End && (*Line == ' ' || *Line == '\t')) Line++;

		if(Line[0] == 'm' && Line[1] == 'a' && Line[2] == 'p' && Line[3] == '_' && Line[4] == 'K' && Line[5] == 'a' && (Line[6] == ' ' || Line[6] == '\t'))
		{
			char *Texture2DFileName = Line + 6;

			while(Texture2DFileName < End && (*Texture2DFileName == ' ' || *Texture2DFileName == '\t')) Texture2DFileName++;

			Error |= !Texture.LoadTexture2D(CString(Directory) + Texture2DFileName);
		}

		while(Line < End && *Line != 0) Line++;
		while(Line < End && *Line == 0) Line++;
	}

	delete [] MtlSource;

	return !Error;
}

bool CObject::ReadSource(char *Directory, char *FileName, char **Source, long &Length)
{
	CString PathFileName = ModuleDirectory + Directory + FileName;

	FILE *File;

	if(fopen_s(&File, PathFileName, "rb") != 0)
	{
		ErrorLog.Append("Error loading file " + PathFileName + "!\r\n");
		return false;
	}

	fseek(File, 0, SEEK_END);
	Length = ftell(File);
	fseek(File, 0, SEEK_SET);
	*Source = new char[Length + 1];
	fread(*Source, 1, Length, File);
	(*Source)[Length] = 0;

	fclose(File);

	for(long i = 0; i < Length; i++)
	{
		if((*Source)[i] == '\r' || (*Source)[i] == '\n') (*Source)[i] = 0;
	}

	return true;
}

void CObject::SetDefaults()
{
	Movable = true;
	CullFace = true;
	FrontFace = GL_CCW;

	TrianglesCount = 0;

	for(int i = 0; i < 3; i++)
	{
		VBO[i] = 0;
	}

	TexCoords = NULL;
	Normals = NULL;
	Vertices = NULL;

	Color = vec3(1.0f, 1.0f, 1.0f);

	Position = vec3(0.0f, 0.0f, 0.0f);
}

// ----------------------------------------------------------------------------------------------------------------------------

CShaderProgram::CShaderProgram()
{
	SetDefaults();
}

CShaderProgram::~CShaderProgram()
{
}

CShaderProgram::operator GLuint ()
{
	return Program;
}

void CShaderProgram::Delete()
{
	delete [] UniformLocations;

	glDetachShader(Program, VertexShader);
	glDetachShader(Program, FragmentShader);

	glDeleteShader(VertexShader);
	glDeleteShader(FragmentShader);

	glDeleteProgram(Program);

	SetDefaults();
}

bool CShaderProgram::Load(char *VertexShaderFileName, char *FragmentShaderFileName)
{
	if(UniformLocations || VertexShader || FragmentShader || Program)
	{
		Delete();
	}

	bool Error = false;

	Error |= ((VertexShader = LoadShader(GL_VERTEX_SHADER, VertexShaderFileName)) == 0);

	Error |= ((FragmentShader = LoadShader(GL_FRAGMENT_SHADER, FragmentShaderFileName)) == 0);

	if(Error)
	{
		Delete();
		return false;
	}

	Program = glCreateProgram();
	glAttachShader(Program, VertexShader);
	glAttachShader(Program, FragmentShader);
	glLinkProgram(Program);

	int Param = 0;
	glGetProgramiv(Program, GL_LINK_STATUS, &Param);

	if(Param == GL_FALSE)
	{
		ErrorLog.Append("Error linking program (%s, %s)!\r\n", VertexShaderFileName, FragmentShaderFileName);

		int InfoLogLength = 0;
		glGetProgramiv(Program, GL_INFO_LOG_LENGTH, &InfoLogLength);
	
		if(InfoLogLength > 0)
		{
			char *InfoLog = new char[InfoLogLength];
			int CharsWritten  = 0;
			glGetProgramInfoLog(Program, InfoLogLength, &CharsWritten, InfoLog);
			ErrorLog.Append(InfoLog);
			delete [] InfoLog;
		}

		Delete();

		return false;
	}

	return true;
}

GLuint CShaderProgram::LoadShader(GLenum Type, char *ShaderFileName)
{
	CString FileName = ModuleDirectory + ShaderFileName;

	FILE *File;

	if(fopen_s(&File, FileName, "rb") != 0)
	{
		ErrorLog.Append("Error loading file " + FileName + "!\r\n");
		return 0;
	}

	fseek(File, 0, SEEK_END);
	long Size = ftell(File);
	fseek(File, 0, SEEK_SET);
	char *Source = new char[Size + 1];
	fread(Source, 1, Size, File);
	fclose(File);
	Source[Size] = 0;

	GLuint Shader;

	Shader = glCreateShader(Type);
	glShaderSource(Shader, 1, (const char**)&Source, NULL);
	delete [] Source;
	glCompileShader(Shader);

	int Param = 0;
	glGetShaderiv(Shader, GL_COMPILE_STATUS, &Param);

	if(Param == GL_FALSE)
	{
		ErrorLog.Append("Error compiling shader %s!\r\n", ShaderFileName);

		int InfoLogLength = 0;
		glGetShaderiv(Shader, GL_INFO_LOG_LENGTH, &InfoLogLength);
	
		if(InfoLogLength > 0)
		{
			char *InfoLog = new char[InfoLogLength];
			int CharsWritten  = 0;
			glGetShaderInfoLog(Shader, InfoLogLength, &CharsWritten, InfoLog);
			ErrorLog.Append(InfoLog);
			delete [] InfoLog;
		}

		glDeleteShader(Shader);

		return 0;
	}

	return Shader;
}

void CShaderProgram::SetDefaults()
{
	UniformLocations = NULL;
	VertexShader = 0;
	FragmentShader = 0;
	Program = 0;
}

// ----------------------------------------------------------------------------------------------------------------------------

CCamera::CCamera()
{
	View = NULL;

	Reference = vec3(0.0f, 0.0f, 0.0f);
	Position = vec3(0.0f, 0.0f, 5.0f);

	X = vec3(1.0f, 0.0f, 0.0f);
	Y = vec3(0.0f, 1.0f, 0.0f);
	Z = vec3(0.0f, 0.0f, 1.0f);
}

CCamera::~CCamera()
{
}

void CCamera::CalculateViewMatrix()
{
	if(View)
	{
		*View = ViewMatrix(X, Y, Z, Position);
	}
}

void CCamera::LookAt(vec3 Reference, vec3 Position, bool RotateAroundReference)
{
	this->Reference = Reference;
	this->Position = Position;

	Z = normalize(Position - Reference);
	X = normalize(cross(vec3(0.0f, 1.0f, 0.0f), Z));
	Y = cross(Z, X);

	if(!RotateAroundReference)
	{
		this->Reference = this->Position;
		this->Position += Z * 0.05f;
	}

	CalculateViewMatrix();
}

void CCamera::Move(vec3 Movement)
{
	Reference += Movement;
	Position += Movement;

	CalculateViewMatrix();
}

vec3 CCamera::OnKeys(BYTE Keys, float FrameTime)
{
	float Speed = 5.0f;

	if(Keys & 0x40) // SHIFT
	{
		Speed *= 2.0f;
	}

	float Distance = Speed * FrameTime;

	vec3 Up(0.0f, 1.0f, 0.0f);
	vec3 Right = X;
	vec3 Forward = cross(Up, Right);

	Up *= Distance;
	Right *= Distance;
	Forward *= Distance;

	vec3 Movement;

	if(Keys & 0x01) // W
	{
		Movement += Forward;
	}

	if(Keys & 0x02) // S
	{
		Movement -= Forward;
	}

	if(Keys & 0x04) // A
	{
		Movement -= Right;
	}

	if(Keys & 0x08) // D
	{
		Movement += Right;
	}

	if(Keys & 0x10) // R
	{
		Movement += Up;
	}

	if(Keys & 0x20) // F
	{
		Movement -= Up;
	}

	return Movement;
}

void CCamera::OnMouseMove(int dx, int dy)
{
	float sensitivity = 0.25f;

	float hangle = (float)dx * sensitivity;
	float vangle = (float)dy * sensitivity;

	Position -= Reference;

	Y = rotate(Y, vangle, X);
	Z = rotate(Z, vangle, X);

	if(Y.y < 0.0f)
	{
		Z = vec3(0.0f, Z.y > 0.0f ? 1.0f : -1.0f, 0.0f);
		Y = cross(Z, X);
	}

	X = rotate(X, hangle, vec3(0.0f, 1.0f, 0.0f));
	Y = rotate(Y, hangle, vec3(0.0f, 1.0f, 0.0f));
	Z = rotate(Z, hangle, vec3(0.0f, 1.0f, 0.0f));

	Position = Reference + Z * length(Position);

	CalculateViewMatrix();
}

void CCamera::OnMouseWheel(float zDelta)
{
	Position -= Reference;

	if(zDelta < 0 && length(Position) < 500.0f)
	{
		Position += Position * 0.1f;
	}

	if(zDelta > 0 && length(Position) > 0.05f)
	{
		Position -= Position * 0.1f;
	}

	Position += Reference;

	CalculateViewMatrix();
}

void CCamera::SetViewMatrixPointer(float *View)
{
	this->View = (mat4x4*)View;

	CalculateViewMatrix();
}

CCamera Camera;

// ----------------------------------------------------------------------------------------------------------------------------

COpenGLRenderer::COpenGLRenderer()
{
	ShowAxisGrid = true;
	WireFrame = false;
	Texturing = true;

	SelectedObject = -1;

	Camera.SetViewMatrixPointer(&View);
}

COpenGLRenderer::~COpenGLRenderer()
{
}

bool COpenGLRenderer::Init()
{
	/*if(gl_version < 21)
	{
		ErrorLog.Set("OpenGL 2.1 not supported!");
		return false;
	}*/

	bool Error = false;

	ObjectsCount = 6;

	Objects = new CObject[ObjectsCount];

	Error |= !Objects[0].Load("Models\\Thor\\", "thor.obj");
	Error |= !Objects[1].Load("Models\\Spongebob\\", "spongebob_bind.obj");
	Error |= !Objects[2].Load("Models\\Alien2\\", "alien2.obj");
	Error |= !Objects[3].Load("Models\\Teapot\\", "teapot.obj");

	Error |= !Objects[4].Texture.LoadTexture2D("earthmap.jpg");
	
	if(gl_version >= 21)
	{
		Error |= !Shader.Load("glsl120shader.vs", "glsl120shader.fs");
	}

	if(Error)
	{
		return false;
	}

	Objects[0].Rotate(-90.0f, vec3(0.0f, 1.0f, 0.0f));
	Objects[0].Scale(1.75f / (Objects[0].Max.y - Objects[0].Min.y));
	Objects[0].Translate(vec3(-(Objects[0].Min.x + Objects[0].Max.x) / 2.0f, -Objects[0].Min.y, -(Objects[0].Min.z + Objects[0].Max.z) / 2.0f));
	Objects[0].Position = vec3(0.5f, 0.0f, 0.0f);

	Objects[1].Scale(0.875f / (Objects[1].Max.y - Objects[1].Min.y));
	Objects[1].Translate(vec3(-(Objects[1].Min.x + Objects[1].Max.x) / 2.0f, -Objects[1].Min.y, -(Objects[1].Min.z + Objects[1].Max.z) / 2.0f));
	Objects[1].Position = vec3(-0.5f, 0.0f, 0.0f);

	Objects[2].Scale(1.0f / (Objects[2].Max.y - Objects[2].Min.y));
	Objects[2].Translate(vec3(-(Objects[2].Min.x + Objects[2].Max.x) / 2.0f, -Objects[2].Min.y, -(Objects[2].Min.z + Objects[2].Max.z) / 2.0f));
	Objects[2].Position = vec3(0.0f, 0.0f, -2.5f);

	Objects[3].Color = vec3(1.0f, 0.5f, 0.0f);
	Objects[3].CullFace = false;
	Objects[3].FrontFace = GL_CW;
	Objects[3].Scale(0.25f / (Objects[3].Max.y - Objects[3].Min.y));
	Objects[3].Translate(vec3(-(Objects[3].Min.x + Objects[3].Max.x) / 2.0f, -Objects[3].Min.y, -(Objects[3].Min.z + Objects[3].Max.z) / 2.0f));
	Objects[3].Position = vec3(0.0f, 0.0f, 0.0f);

	Objects[4].CreateSphere(0.5f, 32);
	Objects[4].Position = vec3(0.0f, 0.5f, -1.0f);

	Objects[5].CreateSphere(0.03125f, 16, true);
	Objects[5].Position = vec3(0.0f, 2.0f, 1.0f);

	LightObjectID = 5;

	for(int i = 0; i < ObjectsCount; i++)
	{
		Objects[i].InitVertexBuffers();
	}

	if(gl_version >= 21)
	{
		Shader.UniformLocations = new GLuint[1];
		Shader.UniformLocations[0] = glGetUniformLocation(Shader, "Texturing");
	}

	GLfloat LightModelAmbient[] = {0.0f, 0.0f, 0.0f, 1.0f};
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, LightModelAmbient);

	GLfloat LightAmbient[] = {0.25f, 0.25f, 0.25f, 1.0f};
	glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient);

	GLfloat LightDiffuse[] = {0.75f, 0.75f, 0.75f, 1.0f};
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse);

	GLfloat MaterialAmbient[] = {0.25f, 0.25f, 0.25f, 1.0f};
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, MaterialAmbient);

	GLfloat MaterialDiffuse[] = {0.75f, 0.75f, 0.75f, 1.0f};
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, MaterialDiffuse);

	glEnable(GL_LIGHT0);
	glEnable(GL_COLOR_MATERIAL);

	Camera.LookAt(vec3(0.0f, 0.875f, 0.0f), vec3(0.0f, 0.875f, 2.5f), true);

	return true;
}

void COpenGLRenderer::Render(float FrameTime)
{
	int gl_version = use_gl_version < ::gl_version ? use_gl_version : ::gl_version;

	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf(&View);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glEnable(GL_DEPTH_TEST);

	// render axises and grid -------------------------------------------------------------------------------------------------

	if(ShowAxisGrid)
	{
		glLineWidth(2.0f);

		glBegin(GL_LINES);

		glColor3f(1.0f, 0.0f, 0.0f);

		glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f);
		glVertex3f(1.0f, 0.1f, 0.0f); glVertex3f(1.1f, -0.1f, 0.0f);
		glVertex3f(1.1f, 0.1f, 0.0f); glVertex3f(1.0f, -0.1f, 0.0f);

		glColor3f(0.0f, 1.0f, 0.0f);

		glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f);
		glVertex3f(-0.05f, 1.25f, 0.0f); glVertex3f(0.0f, 1.15f, 0.0f);
		glVertex3f(0.05f,1.25f, 0.0f); glVertex3f(0.0f, 1.15f, 0.0f);
		glVertex3f(0.0f,1.15f, 0.0f); glVertex3f(0.0f, 1.05f, 0.0f);

		glColor3f(0.0f, 0.0f, 1.0f);

		glVertex3f(0.0f,0.0f,0.0f); glVertex3f(0.0f, 0.0f, 1.0f);
		glVertex3f(-0.05f,0.1f,1.05f); glVertex3f(0.05f, 0.1f, 1.05f);
		glVertex3f(0.05f,0.1f,1.05f); glVertex3f(-0.05f, -0.1f, 1.05f);
		glVertex3f(-0.05f,-0.1f,1.05f); glVertex3f(0.05f, -0.1f, 1.05f);

		glEnd();

		glLineWidth(1.0f);

		glColor3f(1.0f, 1.0f, 1.0f);

		glBegin(GL_LINES);

		float d = 50.0f;

		for(float i = -d; i <= d; i += 1.0f)
		{
			glVertex3f(i, 0.0f, -d);
			glVertex3f(i, 0.0f, d);
			glVertex3f(-d, 0.0f, i);
			glVertex3f(d, 0.0f, i);
		}

		glEnd();
	}

	// render objects ---------------------------------------------------------------------------------------------------------

	glEnable(GL_LIGHTING);

	glLightfv(GL_LIGHT0, GL_POSITION, &vec4(Objects[LightObjectID].Position, 1.0f));

	if(WireFrame)
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}

	if(gl_version >= 21)
	{
		glUseProgram(Shader);
	}

	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_VERTEX_ARRAY);

	for(int i = 0; i < ObjectsCount; i++)
	{
		if(Objects[i].TrianglesCount <= 0) continue;

		glMatrixMode(GL_MODELVIEW);
		glLoadMatrixf(&View);
		glTranslatef(Objects[i].Position.x, Objects[i].Position.y, Objects[i].Position.z);

		if(!WireFrame && Objects[i].CullFace)
		{
			glEnable(GL_CULL_FACE);
		}

		glColor3fv(&Objects[i].Color);

		bool Texturing = this->Texturing && Objects[i].Texture;

		if(Texturing)
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, Objects[i].Texture);
		}
		
		if(gl_version >= 21)
		{
			glUniform1i(Shader.UniformLocations[0], Texturing ? 1 : 0);
		}

		if(gl_version >= 15)
		{
			glBindBuffer(GL_ARRAY_BUFFER, Objects[i].VBO[0]);
			glTexCoordPointer(2, GL_FLOAT, 0, NULL);

			glBindBuffer(GL_ARRAY_BUFFER, Objects[i].VBO[1]);
			glNormalPointer(GL_FLOAT, 0, NULL);

			glBindBuffer(GL_ARRAY_BUFFER, Objects[i].VBO[2]);
			glVertexPointer(3, GL_FLOAT, 0, NULL);
		}
		else
		{
			glTexCoordPointer(2, GL_FLOAT, 0, Objects[i].TexCoords);
			glNormalPointer(GL_FLOAT, 0, Objects[i].Normals);
			glVertexPointer(3, GL_FLOAT, 0, Objects[i].Vertices);
		}

		glFrontFace(Objects[i].FrontFace);

		glDrawArrays(GL_TRIANGLES, 0, Objects[i].TrianglesCount * 3);

		if(Texturing)
		{
			glBindTexture(GL_TEXTURE_2D, 0);
			glDisable(GL_TEXTURE_2D);
		}

		if(!WireFrame && Objects[i].CullFace)
		{
			glDisable(GL_CULL_FACE);
		}
	}
	
	if(gl_version >= 15)
	{
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);

	if(gl_version >= 21)
	{
		glUseProgram(0);
	}

	if(WireFrame)
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}

	glDisable(GL_LIGHTING);

	glDisable(GL_DEPTH_TEST);
}

void COpenGLRenderer::Resize(int Width, int Height)
{
	this->Width = Width;
	this->Height = Height;

	glViewport(0, 0, Width, Height);

	Projection = PerspectiveProjectionMatrix(45.0f, (float)Width, (float)Height, 0.125f, 512.0f);
	ProjectionBiasInverse = PerspectiveProjectionMatrixInverse(Projection) * BiasMatrixInverse();

	glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(&Projection);
}

void COpenGLRenderer::Destroy()
{
	for(int i = 0; i < ObjectsCount; i++)
	{
		Objects[i].Destroy();
	}

	delete [] Objects;

	if(gl_version >= 21)
	{
		Shader.Delete();
	}
}

void COpenGLRenderer::MoveSelectedObject(int x, int y)
{
	if(SelectedObject < 0 || SelectedObject >= ObjectsCount || !Objects[SelectedObject].Movable) return;

	y = Height - 1 - y;

	float s = (float)x / (float)(Width - 1);
	float t = (float)y / (float)(Height - 1);

	vec4 Point = ViewMatrixInverse(View) * (ProjectionBiasInverse * vec4(s, t, 0.5f, 1.0f));
	Point /= Point.w;

	vec3 Ray = normalize(Point - Camera.Position);

	float NdotR = -dot(PlaneNormal, Ray);

	if(NdotR != 0.0f)
	{
		float Distance = (dot(PlaneNormal, Camera.Position) + PlaneD) / NdotR;
		
		vec3 Point = Ray * Distance + Camera.Position;

		vec3 Offset = Point - SelectedPoint;

		SelectedPoint = Point;

		Objects[SelectedObject].Position += Offset;
	}
}

void COpenGLRenderer::SelectObject(int x, int y)
{
	int gl_version = use_gl_version < ::gl_version ? use_gl_version : ::gl_version;

	y = Height - 1 - y;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glEnable(GL_DEPTH_TEST);

	glEnableClientState(GL_VERTEX_ARRAY);

	for(int i = 0; i < ObjectsCount; i++)
	{
		if(Objects[i].TrianglesCount <= 0) continue;

		glMatrixMode(GL_MODELVIEW);
		glLoadMatrixf(&View);
		glTranslatef(Objects[i].Position.x, Objects[i].Position.y, Objects[i].Position.z);

		if(Objects[i].CullFace)
		{
			glEnable(GL_CULL_FACE);
		}

		int ID = i + 1;

		glColor3ub(ID & 0xFF, (ID >> 8) & 0xFF, (ID >> 16) & 0xFF);

		if(gl_version >= 15)
		{
			glBindBuffer(GL_ARRAY_BUFFER, Objects[i].VBO[2]);
			glVertexPointer(3, GL_FLOAT, 0, NULL);
		}
		else
		{
			glVertexPointer(3, GL_FLOAT, 0, Objects[i].Vertices);
		}

		glFrontFace(Objects[i].FrontFace);

		glDrawArrays(GL_TRIANGLES, 0, Objects[i].TrianglesCount * 3);

		if(Objects[i].CullFace)
		{
			glDisable(GL_CULL_FACE);
		}
	}

	if(gl_version >= 15)
	{
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	glDisableClientState(GL_VERTEX_ARRAY);

	glDisable(GL_DEPTH_TEST);

	BYTE Pixel[4];
	
	glReadPixels(x, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &Pixel);
	
	SelectedObject = (Pixel[0] | (Pixel[1] << 8) | (Pixel[2] << 16)) - 1;

	if(SelectedObject >= 0 && SelectedObject < ObjectsCount)
	{
		float s = (float)x / (float)(Width - 1);
		float t = (float)y / (float)(Height - 1);

		float Depth;

		glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &Depth);

		vec4 Point = ViewMatrixInverse(View) * (ProjectionBiasInverse * vec4(s, t, Depth, 1.0f));
		Point /= Point.w;

		SelectedPoint = Point;

		float omcospi4 = 1.0f - cos((float)M_PI / 4.0f);

		if(Camera.Z.y > -omcospi4 && Camera.Z.y < omcospi4)
		{
			PlaneNormal = normalize(vec3(Camera.Z.x, 0.0f, Camera.Z.z));
		}
		else
		{
			PlaneNormal = vec3(0.0f, 1.0f, 0.0f);
		}

		PlaneD = -dot(PlaneNormal, SelectedPoint);
	}
}

COpenGLRenderer OpenGLRenderer;

// ----------------------------------------------------------------------------------------------------------------------------

CWnd::CWnd()
{
	char *moduledirectory = new char[256];
	GetModuleFileName(GetModuleHandle(NULL), moduledirectory, 256);
	*(strrchr(moduledirectory, '\\') + 1) = 0;
	ModuleDirectory = moduledirectory;
	delete [] moduledirectory;
}

CWnd::~CWnd()
{
}

bool CWnd::Create(HINSTANCE hInstance, char *WindowName, int Width, int Height, int Samples, bool CreateForwardCompatibleContext, bool DisableVerticalSynchronization)
{
	WNDCLASSEX WndClassEx;

	memset(&WndClassEx, 0, sizeof(WNDCLASSEX));

	WndClassEx.cbSize = sizeof(WNDCLASSEX);
	WndClassEx.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	WndClassEx.lpfnWndProc = WndProc;
	WndClassEx.hInstance = hInstance;
	WndClassEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	WndClassEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
	WndClassEx.hCursor = LoadCursor(NULL, IDC_ARROW);
	WndClassEx.lpszClassName = "Win32OpenGLWindowClass";

	if(RegisterClassEx(&WndClassEx) == 0)
	{
		ErrorLog.Set("RegisterClassEx failed!");
		return false;
	}

	this->WindowName = WindowName;

	this->Width = Width;
	this->Height = Height;

	DWORD Style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;

	if((hWnd = CreateWindowEx(WS_EX_APPWINDOW, WndClassEx.lpszClassName, WindowName, Style, 0, 0, Width, Height, NULL, NULL, hInstance, NULL)) == NULL)
	{
		ErrorLog.Set("CreateWindowEx failed!");
		return false;
	}

	if((hDC = GetDC(hWnd)) == NULL)
	{
		ErrorLog.Set("GetDC failed!");
		return false;
	}

	PIXELFORMATDESCRIPTOR pfd;

	memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));

	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 32;
	pfd.cDepthBits = 24;
	pfd.iLayerType = PFD_MAIN_PLANE;

	int PixelFormat;

	if((PixelFormat = ChoosePixelFormat(hDC, &pfd)) == 0)
	{
		ErrorLog.Set("ChoosePixelFormat failed!");
		return false;
	}

	static int MSAAPixelFormat = 0;

	if(SetPixelFormat(hDC, MSAAPixelFormat == 0 ? PixelFormat : MSAAPixelFormat, &pfd) == FALSE)
	{
		ErrorLog.Set("SetPixelFormat failed!");
		return false;
	}

	if((hGLRC = wglCreateContext(hDC)) == NULL)
	{
		ErrorLog.Set("wglCreateContext failed!");
		return false;
	}

	if(wglMakeCurrent(hDC, hGLRC) == FALSE)
	{
		ErrorLog.Set("wglMakeCurrent failed!");
		return false;
	}

	if(glewInit() != GLEW_OK)
	{
		ErrorLog.Set("glewInit failed!");
		return false;
	}

	if(MSAAPixelFormat == 0 && Samples > 0)
	{
		if(GLEW_ARB_multisample && WGLEW_ARB_pixel_format)
		{
			while(Samples > 0)
			{
				UINT NumFormats = 0;

				int iAttributes[] =
				{
					WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
					WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
					WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
					WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
					WGL_COLOR_BITS_ARB, 32,
					WGL_DEPTH_BITS_ARB, 24,
					WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
					WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
					WGL_SAMPLES_ARB, Samples,
					0
				};

				if(wglChoosePixelFormatARB(hDC, iAttributes, NULL, 1, &MSAAPixelFormat, &NumFormats) == TRUE && NumFormats > 0) break;

				Samples--;
			}

			wglDeleteContext(hGLRC);

			DestroyWindow(hWnd);

			UnregisterClass(WndClassEx.lpszClassName, hInstance);

			return Create(hInstance, WindowName, Width, Height, Samples, CreateForwardCompatibleContext, DisableVerticalSynchronization);
		}
		else
		{
			Samples = 0;
		}
	}

	this->Samples = Samples;

	int major, minor;

	sscanf_s((char*)glGetString(GL_VERSION), "%d.%d", &major, &minor);

	gl_version = major * 10 + minor;

	if(CreateForwardCompatibleContext && gl_version >= 30 && WGLEW_ARB_create_context)
	{
		wglDeleteContext(hGLRC);

		int GLFCRCAttribs[] =
		{
			WGL_CONTEXT_MAJOR_VERSION_ARB, major,
			WGL_CONTEXT_MINOR_VERSION_ARB, minor,
			WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
			0
		};

		if((hGLRC = wglCreateContextAttribsARB(hDC, 0, GLFCRCAttribs)) == NULL)
		{
			ErrorLog.Set("wglCreateContextAttribsARB failed!");
			return false;
		}

		if(wglMakeCurrent(hDC, hGLRC) == FALSE)
		{
			ErrorLog.Set("wglMakeCurrent failed!");
			return false;
		}

		wgl_context_forward_compatible = true;
	}
	else
	{
		wgl_context_forward_compatible = false;
	}

	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_texture_size);

	if(GLEW_EXT_texture_filter_anisotropic)
	{
		glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_max_texture_max_anisotropy_ext);
	}

	if(DisableVerticalSynchronization && WGLEW_EXT_swap_control)
	{
		wglSwapIntervalEXT(0);
	}

	return OpenGLRenderer.Init();
}

void CWnd::Show(bool Maximized)
{
	RECT dRect, wRect, cRect;

	GetWindowRect(GetDesktopWindow(), &dRect);
	GetWindowRect(hWnd, &wRect);
	GetClientRect(hWnd, &cRect);

	wRect.right += Width - cRect.right;
	wRect.bottom += Height - cRect.bottom;

	wRect.right -= wRect.left;
	wRect.bottom -= wRect.top;

	wRect.left = dRect.right / 2 - wRect.right / 2;
	wRect.top = dRect.bottom / 2 - wRect.bottom / 2;

	MoveWindow(hWnd, wRect.left, wRect.top, wRect.right, wRect.bottom, FALSE);

	ShowWindow(hWnd, Maximized ? SW_SHOWMAXIMIZED : SW_SHOWNORMAL);
}

void CWnd::MessageLoop()
{
	MSG Msg;

	while(GetMessage(&Msg, NULL, 0, 0) > 0)
	{
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);
	}
}

void CWnd::Destroy()
{
	OpenGLRenderer.Destroy();

	wglDeleteContext(hGLRC);

	DestroyWindow(hWnd);
}

void CWnd::OnKeyDown(UINT Key)
{
	switch(Key)
	{
		case VK_F1:
			OpenGLRenderer.ShowAxisGrid = !OpenGLRenderer.ShowAxisGrid;
			break;

		case VK_F2:
			OpenGLRenderer.WireFrame = !OpenGLRenderer.WireFrame;
			break;

		case VK_F3:
			OpenGLRenderer.Texturing = !OpenGLRenderer.Texturing;
			break;

		case '1':
			use_gl_version = 11;
			break;

		case '2':
			use_gl_version = 15;
			break;

		case '3':
			use_gl_version = 21;
			break;

	}
}

void CWnd::OnLButtonDown(int cx, int cy)
{
	OpenGLRenderer.SelectObject(cx, cy);
}

void CWnd::OnMouseMove(int cx, int cy)
{
	if(GetKeyState(VK_LBUTTON) & 0x80)
	{
		OpenGLRenderer.MoveSelectedObject(cx, cy);
	}

	if(GetKeyState(VK_RBUTTON) & 0x80)
	{
		Camera.OnMouseMove(LastCurPos.x - cx, LastCurPos.y - cy);

		LastCurPos.x = cx;
		LastCurPos.y = cy;
	}
}

void CWnd::OnMouseWheel(short zDelta)
{
	Camera.OnMouseWheel(zDelta);
}

void CWnd::OnPaint()
{
	PAINTSTRUCT ps;

	BeginPaint(hWnd, &ps);

	static DWORD LastFPSTime = GetTickCount(), LastFrameTime = LastFPSTime;
	static int FPS = 0;

	DWORD Time = GetTickCount();

	float FrameTime = (Time - LastFrameTime) * 0.001f;

	LastFrameTime = Time;

	if(Time - LastFPSTime > 1000)
	{
		CString Text = WindowName;

		Text.Append(" - %dx%d", Width, Height);
		Text.Append(", ATF %dx", gl_max_texture_max_anisotropy_ext);
		Text.Append(", MSAA %dx", Samples);
		Text.Append(", FPS: %d", FPS);
		Text.Append(" - OpenGL %d.%d", gl_version / 10, gl_version % 10);
		if(gl_version >= 30) if(wgl_context_forward_compatible) Text.Append(" Forward compatible"); else Text.Append(" Compatibility profile");
		Text.Append(" - %s", (char*)glGetString(GL_RENDERER));
		
		SetWindowText(hWnd, Text);

		LastFPSTime = Time;
		FPS = 0;
	}
	else
	{
		FPS++;
	}

	BYTE Keys = 0x00;

	if(GetKeyState('W') & 0x80) Keys |= 0x01;
	if(GetKeyState('S') & 0x80) Keys |= 0x02;
	if(GetKeyState('A') & 0x80) Keys |= 0x04;
	if(GetKeyState('D') & 0x80) Keys |= 0x08;
	if(GetKeyState('R') & 0x80) Keys |= 0x10;
	if(GetKeyState('F') & 0x80) Keys |= 0x20;

	if(GetKeyState(VK_SHIFT) & 0x80) Keys |= 0x40;

	if(Keys & 0x3F)
	{
		vec3 Movement = Camera.OnKeys(Keys, FrameTime);
		Camera.Move(Movement);
	}

	OpenGLRenderer.Render(FrameTime);

	SwapBuffers(hDC);

	EndPaint(hWnd, &ps);

	InvalidateRect(hWnd, NULL, FALSE);
}

void CWnd::OnRButtonDown(int cx, int cy)
{
	LastCurPos.x = cx;
	LastCurPos.y = cy;
}

void CWnd::OnSize(int Width, int Height)
{
	this->Width = Width;
	this->Height = Height;

	OpenGLRenderer.Resize(Width, Height);
}

CWnd Wnd;

// ----------------------------------------------------------------------------------------------------------------------------

LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uiMsg)
	{
		case WM_CLOSE:
			PostQuitMessage(0);
			break;

		case WM_KEYDOWN:
			Wnd.OnKeyDown((UINT)wParam);
			break;

		case WM_LBUTTONDOWN:
			Wnd.OnLButtonDown(LOWORD(lParam), HIWORD(lParam));
			break;

		case WM_MOUSEMOVE:
			Wnd.OnMouseMove(LOWORD(lParam), HIWORD(lParam));
			break;

		case 0x020A: // WM_MOUSWHEEL
			Wnd.OnMouseWheel(HIWORD(wParam));
			break;

		case WM_PAINT:
			Wnd.OnPaint();
			break;

		case WM_RBUTTONDOWN:
			Wnd.OnRButtonDown(LOWORD(lParam), HIWORD(lParam));
			break;

		case WM_SIZE:
			Wnd.OnSize(LOWORD(lParam), HIWORD(lParam));
			break;

		default:
			return DefWindowProc(hWnd, uiMsg, wParam, lParam);
	}

	return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR sCmdLine, int iShow)
{
	if(Wnd.Create(hInstance, "Loading, picking and moving objects", 800, 600))
	{
		Wnd.Show();
		Wnd.MessageLoop();
	}
	else
	{
		MessageBox(NULL, ErrorLog, "Error", MB_OK | MB_ICONERROR);
	}

	Wnd.Destroy();

	return 0;
}
