#include "rwtest.h"

using namespace std;

int screenWidth = 640, screenHeight= 480;
bool running = true;
Camera *camera;

GLint program;
GLuint vbo;

rw::Clump *clump;

char *filename;

void
renderAtomic(rw::Atomic *atomic)
{
	using namespace rw;
	static GLenum prim[] = {
		GL_TRIANGLES, GL_TRIANGLE_STRIP
	};
	Geometry *geo = atomic->geometry;
	atomic->getPipeline()->instance(atomic);
	gl::InstanceDataHeader *inst = (gl::InstanceDataHeader*)geo->instData;
	MeshHeader *meshHeader = geo->meshHeader;

	Frame *frm = atomic->frame;
	frm->updateLTM();
	glUniformMatrix4fv(glGetUniformLocation(program, "worldMat"),
	                   1, GL_FALSE, frm->ltm);

	glVertexAttrib4f(3, 0.0f, 0.0f, 0.0f, 1.0f);
	glVertexAttrib3f(2, 0.0f, 0.0f, 0.0f);
	if(inst->vbo == 0 && inst->ibo == 0)
		gl::uploadGeo(geo);
	glBindBuffer(GL_ARRAY_BUFFER, inst->vbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, inst->ibo);
	gl::setAttribPointers(inst);

	uint64 offset = 0;
	for(uint32 i = 0; i < meshHeader->numMeshes; i++){
		Mesh *mesh = &meshHeader->mesh[i];
		Material *mat = mesh->material;
		float color[4];
		uint8 *col = mat->color;
		color[0] = col[0] / 255.0f;
		color[1] = col[1] / 255.0f;
		color[2] = col[2] / 255.0f;
		color[3] = col[3] / 255.0f;
		glUniform4fv(glGetUniformLocation(program, "matColor"),
			     1, color);
		rw::gl::Texture *tex =(rw::gl::Texture*)mat->texture;
		if(tex)
			tex->bind(0);
		else
			glBindTexture(GL_TEXTURE_2D, 0);
		glDrawElements(prim[meshHeader->flags], mesh->numIndices,
		               GL_UNSIGNED_SHORT, (void*)offset);
		offset += mesh->numIndices*2;
	}
	glDisableVertexAttribArray(0);
	glDisableVertexAttribArray(1);
	glDisableVertexAttribArray(2);
	glDisableVertexAttribArray(3);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void
render(void)
{
	static Mat4 worldMat(1.0f);
	glUseProgram(program);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	camera->look();
	glUniformMatrix4fv(glGetUniformLocation(program, "projMat"),
	                   1, GL_FALSE, camera->projMat.cr);
	glUniformMatrix4fv(glGetUniformLocation(program, "viewMat"),
	                   1, GL_FALSE, camera->viewMat.cr);
	glUniformMatrix4fv(glGetUniformLocation(program, "worldMat"),
	                   1, GL_FALSE, worldMat.cr);

	glVertexAttrib3f(2, -0.5f, 0.5f, 0.70710f);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(3);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 28, (GLvoid*)0);
	glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 28, (GLvoid*)12);
	glDrawArrays(GL_LINES, 0, 6);
	glDisableVertexAttribArray(0);
	glDisableVertexAttribArray(3);

	for(rw::uint32 i = 0; i < clump->numAtomics; i++){
		char *name = PLUGINOFFSET(char, clump->atomicList[i]->frame,
		                          gta::nodeNameOffset);
		if(strstr(name, "_dam") || strstr(name, "_vlo"))
			continue;
		renderAtomic(clump->atomicList[i]);
	}
}

void
init(void)
{
	glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GEQUAL, 0.5f);
	const char *shadersrc =
		"#ifdef VERTEX\n"
		"uniform mat4 projMat;"
		"uniform mat4 viewMat;"
		"uniform mat4 worldMat;"
		"uniform vec4 matColor;"
		"attribute vec3 in_vertex;"
		"attribute vec2 in_texCoord;"
		"attribute vec3 in_normal;"
		"attribute vec4 in_color;"
		"varying vec4 v_color;"
		"varying vec2 v_texCoord;"
		"vec3 lightdir = vec3(0.5, -0.5, -0.70710);"
		"vec4 amblight = vec4(20, 20, 20, 0)/255;"
		"void main()"
		"{"
		"	gl_Position = projMat * viewMat * worldMat * vec4(in_vertex, 1.0);"
		"	vec3 n = mat3(worldMat) * in_normal;"
		"	float l = max(0.0, dot(n, -lightdir));"
		"	v_color = (in_color+vec4(l,l,l,0)+amblight)*matColor;"
		"	v_texCoord = in_texCoord;"
		"}\n"
		"#endif\n"
		"#ifdef FRAGMENT\n"
		"uniform sampler2D u_texture0;"
		"varying vec4 v_color;"
		"varying vec2 v_texCoord;"
		"void main()"
		"{"
		"	vec4 c0 = texture2D(u_texture0, v_texCoord/512.0);"
		"	gl_FragColor = v_color*c0;"
		"}\n"
		"#endif\n";
	const char *srcarr[] = { "#version 120\n",
		"#define VERTEX\n", shadersrc };
	GLint vertshader = rw::gl::compileShader(srcarr, 3, GL_VERTEX_SHADER);
	assert(vertshader != 0);
	srcarr[1] = "#define FRAGMENT\n";
	GLint fragshader = rw::gl::compileShader(srcarr, 3, GL_FRAGMENT_SHADER);
	assert(fragshader != 0);
	program = rw::gl::linkProgram(vertshader, fragshader);
	assert(program != 0);

	GLfloat vertarray[] = {
		0.0f, 0.0f, 0.0f,
		1.0f, 0.0f, 0.0f, 1.0f,
		1.0f, 0.0f, 0.0f,
		1.0f, 0.0f, 0.0f, 1.0f,

		0.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f, 1.0f,
		0.0f, 1.0f, 0.0f,
		0.0f, 1.0f, 0.0f, 1.0f,

		0.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 1.0f,
		0.0f, 0.0f, 1.0f,
		0.0f, 0.0f, 1.0f, 1.0f,
	};
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertarray),
	             vertarray, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	camera = new Camera;
	camera->setAspectRatio(1.0f*screenWidth/screenHeight);
	camera->setNearFar(0.1f, 450.0f);
	camera->setTarget(Vec3(0.0f, 0.0f, 0.0f));
	camera->setPosition(Vec3(0.0f, 5.0f, 0.0f));

	rw::currentTexDictionary = new rw::TexDictionary;
//	rw::Image::setSearchPath("/home/aap/gamedata/ps2/gtasa/models/gta3_archive/txd_extracted/");
//	rw::Image::setSearchPath("/home/aap/gamedata/ps2/gtavc/MODELS/gta3_archive/txd_extracted/");
	rw::Image::setSearchPath(
	"/home/aap/gamedata/ps2/gta3/MODELS/gta3_archive/txd_extracted/;//home/aap/gamedata/ps2/gtavc/MODELS/gta3_archive/txd_extracted/;/home/aap/gamedata/ps2/gtasa/models/gta3_archive/txd_extracted/");
	//"D:\\rockstargames\\ps2\\gtavc\\MODELS\\gta3_archive\\txd_extracted\\;D:\\rockstargames\\ps2\\gtasa\\models\\gta3_archive\\txd_extracted\\");

	rw::gl::registerNativeRaster();
	gta::registerEnvSpecPlugin();
	rw::registerMatFXPlugin();
	rw::registerMaterialRightsPlugin();
	rw::registerAtomicRightsPlugin();
	rw::registerHAnimPlugin();
	gta::registerNodeNamePlugin();
	gta::registerBreakableModelPlugin();
	gta::registerExtraVertColorPlugin();
	rw::ps2::registerADCPlugin();
	rw::ps2::registerPDSPlugin();
	rw::registerSkinPlugin();
	rw::xbox::registerVertexFormatPlugin();
	rw::registerNativeDataPlugin();
//      rw::ps2::registerNativeDataPlugin();
	rw::registerMeshPlugin();
	rw::Atomic::init();

	printf("platform: %d\n", rw::platform);

	rw::StreamFile in;
	if(in.open(filename, "rb") == NULL)
		printf("couldn't open file\n");
	rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL);
	clump = rw::Clump::streamRead(&in);
	assert(clump);
	in.close();
}

void
keypress(GLFWwindow *w, int key, int scancode, int action, int mods)
{
	if(action != GLFW_PRESS)
		return;

	switch(key){
	case 'Q':
	case GLFW_KEY_ESCAPE:
		running = false;
		break;
	}
}

static int lastX, lastY;
static int clickX, clickY;
static bool isLDown, isMDown, isRDown;
static bool isShiftDown, isCtrlDown, isAltDown;

void
mouseButton(GLFWwindow *w, int button, int action, int mods)
{
	double x, y;
	glfwGetCursorPos(w, &x, &y);
	if(action == GLFW_PRESS){
		lastX = clickX = x;
		lastY = clickY = y;
		if(button == GLFW_MOUSE_BUTTON_LEFT)
			isLDown = true;
		if(button == GLFW_MOUSE_BUTTON_MIDDLE)
			isMDown = true;
		if(button == GLFW_MOUSE_BUTTON_RIGHT)
			isRDown = true;
	}else if(action == GLFW_RELEASE){
		if(button == GLFW_MOUSE_BUTTON_LEFT)
			isLDown = false;
		if(button == GLFW_MOUSE_BUTTON_MIDDLE)
			isMDown = false;
		if(button == GLFW_MOUSE_BUTTON_RIGHT)
			isRDown = false;
	}
}

void
mouseMotion(GLFWwindow *w, double x, double y)
{
	GLfloat dx, dy;
	static int xoff = 0, yoff = 0;
	static bool wrappedLast = false;
	int width, height;

	glfwGetWindowSize(w, &width, &height);

	dx = float(lastX - x) / float(width);
	dy = float(lastY - y) / float(height);
	/* Wrap the mouse if it goes over the window border.
	 * Unfortunately, after glfwSetMousePos is done, there can be old
	 * events with an old mouse position,
	 * hence the check if the pointer was wrapped the last time. */
	if((isLDown || isMDown || isRDown) &&
	   (x < 0 || y < 0 || x >= width || y >= height)){
		if(wrappedLast){
			dx = float(lastX-xoff - x) / float(width);
			dy = float(lastY-yoff - y) / float(height);
		}
		xoff = yoff = 0;
		while (x+xoff >= width) xoff -= width;
		while (y+yoff >= height) yoff -= height;
		while (x+xoff < 0) xoff += width;
		while (y+yoff < 0) yoff += height;
		glfwSetCursorPos(w, x+xoff, y+yoff);
		wrappedLast = true;
	}else{
		wrappedLast = false;
		xoff = yoff = 0;
	}
	lastX = x+xoff;
	lastY = y+yoff;
	if(isLDown){
		if(isShiftDown)
			camera->turn(dx*2.0f, dy*2.0f);
		else
			camera->orbit(dx*2.0f, -dy*2.0f);
	}
	if(isMDown){
		if(isShiftDown)
			;
		else
			camera->pan(dx*8.0f, -dy*8.0f);
	}
	if(isRDown){
		if(isShiftDown)
			;
		else
			camera->zoom(dx*12.0f);
	}
}

void
resize(GLFWwindow*, int width, int height)
{
	screenWidth = width;
	screenHeight = height;
	glViewport(0, 0, screenWidth, screenHeight);
	camera->setAspectRatio(1.0f*screenWidth/screenHeight);
}

void
closewindow(GLFWwindow*)
{
	running = false;
}

int
main(int argc, char *argv[])
{
	if(argc < 2)
		return 1;
	filename = argv[1];

	if(!glfwInit()){
		fprintf(stderr, "Error: could not initialize GLFW\n");
		return 1;
	}
	GLFWwindow *window = glfwCreateWindow(screenWidth, screenHeight,
	                                      "OpenGL", 0, 0);
	if(!window){
		fprintf(stderr, "Error: could not create GLFW window\n");
		glfwTerminate();
		return 1;
	}
	glfwMakeContextCurrent(window);
	GLenum status = glewInit();
	if(status != GLEW_OK){
		fprintf(stderr, "Error: %s\n", glewGetErrorString(status));
		return 1;
	}
	if(!GLEW_VERSION_2_0){
		fprintf(stderr, "Error: OpenGL 2.0 needed\n");
		return 1;
	}

	init();

	glfwSetWindowSizeCallback(window, resize);
	glfwSetWindowCloseCallback(window, closewindow);
	glfwSetMouseButtonCallback(window, mouseButton);
	glfwSetCursorPosCallback(window, mouseMotion);
	glfwSetKeyCallback(window, keypress);
	while(running){
		glfwPollEvents();
		isShiftDown = glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS;
		isCtrlDown = glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS;
		isAltDown = glfwGetKey(window, GLFW_KEY_LEFT_ALT) == GLFW_PRESS;

		render();
		glfwSwapBuffers(window);
	}

	glfwTerminate();
	return 0;
}