#include <windows.h>

#include "string.h"
#include "glmath.h"

#include <gl/glew.h> // http://glew.sourceforge.net/
#include <gl/wglew.h>

#include <FreeImage.h> // http://freeimage.sourceforge.net/

#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "glew32.lib")

#pragma comment(lib, "FreeImage.lib")

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

class CTexture
{
protected:
	GLuint TextureID;

public:
	CTexture();
	~CTexture();

	operator GLuint ();

	void Delete();
	bool LoadTexture2D(char *Texture2DFileName);
};

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

class CObject
{
public:
	bool Movable, CullFace;
	GLenum FrontFace;
	int TrianglesCount;
	GLuint VBO[3];
	CTexture Texture;
	vec2 *TexCoords;
	vec3 *Normals, *Vertices, Color, Min, Max, Position;

public:
	CObject();
	~CObject();

	void CreateSphere(float Radius, int Resolution = 16, bool InvertNormals = false);
	void Destroy();
	void InitVertexBuffers();
	bool Load(char *Directory, char *ObjFileName);
	void Rotate(float Angle, const vec3 &Axis);
	void Scale(float ScaleFactor);
	void Translate(const vec3 &Translation);

protected:
	void AllocateMemory();
	void GetMinMax();
	bool ParseMtl(char *Directory, char *MtlFileName);
	bool ReadSource(char *Directory, char *FileName, char **Source, long &Length);
	void SetDefaults();
};

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

class CShaderProgram
{
public:
	GLuint *UniformLocations;

protected:
	GLuint VertexShader, FragmentShader, Program;

public:
	CShaderProgram();
	~CShaderProgram();

	operator GLuint ();

	void Delete();
	bool Load(char *VertexShaderFileName, char *FragmentShaderFileName);

protected:
	GLuint LoadShader(GLenum Type, char *ShaderFileName);
	void SetDefaults();
};

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

class CCamera
{
protected:
	mat4x4 *View;

public:
	vec3 X, Y, Z, Reference, Position;

	CCamera();
	~CCamera();

	void CalculateViewMatrix();
	void LookAt(vec3 Reference, vec3 Position, bool RotateAroundReference = false);
	void Move(vec3 Movement);
	vec3 OnKeys(BYTE Keys, float FrameTime);
	void OnMouseMove(int dx, int dy);
	void OnMouseWheel(float zDelta);
	void SetViewMatrixPointer(float *View);
};

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

#define SHADOW_CUBE_MAP_SIZE 512

class COpenGLRenderer
{
protected:
	int Width, Height;
	mat4x4 Model, View, Projection, ProjectionBiasInverse;
    
	int ObjectsCount, LightObjectID;
	CObject *Objects;

	CShaderProgram ShadowCubeMapping;

	GLuint ShadowCubeMap, FBO;
	mat4x4 LightView[6], LightProjection, LightTexture[6];

	int SelectedObject;
	float PlaneD;
	vec3 SelectedPoint, PlaneNormal;

public:
	COpenGLRenderer();
	~COpenGLRenderer();

	bool Init();
	void Render(float FrameTime);
	void Resize(int Width, int Height);
	void Destroy();

	void MoveSelectedObject(int x, int y);
	void SelectObject(int x, int y);

protected:
	void RenderShadowCubeMap();
};

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

class CWnd
{
protected:
	HWND hWnd;
	HDC hDC;
	HGLRC hGLRC;
	char *WindowName;
	int Width, Height, Samples;
	POINT LastCurPos;

public:
	CWnd();
	~CWnd();

	bool Create(HINSTANCE hInstance, char *WindowName, int Width, int Height, int Samples = 4, bool CreateForwardCompatibleContext = false, bool DisableVerticalSynchronization  = true);
	void Show(bool Maximized = false);
	void MessageLoop();
	void Destroy();

	void OnKeyDown(UINT Key);
	void OnLButtonDown(int cx, int cy);
	void OnMouseMove(int cx, int cy);
	void OnMouseWheel(short zDelta);
	void OnPaint();
	void OnRButtonDown(int cx, int cy);
	void OnSize(int Width, int Height);
};

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

LRESULT CALLBACK WndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR sCmdLine, int iShow);
