#include "Application.hpp"

#include "FrameBuffer.hpp"
#include "CodeGenerator.hpp"
#include "Error.hpp"
#include "Renderer.hpp"
#include "Texture.hpp"
#include "Matrix.hpp"
#include "Sampler.hpp"
#include "Timer.hpp"
#include "Model3DS.hpp"
#include "CPUID.hpp"
#include "Keyboard.hpp"
#include "Mouse.hpp"
#include "Clipper.hpp"

namespace swShader
{
	Application::Application(HINSTANCE instance) : Window(instance, "swShader Demo", 640, 480)
	{
		if(!CPUID::supportsMMX())
		{
			throw Error("swShader requires MMX support.");
		}

		bool forceEmulateSSE = false;

		if(!CPUID::supportsSSE() || forceEmulateSSE)
		{
			SoftWire::CodeGenerator::enableEmulateSSE();
		}

		frameBuffer = 0;
		renderer = 0;
		keyboard = 0;
		mouse = 0;

		frame = 0;
		totalTime = 0;
		previousTime = 0;

		delete frameBuffer;

		if(style & FULLSCREEN)
		{
			hideCursor();
			frameBuffer = new FrameBuffer(handle, width, height, true);
		}
		else
		{
			showCursor();
			frameBuffer = new FrameBuffer(handle, width, height, false);
		}

		renderer = new Renderer(frameBuffer);

		frameBuffer->clearColorBuffer();
		frameBuffer->clearDepthBuffer();
		frameBuffer->flip();

		keyboard = new Keyboard(handle);
		mouse = new Mouse(handle);

		texture[0] = new Texture("Porcelain.jpg");
		texture[1] = new Texture("Lightning.jpg");
		texture[2] = new Texture("Gold.jpg");

		teapot = new Model3DS("Teapot.3ds");
		torus = new Model3DS("Torus.3ds");

		camera.setPosition(0, -200, 0);
	}

	Application::~Application()
	{
		delete frameBuffer;
		frameBuffer = 0;

		delete renderer;
		renderer = 0;

		delete texture[0];
		texture[0] = 0;
		delete texture[1];
		texture[1] = 0;
		delete texture[2];
		texture[2] = 0;

		delete keyboard;
		keyboard = 0;
		delete mouse;
		mouse = 0;

		delete teapot;
		teapot = 0;
		delete torus;
		torus = 0;
	}

	void Application::eventDraw()
	{
		static double previousTime = Timer::seconds();

		try
		{
			renderFrame();
			handleInput();
		}
		catch(const Error &error)
		{
			if(style & FULLSCREEN) toggleFullscreen();

			throw error;
		}
		catch(...)
		{
			throw Error("Unknown fatal error in render loop");
		}
	}

	void Application::eventKeyDown(int key)
	{
		if(key == VK_ESCAPE)
		{
			terminate();
		}
	}

	void Application::toggleFullscreen()
	{
		close();

		if(style & FULLSCREEN) style &= ~FULLSCREEN;
		else                   style |= FULLSCREEN;

		open();

		// New window handle, re-init
		mouse->init(handle);
		keyboard->init(handle);

		if(style & FULLSCREEN)
		{
			hideCursor();
			frameBuffer->initFullscreen(handle, width, height);
		}
		else
		{
			showCursor();
			frameBuffer->initWindowed(handle, width, height);
		}
	}

	void Application::renderFrame()
	{
		if(!renderer || !frameBuffer) throw Error("Initialization error");

		frameBuffer->updateBounds();
		frameBuffer->clearColorBuffer();
		frameBuffer->clearDepthBuffer();
		frameBuffer->lock();

		renderer->setViewMatrix(camera.getTransform());

		float pulse1[4];
		pulse1[0] = (1 + sin(10 * (float)totalTime)) / 2;

		float pulse2[4];
		pulse2[0] = 1 + cos((float)totalTime / 2 + 0) / 5;
		pulse2[1] = 1 + cos((float)totalTime / 3 + 1) / 5;
		pulse2[2] = 1 + cos((float)totalTime / 4 + 2) / 5;
		pulse2[3] = 1;

		float pulse3[4];
		pulse3[0] = cos(2 * (float)totalTime + 0);
		pulse3[1] = cos(3 * (float)totalTime + 1);
		pulse3[2] = cos(4 * (float)totalTime + 2);
		pulse3[3] = 1;

		Matrix M = Matrix::eulerRotate(0.2f * (float)totalTime,
		                               0.4f * (float)totalTime,
		                               0.3f * (float)totalTime);

		Vector V = M * Vector(0, 0, 1000);

		float light[4] = {V.x, V.y, V.z, 0};

		float one[4] = {1, 1, 1, 1};

		// Draw teapot
		renderer->setModelMatrix(Matrix(1));
		const Matrix &T = renderer->getModelTransform();
		renderer->setVertexShaderConstantF(0, T);

		renderer->setPixelShaderConstantF(0, pulse1);
		renderer->setVertexShaderConstantF(4, pulse2);
		renderer->setVertexShaderConstantF(5, pulse3);
		renderer->setVertexShaderConstantF(6, light);
		renderer->setVertexShaderConstantF(9, (float*)&camera.getPosition());

		renderer->setVertexShader("VShader.txt");
		renderer->setPixelShader("PShader.txt");

		renderer->setTextureMap(0, texture[0]);
		renderer->setTextureMap(1, texture[1]);

		renderer->drawPrimitive(teapot, teapot);

		// Draw torus
		renderer->setModelMatrix(M);

		renderer->setVertexShader(0);
		renderer->setPixelShader(0);

		renderer->setTexCoordIndex(0, 0);
		renderer->setStageOperation(0, Sampler::STAGE_REPLACE);
		renderer->setFirstArgument(0, Sampler::SOURCE_TEXTURE);

		renderer->setShadingMode(Context::SHADING_GOURAUD);

		renderer->setLightEnable(0, true);
		renderer->setLightPosition(0, Point(200, 0, 0));
		renderer->setLightColor(0, Color<float>::WHITE);
		renderer->setLightAttenuation(0, 0, 0, 0.00001f);
		renderer->setLightRange(0, 1000);
		renderer->setSpecularEnable(0, true);

		renderer->setLightEnable(1, true);
		renderer->setLightPosition(1, Point(0, 0, 200));
		renderer->setLightColor(1, Color<float>::WHITE);
		renderer->setLightAttenuation(1, 0, 0, 0.00001f);
		renderer->setLightRange(1, 1000);
		renderer->setSpecularEnable(1, true);

		renderer->setAmbientLight(Color<float>(0.2f, 0.2f, 0.2f, 0.2f));

		renderer->setMaterialDiffuse(Color<float>::WHITE);
		renderer->setMaterialAmbient(Color<float>::WHITE);
		renderer->setMaterialSpecular(Color<float>::WHITE);
		renderer->setMaterialShininess(20, 20, 20, 20);

	//	float plane[4] = {0, 0, 1, 0};
	//	renderer->setClipPlane(0, plane);
	//	renderer->setClipFlags(Clipper::CLIP_ALL | Clipper::CLIP_PLANE0);

		renderer->setTextureMap(0, texture[2]);

		renderer->drawPrimitive(torus, torus);

		frame++;
		monitor.newFrame();

		const double currentTime = Timer::seconds();
		elapsedTime = (float)(currentTime - previousTime);
		totalTime += elapsedTime;
		previousTime = currentTime;

		frameBuffer->flip();
	}

	void Application::handleInput()
	{
		if(!keyboard || !mouse) throw Error("Initialization error");

		if(!hasFocus())return;
		if(!keyboard->acquire()) return;
		if(!mouse->acquire()) return;

		keyboard->input();
		mouse->input();

		float speedFactor = 100 * elapsedTime;

		if(keyboard->keyPressed(DIK_W) || keyboard->keyPressed(DIK_UP))
		{
			camera.translateRelative(speedFactor * Vector(0, +1, 0));
		}

		if(keyboard->keyPressed(DIK_S) || keyboard->keyPressed(DIK_DOWN))
		{
			camera.translateRelative(speedFactor * Vector(0, -1, 0));
		}

		if(keyboard->keyPressed(DIK_A) || keyboard->keyPressed(DIK_LEFT))
		{
			camera.translateRelative(speedFactor * Vector(-1, 0, 0));
		}

		if(keyboard->keyPressed(DIK_D) || keyboard->keyPressed(DIK_RIGHT))
		{
			camera.translateRelative(speedFactor * Vector(+1, 0, 0));
		}

		if(mouse->getHorizontalDisplacement() || mouse->getVerticalDisplacement())
		{
			float phi = 0.002f * mouse->getHorizontalDisplacement();
			float theta = 0.002f * mouse->getVerticalDisplacement();

			camera.eulerRotate(theta, 0, phi);
		}
	}
}