/***********      .---.         .-"-.      *******************\
* -------- *     /   ._.       / ´ ` \     * ---------------- *
* Author's *     \_  (__\      \_°v°_/     * humus@rogers.com *
*   note   *     //   \\       //   \\     * ICQ #47010716    *
* -------- *    ((     ))     ((     ))    * ---------------- *
*          ****--""---""-------""---""--****                  ********\
* This file is a part of the work done by Humus. You are free to use  *
* the code in any way you like, modified, unmodified or copy'n'pasted *
* into your own work. However, I expect you to respect these points:  *
*  @ If you use this file and its contents unmodified, or use a major *
*    part of this file, please credit the author and leave this note. *
*  @ For use in anything commercial, please request my approval.      *
*  @ Share your work and ideas too as much as you can.                *
\*********************************************************************/

#include "App.h"

BaseApp *app = new App();

void App::resetCamera(){
	camPos = vec3(-1280, -146, -2140);
	wx = 0;
	wy = 0;

	camDir = vec3(0, 0, 0);
	speed = 400;
}

void App::controls(){
	if (freeFly){
		BaseApp::controls();
		return;
	}

	static bool canJump = true;
	static bool dodging = false;

	// Compute directional vectors from euler angles
	float cosX = cosf(wx * 0), sinX = sinf(wx * 0), cosY = cosf(wy), sinY = sinf(wy);
	vec3 dx(cosY, 0, sinY);
	vec3 dy(-sinX * sinY,  cosX, sinX * cosY);
	vec3 dz(-cosX * sinY, -sinX, cosX * cosY);

	vec3 dir(0, 0, 0);
	if (keys[leftKey])  dir -= dx;
	if (keys[rightKey]) dir += dx;
	if (keys[backwardKey]) dir -= dz;
	if (keys[forwardKey])  dir += dz;

	float lenSq = dot(dir, dir);
	if (lenSq > 0){
		dir *= (1.0f / sqrtf(lenSq));
	}

	static bool walking = false;

	float walkHeight = walking? 50.0f : 45.0f;

	vec3 point, normal;
	if (walking = engine->collision(camPos, camPos - vec3(0, walkHeight, 0), &point, &normal)){
		// Walking

		camDir = dir;
		camDir.y = 0;

		if (keys[upKey]){
			if (canJump){
				camDir.y += 1.8f;
			}
		} else {
			canJump = true;
			camPos = point;
			camPos.y += 46;
		}

		dodging = false;

	} else {
		// Hovering

		canJump = false;

		camDir.y -= 2.5f * frameTime;
		camDir += frameTime * dir;

		if (!dodging){
			float len = length(camDir.xz());
			if (len > 1.0f){
				len = 1.0f / len;
				camDir.x *= len;
				camDir.z *= len;
			}
		}
	}

	// Handle dodging
	static float lastLeftTime, lastRightTime, lastDownTime, lastUpTime;
	static bool lastLeft = false, lastRight = false, lastDown = false, lastUp = false;
	if (keys[leftKey]){
		if (!lastLeft){
			if (time - lastLeftTime < 0.3f){
				if (walking || engine->collision(camPos, camPos + 80 * dx)){
					camDir -= 1.4f * dx;
					camDir.y += 1;
					camPos.y += 10;
					dodging = true;
					walking = false;
				}
			}
			lastLeftTime = time;
		}
		lastLeft = true;
	} else lastLeft = false;

	if (keys[rightKey]){
		if (!lastRight){
			if (time - lastRightTime < 0.3f){
				if (walking || engine->collision(camPos, camPos - 80 * dx)){
					camDir += 1.4f * dx;
					camDir.y += 1;
					camPos.y += 10;
					dodging = true;
					walking = false;
				}
			}
			lastRightTime = time;
		}
		lastRight = true;
	} else lastRight = false;

	if (keys[backwardKey]){
		if (!lastDown){
			if (time - lastDownTime < 0.3f){
				if (walking || engine->collision(camPos, camPos + 80 * dz)){
					camDir -= 1.4f * dz;
					camDir.y += 1;
					camPos.y += 10;
					dodging = true;
					walking = false;
				}
			}
			lastDownTime = time;
		}
		lastDown = true;
	} else lastDown = false;

	if (keys[forwardKey]){
		if (!lastUp){
			if (time - lastUpTime < 0.3f){
				if (walking || engine->collision(camPos, camPos - 80 * dz)){
					camDir += 1.4f * dz;
					camDir.y += 1;
					camPos.y += 10;
					dodging = true;
					walking = false;
				}
			}
			lastUpTime = time;
		}
		lastUp = true;
	} else lastUp = false;


	// Interact with kickers
	if (walking){
		for (uint i = 0; i < kickers.getCount(); i++){
			if (time - kickers[i].kickTime > 0.5f){
				float d = dot(camPos, kickers[i].normal) + kickers[i].offset;
				if (d >= 0 && d < 50){
					vec3 dir = kickers[i].center - camPos;
					if (dot(dir, dir) < 64 * 64){
						camDir += kickers[i].kickDir;
						camPos += (46 - d) * normalize(kickers[i].kickDir);
						kickers[i].kickTime = time;
						walking = false;
						engine->makeKickerSound(kickers[i].center, soundVolume->getValue());
					}
				}
			}
		}
	}



	vec3 newPos = camPos + camDir * (frameTime * speed);

	if (engine->collision(camPos, newPos, &point, &normal)){
		newPos = point + normal;
	}

	vec3 pushedPos = newPos;
	if (engine->push(pushedPos, 40)){
		vec3 pDir = pushedPos - newPos;
		float d = dot(pDir, pDir);

		if (d > 0.0f){
			pDir *= 1.0f / sqrtf(d);

			float friction = saturate(1 + 0.5f * dot(normalize(camDir), pDir));
			camDir = friction * reflect(camDir, pDir);
			if (walking){
				camDir.x = 0;
				camDir.z = 0;
			}
		}

		newPos = pushedPos + 0.1f * pDir;
	}

	camPos = newPos;
}

bool App::onKey(const unsigned int key, const bool pressed){
	if (pressed){
		switch (key){
		case KEY_F4:
			freeFly = !freeFly;
			break;
		case KEY_F8:
			showDebug = !showDebug;
			break;
		}
	}

	return OpenGLApp::onKey(key, pressed);
}

bool App::onMouseButton(const int x, const int y, const MouseButton button, const bool pressed){
	if (OpenGLApp::onMouseButton(x, y, button, pressed)) return true;

	if (pressed){
		if (button == MOUSE_LEFT){
			// Compute directional vectors from euler angles
			float cosX = cosf(wx), sinX = sinf(wx), cosY = cosf(wy), sinY = sinf(wy);
			vec3 dx(cosY, 0, sinY);
			vec3 dy(-sinX * sinY,  cosX, sinX * cosY);
			vec3 dz(-cosX * sinY, -sinX, cosX * cosY);

			engine->addRocket(camPos + 10 * dx - 5 * dy + 5 * dz, dz);

			shootTime = time;
			return true;
		}/* else if (button == MOUSE_RIGHT){
			engine->updateVisibility(renderer, camPos, width, height, 512, 512);
		}*/
	}
	return false;
}

bool App::init(){
	engine = new Engine();

	if (!engine->loadGeometry("../Models/GameEngineII/Map.t3d", 500, true)){
		delete engine;
		return false;
	}

	engine->computeVisibility(renderer, 8, true);

	SoundID hum = engine->addAmbientSound("../Sounds/hum2.wav");
	SoundID wind = engine->addAmbientSound("../Sounds/wind01.wav");

	engine->addAmbientSoundSource(wind, vec3(2500, 250, 2000));
	engine->addAmbientSoundSource(wind, vec3(-1900, 350, 3100));

	engine->addLight(new Light(vec3(-512, 235, 192), 500, vec3(1.0f, 1.0f, 1.0f), 0, 0, 0.7f, INVISIBLE));
	engine->addAmbientSoundSource(hum, vec3(-512, 235, 224));
	engine->addLight(new Light(vec3(640,  64,  32), 400, vec3(1.0f, 1.0f, 1.0f), 0.4f, 1, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(1216, 64, 576), 330, vec3(1.0f, 1.0f, 1.0f), 0.4f, 1, 0, INVISIBLE | NOSHADOWS));

	engine->addLight(new Light(vec3(455, 600, 1378), 0.42f, 2.60f, 1.1f, 1100, vec3(1.0f, 0.7f, 0.7f), 0, 1.1f, 0, INVISIBLE));
	engine->addLight(new Light(vec3(455, 600,  420), 0.92f, 0.33f, 1.1f, 1200, vec3(0.7f, 0.7f, 1.0f), 0, 1.1f, 0, INVISIBLE));

	engine->addLight(new Light(vec3(256, -128, 1472), 400, vec3(1.0f, 0.2f, 0.0f), 0.5f, 0, 0, INVISIBLE));
	engine->addParticleSystem(new ParticleSystem(vec3(256, -224, 1472), 260, 65));

	engine->addLight(new Light(vec3(2400, 250, 2100), 1100, vec3(1.0f, 0.3f, 0.1f), 0, 0, 0, INVISIBLE));

	engine->addLight(new Light(vec3(1676, 20, 1160), 300, vec3(0.5f, 0.6f, 1.0f), 0.5f, 0, 0, INVISIBLE));
	engine->addAmbientSoundSource(hum, vec3(1676, 52, 1160));
	engine->addLight(new Light(vec3(1304, 24, 1504), 300, vec3(0.7f, 0.8f, 1.0f), 0.4f, 0, 0));
	engine->addLight(new Light(vec3(1152,  8, 2136), 350, vec3(0.7f, 0.8f, 1.0f), 0.6f, 0, 0));


	engine->addLight(new Light(vec3(-1920 - 192, 272, 1920 - 192), 300, vec3(1.0f, 0.3f, 0.0f), 0.8f, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(-1920 + 192, 272, 1920 - 192), 300, vec3(1.0f, 0.3f, 0.0f), 0.8f, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(-1920 + 192, 272, 1920 + 192), 300, vec3(1.0f, 0.3f, 0.0f), 0.8f, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(-1920 - 192, 272, 1920 + 192), 300, vec3(1.0f, 0.3f, 0.0f), 0.8f, 0, 0, INVISIBLE | NOSHADOWS));

	engine->addLight(new Light(vec3(-1710, 510, 2940), 550, vec3(1.0f, 0.3f, 0.0f), 0, 0, 0, INVISIBLE));

	engine->addLight(new Light(vec3(-896,  200, -1664), 750, vec3(1.0f, 0.3f, 0.0f), 0, 1, 0, INVISIBLE));
	engine->addParticleSystem(new ParticleSystem(vec3(-896, 150, -1664), 150, 40));
	engine->addLight(new Light(vec3(-2048, 200, -896),  750, vec3(0.0f, 0.3f, 1.3f), 0, 1, 0, INVISIBLE));
	engine->addParticleSystem(new ParticleSystem(vec3(-2048, 150, -896), 150, 40, vec3(0.2f,0,0), vec3(0,0.6f,0), vec3(0.6f,0.4f,0)));
	engine->addLight(new Light(vec3(-2816, 200, -2304), 750, vec3(0.2f, 1.0f, 0.2f), 0, 0.7f, 0, INVISIBLE));
	engine->addParticleSystem(new ParticleSystem(vec3(-2816, 150, -2304), 150, 40, vec3(0.2f,0,0), vec3(0.6f,0.4f,0), vec3(0,0,0.6f)));

	engine->addLight(new Light(vec3(1632, 150, -1696), 750, vec3(0.2f, 0.5f, 0.8f), 0, 0.8f, 0, INVISIBLE));
	engine->addParticleSystem(new ParticleSystem(vec3(1632, 80, -1696), 120, 35, vec3(0.2f,0,0), vec3(0,0.1f,0.3f), vec3(0.3f,0.2f,0)));
	engine->addLight(new Light(vec3(1632, 190, -640),  550, vec3(1.0f, 0.7f, 0.3f), 0, 0.8f, 0, INVISIBLE));
	engine->addParticleSystem(new ParticleSystem(vec3(1632, 150, -640), 120, 35, vec3(0.5f,0,0), vec3(0,0.5f,0), vec3(0,0,0.5f)));
	engine->addLight(new Light(vec3(384, 64, -1840), 350, vec3(0.7f, 1.0f, 0.8f), 0.8f, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addAmbientSoundSource(hum, vec3(384, 64, -1880));

	engine->addLight(new Light(vec3(1380, 90, 0), 200, vec3(0.0f, 0.7f, 0.3f), 0.8f, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(684, 372, -785), 700, vec3(0.7f, 1.0f, 0.4f), 0, 1, 0, INVISIBLE));

	engine->addLight(new Light(vec3(-2638, 696, 1204), 1.07f, 3 * PI / 4, 1.0f, 1400, vec3(0.7f, 0.7f, 1.0f), 0, 1, 0));

	engine->addLight(new Light(vec3(-2816, 90,  16),   500, vec3(1.0f, 1.0f, 1.0f), 0, 0, 0, INVISIBLE));
	engine->addLight(new Light(vec3(-3552, 256, 1440), 300, vec3(1.0f, 1.0f, 1.0f), 1, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(-3552, 0,   64  ), 300, vec3(1.0f, 1.0f, 1.0f), 1, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(-3344, -96, 1024), 300, vec3(0.5f, 0.8f, 1.0f), 0, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(-2736, -96, 1496), 250, vec3(0.5f, 0.8f, 1.0f), 0, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(-2336, -96, 1128), 250, vec3(0.5f, 0.8f, 1.0f), 0, 0, 0, INVISIBLE | NOSHADOWS));

	engine->addLight(new Light(vec3(-1216, 90,   448), 300, vec3(1.0f, 1.0f, 1.0f), 0, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(-1104, 448, 1408), 300, vec3(1.0f, 1.0f, 1.0f), 0, 0, 0, INVISIBLE | NOSHADOWS));
	engine->addLight(new Light(vec3(-472,  448, 1408), 400, vec3(1.0f, 1.0f, 1.0f), 0, 0, 0, INVISIBLE));

	engine->addLight(new Light(vec3(-1920, 650, 1024), PI / 2, 0, 1.7f, 600, vec3(1.0f, 0.7f, 0.7f), 0, 1.1f, 0, INVISIBLE));

	engine->addLight(new Light(vec3(1120, 560, -448), 450, vec3(1.0f, 1.0f, 1.0f), 0, 0, 0, INVISIBLE));
	engine->addLight(new Light(vec3(640,  410,   32), 400, vec3(1.0f, 1.0f, 1.0f), 0, 0, 0, INVISIBLE));

	engine->addLight(new Light(vec3(-1387, 328, 2692), 300, vec3(1.0f, 1.0f, 1.0f), 0, 0,    0, INVISIBLE));
	engine->addLight(new Light(vec3( 73,    64, 2007), 300, vec3(1.0f, 0.1f, 0.1f), 0, 0.8f, 0, NOSHADOWS));

	engine->addLight(new Light(vec3(-576, 150, 2560), 700, vec3(1.0f, 0.4f, 0.1f), 0, 1.2f, 0, INVISIBLE));
	engine->addParticleSystem(new ParticleSystem(vec3(-576, 30, 2560), 250, 40));
	engine->addLight(new Light(vec3(-128, 222, 2848), 500, vec3(0.0f, 0.3f, 1.0f), 0, 1.2f, 0.5f, INVISIBLE));
	engine->addParticleSystem(new ParticleSystem(vec3(-128, 102, 2848), 150, 35, vec3(0.1f,0,0), vec3(0,0.4f,0), vec3(0.5f,0.3f,0)));
	engine->addLight(new Light(vec3(-224, 222, 3104), 500, vec3(1.0f, 0.3f, 0.0f), 0, 1.2f, 0.5f, INVISIBLE));
	engine->addParticleSystem(new ParticleSystem(vec3(-224, 102, 3104), 150, 35, vec3(0.5f,0.3f,0), vec3(0,0.4f,0), vec3(0.1f,0,0)));



	kickers.add(Kicker(vec3(192, -200, 960), vec3(320, -200, 960), vec3(192, -200, 832), vec3(0, 2.5f, 0)));

	kickers.add(Kicker(vec3(224, -204, 796), vec3(288, -204, 796), vec3(224, -260, 684),  vec3(0, 2.7f,  1.2f)));
	kickers.add(Kicker(vec3(288, -204, 996), vec3(224, -204, 996), vec3(288, -260, 1108), vec3(0, 2.7f, -1.2f)));

	kickers.add(Kicker(vec3(356, -204, 864), vec3(356, -204, 928), vec3(356+112, -260, 864), vec3(-1.2f, 2.7f, 0)));
	kickers.add(Kicker(vec3(156, -204, 928), vec3(156, -204, 864), vec3(156-112, -260, 928), vec3( 1.2f, 2.7f, 0)));

	kickers.add(Kicker(vec3(128, -128, -1184), vec3(288, -128, -1184), vec3(128, -128, -1344), vec3(0.1f, 3.2f, 0)));


	freeFly = false;
	showDebug = false;
	shootTime = -100;
	stencilBits = 8;


	int tab = configDialog->addTab("Settings");
	configDialog->addWidget(tab, useClipPlanes    = new CheckBox(0,  0, 200, 36, "Use clip planes",       true));
	configDialog->addWidget(tab, useScissor       = new CheckBox(0, 40, 232, 36, "Use scissor rects",     true));
	configDialog->addWidget(tab, useStencilBranch = new CheckBox(0, 80, 275, 36, "Use stencil branching", true));
	configDialog->addWidget(tab, new Label(0, 140, 200, 36, "Effects volume"));
	configDialog->addWidget(tab, soundVolume = new Slider(0, 180, 270, 24, 0, 1, 1));
	configDialog->addWidget(tab, new Label(0, 210, 200, 36, "Music volume"));
	configDialog->addWidget(tab, musicVolume = new Slider(0, 250, 270, 24, 0, 0.5f, 0.25f));

	tab = configDialog->addTab("Features");
	configDialog->addWidget(tab, useSpecular      = new CheckBox(0,   0, 270, 36, "Use specular lighting", true));
	configDialog->addWidget(tab, useParallax      = new CheckBox(0,  40, 270, 36, "Use parallax mapping",  true));
	configDialog->addWidget(tab, useShadow        = new CheckBox(0,  80, 260, 36, "Use shadow mapping",    true));
	configDialog->addWidget(tab, useSelfShadow    = new CheckBox(0, 120, 360, 36, "Use self-shadowing bumpmaps", true));
	configDialog->addWidget(tab, useVolumetric    = new CheckBox(0, 160, 290, 36, "Use volumetric lighting",     true));

	return true;
}

void App::exit(){
	engine->save();

	delete engine;
}

bool App::load(){
	if (!GL_EXT_framebuffer_object_supported){
		ErrorMsg("GL_EXT_framebuffer_object not support");
		return false;
	}

	if (!GLSL_supported){
		ErrorMsg("GLSL not supported");
		return false;
	}

	if (!GL_EXT_multi_draw_arrays_supported){
		ErrorMsg("GL_EXT_multi_draw_array not supported");
		return false;
	}


	MaterialDesc materials[] = {
		{ "16tile01",         0.02f, 0.8f, 0 },
		{ "FieldStone",       0.01f, 0.3f, 0 },
		{ "kicker",           0.02f, 0.5f, EMISSIVE },
		{ "light2",           0,     0,    EMISSIVE },
		{ "StoneWall_1-2",    0.03f, 0.7f, SELFSHADOW },
		{ "StoneWall_2-2",    0.05f, 0.5f, 0 },
		{ "WallHorizontal",   0,     0.5f, 0 },
		{ "Wood",             0,     0.5f, 0 },
		{ "brick01",          0.04f, 0.3f, SELFSHADOW },
		{ "floor_wood_3",     0,     0.2f, 0 },
		{ "floor_wood_4",     0,     0.2f, 0 },
		{ "laying_rock7",     0.04f, 0.5f, SELFSHADOW },
		{ "stone08",          0,     0.7f, 0 },
		{ "stone34",          0,     0.1f, 0 },
		{ "victoria",         0.04f, 0.8f, 0 },
		{ "wallpaper20",      0,     0,    0 },
		{ "Dragonheart",      0,     0,    SKYBOX },
	};


	if (!engine->makeDrawable(renderer, materials, elementsOf(materials), cullBack, noDepthTest, noDepthWrite, linearClamp)) return false;

	return true;
}

void App::unload(){

}

void App::drawFrame(){
	// Setup rendering options
	engine->setClipPlanesEnable(useClipPlanes->isChecked());
	engine->setScissorEnable(useScissor->isChecked());
	engine->setStencilBranchEnable(useStencilBranch->isChecked());
	engine->setSpecularEnable(useSpecular->isChecked());
	engine->setParallaxEnable(useParallax->isChecked());
	engine->setShadowEnable(useShadow->isChecked());
	engine->setSelfShadowEnable(useSelfShadow->isChecked());
	engine->setVolumetricEnable(useVolumetric->isChecked());

	if (wx >  1.57f) wx =  1.57f;
	if (wx < -1.57f) wx = -1.57f;

	engine->updateRockets(frameTime, camPos, camDir, soundVolume->getValue());

	// Update audio
	float cosX = cosf(wx), sinX = sinf(wx), cosY = cosf(wy), sinY = sinf(wy);
	engine->updateAudio(camPos, vec3(-cosX * sinY, -sinX, cosX * cosY), soundVolume->getValue(), musicVolume->getValue());

	float gunZ = 0;
	if (time - shootTime < 0.4f){
		float t = (time - shootTime) / 0.4f;
		t *= 4 * (1 - t) * (1 - t);
		gunZ = -5 * t;
	}

	float drawAmount = engine->draw(renderer, camPos, wx, wy, time, frameTime, gunZ, width, height, keys[KEY_F5] || keys[KEY_F6], keys[KEY_F6]);

	int nDrawCalls = renderer->getDrawCallCount();
	int nDrawnLights = engine->getDrawnLights();
	int nDrawnVolumeLights = engine->getDrawnVolumeLights();

	if (showDebug){
		renderer->setup2DMode(0, (float) width, 0, (float) height);
		char str[64];
		sprintf(str, "%.3f%%", 100 * drawAmount);

		renderer->drawText(str, width - 128.0f, height - 46.0f, 30, 38, defaultFont, linearClamp, blendSrcAlpha, noDepthTest);

		sprintf(str, "Drawn lights: %d", nDrawnLights);
		renderer->drawText(str, 10, height - 160.0f, 24, 28, defaultFont, linearClamp, blendSrcAlpha, noDepthTest);

		sprintf(str, "Drawn volume lights: %d", nDrawnVolumeLights);
		renderer->drawText(str, 10, height - 128.0f, 24, 28, defaultFont, linearClamp, blendSrcAlpha, noDepthTest);

		sprintf(str, "Draw calls: %d", nDrawCalls);
		renderer->drawText(str, 10, height - 96.0f, 24, 28, defaultFont, linearClamp, blendSrcAlpha, noDepthTest);

		sprintf(str, "%.0f, %.0f, %.0f", camPos.x, camPos.y, camPos.z);
		renderer->drawText(str, 10, height - 64.0f, 24, 28, defaultFont, linearClamp, blendSrcAlpha, noDepthTest);

		renderer->drawText(engine->isInOpenSpace(camPos)? "Inside" : "Outside", 10, height - 32.0f, 24, 28, defaultFont, linearClamp, blendSrcAlpha, noDepthTest);
	}
}
