#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);

		renderer->setVertexShader("VShader.txt");

		if(true)   // Fixed-function or programmable pixel pipeline
		{
			renderer->setPixelShader("PShader.txt");
		}
		else
		{
			renderer->setTexCoordIndex(0, 0);
			renderer->setStageOperation(0, Sampler::STAGE_REPLACE);
			renderer->setFirstArgument(0, Sampler::SOURCE_TEXTURE);

			renderer->setTexCoordIndex(1, 0);
			renderer->setStageOperation(1, Sampler::STAGE_ADD);
			renderer->setFirstArgument(1, Sampler::SOURCE_TEXTURE);
			renderer->setSecondArgument(1, Sampler::SOURCE_CURRENT);
		}

		texture[0] = new Texture("Porcelain.jpg");
		texture[1] = new Texture("Lightning.jpg");

		renderer->setTextureMap(0, texture[0]);
		renderer->setTextureMap(1, texture[1]);

	//	float plane[4] = {1, -1, 0, 0.5};
	//	renderer->setClipPlane(0, plane);
	//	renderer->setClipFlags(Clipper::CLIP_ALL | Clipper::CLIP_PLANE0);

		frameBuffer->clear();
		frameBuffer->flip();

		keyboard = new Keyboard(handle);
		mouse = new Mouse(handle);

		model = new Model3DS("Teapot.3ds");

		camera.setPosition(0, -100, 0);
	}

	Application::~Application()
	{
		delete frameBuffer;
		frameBuffer = 0;

		delete renderer;
		renderer = 0;

		delete texture[0];
		texture[0] = 0;
		delete texture[1];
		texture[1] = 0;

		delete keyboard;
		keyboard = 0;
		delete mouse;
		mouse = 0;

		delete model;
		model = 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->clear();
		frameBuffer->lock();

		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;

		renderer->setPixelShaderConstantF(0, pulse1);
		renderer->setVertexShaderConstantF(4, pulse2);
		renderer->setVertexShaderConstantF(5, pulse3);
	//	renderer->setModelTransform(Matrix::eulerRotate(0.2f * (float)totalTime,
	//		                                            0.4f * (float)totalTime,
	//													0.3f * (float)totalTime));
		renderer->setViewTransform(camera.getTransform());

		renderer->drawPrimitive(model, model);

		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);
		}
	}
}