/*
 * This file is part of FGGL.
 *
 * FGGL 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.
 *
 * FGGL 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 FGGL.
 * If not, see <https://www.gnu.org/licenses/>.
 */

//
// Created by webpigeon on 11/09/22.
//

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <map>

#include <yaml-cpp/yaml.h>

#include "fggl/data/model.hpp"
#include "fggl/data/procedural.hpp"
#include "fggl/entity/loader/loader.hpp"

#include "binary.hpp"

constexpr const char* YAML_PREFAB = "prefabs";
constexpr const char* YAML_COMPONENT = "components";

constexpr uint32_t DEFAULT_STACKS = 16;
constexpr uint32_t DEFAULT_SLICES = 16;
constexpr const char *SHAPE_SPHERE{"sphere"};
constexpr const char *SHAPE_BOX{"box"};

namespace fggl {
	static void process_shape(const YAML::Node &node, data::Mesh &mesh) {
		auto transform = data::OFFSET_NONE;

		auto offset = node["offset"].as<math::vec3>(math::VEC3_ZERO);
		transform = glm::translate(transform, offset);

		auto scale = node["scale"].as<math::vec3>(math::VEC3_ONES);
		transform = glm::scale(transform, scale);
		debug::debug("scale: {}, {}, {}", scale.x, scale.y, scale.z);

		// now the shape itself
		auto type = node["type"].as<std::string>();
		if (type == SHAPE_BOX) {
			data::make_cube(mesh, transform);
		} else if (type == SHAPE_SPHERE) {
			auto stacks = node["stacks"].as<uint32_t>(DEFAULT_STACKS);
			auto slices = node["slices"].as<uint32_t>(DEFAULT_SLICES);
			data::make_sphere(mesh, transform, stacks, slices);
		} else {
			debug::log(debug::Level::warning, "unknown shape type requested: {}", type);
		}
	}

	static void process_mesh(const YAML::Node& spec, fggl::data::Mesh& mesh) {
		// process shape structure
		if (spec["shape"].IsSequence()) {
			for (const auto &node : spec["shape"]) {
				process_shape(node, mesh);
			}
		} else {
			process_shape(spec["shape"], mesh);
		}
		mesh.removeDups();
	}
}

void write_comp_static_model(FILE* fout, const YAML::Node& config) {
	// calculate mesh
	fggl::data::Mesh mesh;
	fggl::process_mesh( config, mesh);

	// calculate correct name
	auto meshName = config["shape_id"];
	auto meshNameStr = meshName.as<std::string>();
	auto meshGuid = fggl::util::make_guid_rt(meshNameStr);

	// header data
	fggl::data::ModelHeader header{
		.guid = meshGuid.get(),
		.type = fggl::data::ModelType::OPENGL,
	};

	// write the mesh to disk
	fggl::data::write_model( fout, header, mesh );
}

int main(int argc, char* argv[]) {
	auto* packName = argv[1];
	std::cout << "generating " << packName << std::endl;

	std::map< fggl::util::GUID, std::string > guids;

	// rollball
	auto rollPath = std::filesystem::current_path() / "rollball.yml";
	std::cout << rollPath << std::endl;

	YAML::Node root = YAML::LoadFile(rollPath);
	if ( !root ){
		return EXIT_FAILURE;
	}

	//std::cout << root[ YAML_PREFAB ] << std::endl;

	std::map<std::string, std::function<void(FILE* fout, const YAML::Node&)>> converters;
	converters["StaticMesh"] = write_comp_static_model;

	std::string meshFile = "rollball_models.bin";
	FILE* fout = fopen(meshFile.c_str(), "w");

	// pack prefabs
	for (const auto& prefab : root[YAML_PREFAB]) {
		// name
		auto name = prefab["name"].as<std::string>();
		auto nameRef = fggl::util::make_guid(name.c_str());
		guids[nameRef] = name;
		std::cout << name << std::endl;

		// parent
		//auto parentName = prefab["parent"].as<std::string>();
		//auto parentGuid = fggl::util::make_guid(parentName.c_str());

		// components
		for( const auto& key : prefab[YAML_COMPONENT] ) {
			auto compStr = key.first.as<std::string>();
			auto compGuid = fggl::util::make_guid_rt(compStr);
			guids[compGuid] = compStr;

			// figure out the type
			auto compWrite = converters.find(compStr);
			if ( compWrite != converters.end()) {
				compWrite->second(fout, key.second);
			}
		}

	}

	fclose(fout);

	// guid table
	std::cerr << "GUID Table" << std::endl;
	std::cerr << "guid,str" << std::endl;
	for (auto& [guid,str] : guids) {
		std::cerr << guid.get() << "," << str << std::endl;
	}

	return EXIT_SUCCESS;
}