/*
 * Copyright 2013 Milos Tosic. All rights reserved.
 * License: http://www.opensource.org/licenses/BSD-2-Clause
 */

#include "common.h"
#include "bgfx_utils.h"
#include "imgui/imgui.h"

#include <bx/readerwriter.h>

struct KnightPos
{
	int32_t m_x;
	int32_t m_y;
};

static const KnightPos knightTour[8*4] =
{
	{0,0}, {1,2}, {3,3}, {4,1}, {5,3}, {7,2}, {6,0}, {5,2},
	{7,3}, {6,1}, {4,0}, {3,2}, {2,0}, {0,1}, {1,3}, {2,1},
	{0,2}, {1,0}, {2,2}, {0,3}, {1,1}, {3,0}, {4,2}, {5,0},
	{7,1}, {6,3}, {5,1}, {7,0}, {6,2}, {4,3}, {3,1}, {2,3},
};

class Lod : public entry::AppI
{
	void init(int _argc, char** _argv) BX_OVERRIDE
	{
		Args args(_argc, _argv);

		m_width  = 1280;
		m_height = 720;
		m_debug  = BGFX_DEBUG_TEXT;
		m_reset  = BGFX_RESET_VSYNC;

		bgfx::init(args.m_type, args.m_pciId);
		bgfx::reset(m_width, m_height, m_reset);

		// Enable debug text.
		bgfx::setDebug(m_debug);

		// Set view 0 clear state.
		bgfx::setViewClear(0
			, BGFX_CLEAR_COLOR|BGFX_CLEAR_DEPTH
			, 0x303030ff
			, 1.0f
			, 0
			);

		s_texColor   = bgfx::createUniform("s_texColor",   bgfx::UniformType::Int1);
		s_texStipple = bgfx::createUniform("s_texStipple", bgfx::UniformType::Int1);
		u_stipple    = bgfx::createUniform("u_stipple",    bgfx::UniformType::Vec4);

		m_program = loadProgram("vs_tree", "fs_tree");

		m_textureLeafs = loadTexture("leafs1.dds");
		m_textureBark  = loadTexture("bark1.dds");

		const bgfx::Memory* stippleTex = bgfx::alloc(8*4);
		memset(stippleTex->data, 0, stippleTex->size);

		for (uint32_t ii = 0; ii < 32; ++ii)
		{
			stippleTex->data[knightTour[ii].m_y * 8 + knightTour[ii].m_x] = ii*4;
		}

		m_textureStipple = bgfx::createTexture2D(8, 4, 1
			, bgfx::TextureFormat::R8
			, BGFX_TEXTURE_MAG_POINT|BGFX_TEXTURE_MIN_POINT
			, stippleTex
			);

		m_meshTop[0] = meshLoad("meshes/tree1b_lod0_1.bin");
		m_meshTop[1] = meshLoad("meshes/tree1b_lod1_1.bin");
		m_meshTop[2] = meshLoad("meshes/tree1b_lod2_1.bin");

		m_meshTrunk[0] = meshLoad("meshes/tree1b_lod0_2.bin");
		m_meshTrunk[1] = meshLoad("meshes/tree1b_lod1_2.bin");
		m_meshTrunk[2] = meshLoad("meshes/tree1b_lod2_2.bin");

		// Imgui.
		imguiCreate();

		m_scrollArea  = 0;
		m_transitions = true;

		m_transitionFrame = 0;
		m_currLod         = 0;
		m_targetLod       = 0;
	}

	virtual int shutdown() BX_OVERRIDE
	{
		imguiDestroy();

		for (uint32_t ii = 0; ii < BX_COUNTOF(m_meshTop); ++ii)
		{
			meshUnload(m_meshTop[ii]);
			meshUnload(m_meshTrunk[ii]);
		}

		// Cleanup.
		bgfx::destroyProgram(m_program);

		bgfx::destroyUniform(s_texColor);
		bgfx::destroyUniform(s_texStipple);
		bgfx::destroyUniform(u_stipple);

		bgfx::destroyTexture(m_textureStipple);
		bgfx::destroyTexture(m_textureLeafs);
		bgfx::destroyTexture(m_textureBark);

		// Shutdown bgfx.
		bgfx::shutdown();

		return 0;
	}

	bool update() BX_OVERRIDE
	{
		if (!entry::processEvents(m_width, m_height, m_debug, m_reset, &m_mouseState) )
		{
			imguiBeginFrame(m_mouseState.m_mx
				,  m_mouseState.m_my
				, (m_mouseState.m_buttons[entry::MouseButton::Left  ] ? IMGUI_MBUT_LEFT   : 0)
				| (m_mouseState.m_buttons[entry::MouseButton::Right ] ? IMGUI_MBUT_RIGHT  : 0)
				| (m_mouseState.m_buttons[entry::MouseButton::Middle] ? IMGUI_MBUT_MIDDLE : 0)
				,  m_mouseState.m_mz
				, m_width
				, m_height
				);

			imguiBeginScrollArea("Toggle transitions", m_width - m_width / 5 - 10, 10, m_width / 5, m_height / 6, &m_scrollArea);
			imguiSeparatorLine();

			if (imguiButton(m_transitions ? "ON" : "OFF") )
			{
				m_transitions = !m_transitions;
			}

			static float distance = 2.0f;
			imguiSlider("Distance", distance, 2.0f, 6.0f, 0.01f);

			imguiEndScrollArea();
			imguiEndFrame();

			// Set view 0 default viewport.
			bgfx::setViewRect(0, 0, 0, m_width, m_height);

			// This dummy draw call is here to make sure that view 0 is cleared
			// if no other draw calls are submitted to view 0.
			bgfx::touch(0);

			int64_t now = bx::getHPCounter();
			static int64_t last = now;
			const int64_t frameTime = now - last;
			last = now;
			const double freq = double(bx::getHPFrequency() );
			const double toMs = 1000.0/freq;

			// Use debug font to print information about this example.
			bgfx::dbgTextClear();
			bgfx::dbgTextPrintf(0, 1, 0x4f, "bgfx/examples/12-lod");
			bgfx::dbgTextPrintf(0, 2, 0x6f, "Description: Mesh LOD transitions.");
			bgfx::dbgTextPrintf(0, 3, 0x0f, "Frame: % 7.3f[ms]", double(frameTime)*toMs);

			float at[3]  = { 0.0f, 1.0f,      0.0f };
			float eye[3] = { 0.0f, 2.0f, -distance };

			// Set view and projection matrix for view 0.
			const bgfx::HMD* hmd = bgfx::getHMD();
			if (NULL != hmd && 0 != (hmd->flags & BGFX_HMD_RENDERING) )
			{
				float view[16];
				bx::mtxQuatTranslationHMD(view, hmd->eye[0].rotation, eye);

				float proj[16];
				bx::mtxProj(proj, hmd->eye[0].fov, 0.1f, 100.0f);

				bgfx::setViewTransform(0, view, proj);

				// Set view 0 default viewport.
				//
				// Use HMD's m_width/m_height since HMD's internal frame buffer size
				// might be much larger than window size.
				bgfx::setViewRect(0, 0, 0, hmd->width, hmd->height);
			}
			else
			{
				float view[16];
				bx::mtxLookAt(view, eye, at);

				float proj[16];
				bx::mtxProj(proj, 60.0f, float(m_width)/float(m_height), 0.1f, 100.0f);
				bgfx::setViewTransform(0, view, proj);

				// Set view 0 default viewport.
				bgfx::setViewRect(0, 0, 0, m_width, m_height);
			}

			float mtx[16];
			bx::mtxScale(mtx, 0.1f, 0.1f, 0.1f);

			float stipple[3];
			float stippleInv[3];

			const int currentLODframe = m_transitions ? 32-m_transitionFrame : 32;
			const int mainLOD = m_transitions ? m_currLod : m_targetLod;

			stipple[0] = 0.0f;
			stipple[1] = -1.0f;
			stipple[2] = (float(currentLODframe)*4.0f/255.0f) - (1.0f/255.0f);

			stippleInv[0] = (float(31)*4.0f/255.0f);
			stippleInv[1] = 1.0f;
			stippleInv[2] = (float(m_transitionFrame)*4.0f/255.0f) - (1.0f/255.0f);

			const uint64_t stateTransparent = 0
				| BGFX_STATE_RGB_WRITE
				| BGFX_STATE_ALPHA_WRITE
				| BGFX_STATE_DEPTH_TEST_LESS
				| BGFX_STATE_CULL_CCW
				| BGFX_STATE_MSAA
				| BGFX_STATE_BLEND_ALPHA
				;

			const uint64_t stateOpaque = BGFX_STATE_DEFAULT;

			bgfx::setTexture(0, s_texColor, m_textureBark);
			bgfx::setTexture(1, s_texStipple, m_textureStipple);
			bgfx::setUniform(u_stipple, stipple);
			meshSubmit(m_meshTrunk[mainLOD], 0, m_program, mtx, stateOpaque);

			bgfx::setTexture(0, s_texColor, m_textureLeafs);
			bgfx::setTexture(1, s_texStipple, m_textureStipple);
			bgfx::setUniform(u_stipple, stipple);
			meshSubmit(m_meshTop[mainLOD], 0, m_program, mtx, stateTransparent);

			if (m_transitions
			&& (m_transitionFrame != 0) )
			{
				bgfx::setTexture(0, s_texColor, m_textureBark);
				bgfx::setTexture(1, s_texStipple, m_textureStipple);
				bgfx::setUniform(u_stipple, stippleInv);
				meshSubmit(m_meshTrunk[m_targetLod], 0, m_program, mtx, stateOpaque);

				bgfx::setTexture(0, s_texColor, m_textureLeafs);
				bgfx::setTexture(1, s_texStipple, m_textureStipple);
				bgfx::setUniform(u_stipple, stippleInv);
				meshSubmit(m_meshTop[m_targetLod], 0, m_program, mtx, stateTransparent);
			}

			int lod = 0;
			if (eye[2] < -2.5f)
			{
				lod = 1;
			}

			if (eye[2] < -5.0f)
			{
				lod = 2;
			}

			if (m_targetLod != lod)
			{
				if (m_targetLod == m_currLod)
				{
					m_targetLod = lod;
				}
			}

			if (m_currLod != m_targetLod)
			{
				m_transitionFrame++;
			}

			if (m_transitionFrame > 32)
			{
				m_currLod = m_targetLod;
				m_transitionFrame = 0;
			}

			// Advance to next frame. Rendering thread will be kicked to
			// process submitted rendering primitives.
			bgfx::frame();

			return true;
		}

		return false;
	}

	entry::MouseState m_mouseState;
	uint32_t m_width;
	uint32_t m_height;
	uint32_t m_debug;
	uint32_t m_reset;

	Mesh* m_meshTop[3];
	Mesh* m_meshTrunk[3];

	bgfx::ProgramHandle m_program;
	bgfx::UniformHandle s_texColor;
	bgfx::UniformHandle s_texStipple;
	bgfx::UniformHandle u_stipple;

	bgfx::TextureHandle m_textureStipple;
	bgfx::TextureHandle m_textureLeafs;
	bgfx::TextureHandle m_textureBark;

	int32_t m_scrollArea;
	int32_t m_transitionFrame;
	int32_t m_currLod;
	int32_t m_targetLod;
	bool    m_transitions;
};

ENTRY_IMPLEMENT_MAIN(Lod);
