#include <filesystem>
#include <glm/trigonometric.hpp>
#include <iostream>

#include <fggl/gfx/window.hpp>
#include <fggl/gfx/ogl.hpp>
#include <fggl/gfx/renderer.hpp>
#include <fggl/gfx/shader.hpp>
#include <fggl/data/procedural.hpp>
#include <fggl/ecs/ecs.hpp>
#include <fggl/debug/debug.h>
#include <fggl/data/storage.hpp>

// prototype of resource discovery
void discover(std::filesystem::path base) {

	std::vector< std::filesystem::path > contentPacks;
	for ( auto& item : std::filesystem::directory_iterator(base) ) {

		// content pack detection
		if ( std::filesystem::is_directory( item ) ) {
			auto manifest = item.path() / "manifest.yml";
			if ( std::filesystem::exists( manifest ) ) {
				contentPacks.push_back( item.path() );
			}
		}
		
	}

	// what did we find?
	std::cerr << "found pack(s): " << std::endl;
	for ( auto& pack : contentPacks ) {
		std::cerr << pack << std::endl;
	}

}

int main(int argc, char* argv[]) {
	fggl::gfx::Context ctx;

	// build our main window
	fggl::gfx::Window win;
	win.fullscreen( true );

	// opengl time
	fggl::gfx::Graphics ogl(win);
	fggl::gfx::MeshRenderer meshRenderer;

	// debug layer
	fggl::debug::DebugUI debug(win);
	debug.visible(false);

	// storage API
	fggl::data::Storage storage;
	discover( storage.resolvePath(fggl::data::Data, "res") );

	fggl::gfx::ShaderCache cache(storage);

	fggl::gfx::ShaderConfig config;
	config.name = "unlit";
	config.vertex = "unlit_vert.glsl";
	config.fragment = "unlit_frag.glsl";
	auto shader = cache.load(config);

	fggl::gfx::ShaderConfig configPhong;
	configPhong.name = "phong";
	configPhong.vertex = configPhong.name + "_vert.glsl";
	configPhong.fragment = configPhong.name + "_frag.glsl";
//	configPhong.fragment = configPhong.name + "_normals_frag.glsl";
	auto shaderPhong = cache.load(configPhong);

	fggl::gfx::ShaderConfig configNormals;
	configNormals.name = "normals";
	configNormals.hasGeom = true;
	configNormals.vertex = configNormals.name + "_vert.glsl";
	configNormals.geometry = configNormals.name + "_geom.glsl";
	configNormals.fragment = configNormals.name + "_frag.glsl";
	auto shaderNormals = cache.load( configNormals );

	// create ECS
	fggl::ecs::ECS ecs;
	ecs.registerComponent<fggl::gfx::MeshToken>();
	ecs.registerComponent<fggl::math::Transform>();

	int nCubes = 1;
	int nSections = 3;

	constexpr float HALF_PI = M_PI / 2.0f;

	for ( int i=0; i<nCubes; i++ ) {
		auto entity = ecs.createEntity();

		// set the position
		auto result = ecs.addComponent<fggl::math::Transform>(entity);
		result->origin( glm::vec3( 0.0f, 0.0f, i * -1.0f) );

		fggl::data::Mesh mesh;
		for (int i=-(nSections/2); i<=nSections/2; i++) {
			const auto shapeOffset = glm::vec3( 0.0f, 0.0f, i * 1.0f );

			const auto cubeMat = glm::translate( fggl::math::mat4( 1.0f ) , shapeOffset );
			const auto leftSlope = fggl::math::modelMatrix( glm::vec3(-1.0f, 0.0f, 0.0f) + shapeOffset, glm::vec3( 0.0f, -HALF_PI, 0.0f) );
			const auto rightSlope = fggl::math::modelMatrix( glm::vec3( 1.0f, 0.0f, 0.0f) + shapeOffset, glm::vec3( 0.0f, HALF_PI, 0.0f) );

			fggl::data::make_cube( mesh, cubeMat );
			fggl::data::make_slope( mesh, leftSlope );
			fggl::data::make_slope( mesh, rightSlope );
		}

		auto token = meshRenderer.upload( mesh );
		token.pipeline = shaderPhong;
		ecs.addComponent<fggl::gfx::MeshToken>(entity, token);
	}


	float time = 0.0f;
	float dt = 16.0f;
	while( !win.closeRequested() ) {
		ctx.pollEvents();
		debug.frameStart();

		// update step
		time += dt;

		float amount = glm::radians( time / 2048.0f * 360.0f );
		auto spinners = ecs.getEntityWith<fggl::math::Transform>();
		for ( auto entity : spinners ) {
			auto transform = ecs.getComponent<fggl::math::Transform>(entity);
			transform->euler(glm::vec3(0.0f, amount, 0.0f));
		}

		// render step
		ogl.clear();

		// render using real shader
		auto renderables = ecs.getEntityWith<fggl::gfx::MeshToken>();
		for ( auto renderable : renderables ) {
			auto token = ecs.getComponent<fggl::gfx::MeshToken>(renderable);
			token->pipeline = shaderPhong;
		}
		meshRenderer.render(win, ecs, 16.0f);

		// render using normals shader
		for ( auto renderable : renderables ) {
			auto token = ecs.getComponent<fggl::gfx::MeshToken>(renderable);
			token->pipeline = shaderNormals;
		}
		meshRenderer.render(win, ecs, 16.0f);

		debug.draw();
		win.swap();
	}

	return 0;
}