/*
 * ${license.title}
 * Copyright (C) 2022 ${license.owner}
 * ${license.mailto}
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

//
// Created by webpigeon on 22/04/22.
//

#include "GameScene.h"

camera_type cam_mode = cam_free;

static void placeObject(fggl::ecs3::World& world, fggl::ecs::entity_t parent, fggl::ecs::entity_t prototype, glm::vec3 targetPos) {
	auto obj = world.copy(prototype);
	auto result = world.get<fggl::math::Transform>(obj);

	int xPos = (int)targetPos.x;
	int zPos = (int)targetPos.z * -1;

	// figure out the floor height
	auto heightMap = world.get<fggl::data::HeightMap>(parent);
	targetPos.y = heightMap->getValue(xPos, zPos); // TODO should really be the gradient at the required point

	result->origin( targetPos );
}

static void process_camera(fggl::ecs3::World& ecs, const std::shared_ptr<fggl::input::Input>& input) {
	auto cameras = ecs.findMatching<fggl::gfx::Camera>();
	fggl::ecs3::entity_t cam = cameras[0];

	auto camTransform = ecs.get<fggl::math::Transform>(cam);
	auto camComp = ecs.get<fggl::gfx::Camera>(cam);

	const glm::vec3 dir = ( camTransform->origin() - camComp->target );
	const glm::vec3 forward = glm::normalize( dir );

	// scroll wheel
	glm::vec3 motion(0.0f);
	float delta = input->mouse.axis( fggl::input::MouseAxis::SCROLL_Y );
	if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) )
		motion -= (forward * delta);
	camTransform->origin( camTransform->origin() + motion );

	if ( cam_mode == cam_arcball || input->mouse.down( fggl::input::MouseButton::MIDDLE ) ) {
		fggl::input::process_arcball(ecs, *input, cam);
	} else if ( cam_mode == cam_free ) {
		fggl::input::process_freecam(ecs, *input, cam);
	}
	fggl::input::process_edgescroll( ecs, *input, cam );
}

void GameScene::setup() {
	m_canvas.size( fggl::math::vec2(0,0), fggl::math::vec2(100, 100));

	auto types = m_world->types();

	// create camera using strings
	{
		auto prototype = m_world->create(false);
		m_world->add(prototype, types.find(fggl::math::Transform::name));
		m_world->add(prototype, types.find(fggl::gfx::Camera::name));
		m_world->add(prototype, types.find(fggl::input::FreeCamKeys::name));

		auto camTf = m_world->get<fggl::math::Transform>(prototype);
		if ( camTf != nullptr) {
			camTf->origin(glm::vec3(10.0f, 3.0f, 10.0f));
		}

		auto cameraKeys = m_world->get<fggl::input::FreeCamKeys>(prototype);
		if ( cameraKeys != nullptr ) {
			cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W);
			cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S);
			cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A);
			cameraKeys->right = glfwGetKeyScancode(GLFW_KEY_D);
			cameraKeys->rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q);
			cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E);
		}
	}

	fggl::ecs3::entity_t terrain;
	{
		terrain = m_world->create(false);
		m_world->add(terrain, types.find(fggl::math::Transform::name));

		auto camTf = m_world->get<fggl::math::Transform>(terrain);
		camTf->origin( glm::vec3(0.0f, 0.0f, 0.0f) );

		//auto terrainData = m_world.get<fggl::data::HeightMap>(terrain);
		fggl::data::HeightMap terrainData{};
		terrainData.clear();

		const siv::PerlinNoise::seed_type seed = 123456u;
		const siv::PerlinNoise perlin{ seed };

		for (int y = 0; y < 255; ++y) {
			for (int x = 0; x < 255; ++x) {
				const double noise = perlin.octave2D_11( (x * 0.01), (y * 0.01) , 4) * 10.f;
				terrainData.heightValues[x * 255 +y] = (float)noise;
			}
		}
		m_world->set<fggl::data::HeightMap>(terrain, &terrainData);
	}

	// create foundation object
	fggl::ecs3::entity_t foundation;
	{
		foundation = m_world->create(true);
		m_world->add(foundation, types.find(fggl::math::Transform::name));

		// plot rendering
		fggl::data::Mesh mesh;
		fggl::data::make_cube(mesh);
		mesh.removeDups();

		// add mesh as a component
		constexpr char shader[] = "phong";
		fggl::data::StaticMesh staticMesh{mesh, shader};
		m_world->set<fggl::data::StaticMesh>(foundation, &staticMesh);
	}

	// create building prototype
	fggl::ecs3::entity_t bunker;
	{
		bunker = m_world->create(true);
		m_world->add(bunker, types.find(fggl::math::Transform::name));

		// mesh
		int nSections = 2;
		constexpr float HALF_PI = M_PI / 2.0f;
		constexpr char shader[] = "phong";

		fggl::data::Mesh mesh;
		for (int j=-(nSections/2); j<=nSections/2; j++) {
			const auto shapeOffset = glm::vec3( 0.0f, 0.5f, (float)j * 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 );
		}
		mesh.removeDups();

		fggl::data::StaticMesh staticMesh{mesh, shader};
		m_world->set<fggl::data::StaticMesh>(bunker, &staticMesh);
	}

	for (int i=0; i<3; ++i) {
		glm::vec3 location(i * 6.5f + 1.0f, 0.0f, -7.0f);
		placeObject(*m_world, terrain, foundation, location);
	}

	int nCubes = 3;
	for ( int i=0; i<nCubes; i++ ) {
		glm::vec3 location;
		location.x = i * 6.f + 1.0f;
		location.z = -5.0f + 1.0f;
		placeObject(*m_world, terrain, bunker, location);
	}
}

void GameScene::update() {
process_camera(*m_world, m_inputs);
}

void GameScene::render(fggl::gfx::Graphics &gfx) {

		// render the 3D scene
		if ( m_world != nullptr ) {
			gfx.drawScene( *m_world );
		}

		const fggl::math::vec2 panelSize { 250.0F, 250.0F };
		const auto canvasY = gfx.canvasBounds().bottom - panelSize.y;
		m_canvas.size( {0.0F, canvasY}, panelSize);

		// now the 2D scene
		fggl::gfx::Paint paint;
		m_canvas.render(paint);
		gfx.draw2D(paint);
}