/*******************************************************
 * Copyright (C) 2017-2020 CENOS Ltd vg@cenos-platform.com
 *
 * This file is part of CENOS.
 *
 * CENOS can not be copied and/or distributed without the express
 * permission of CENOS Ltd
 *******************************************************/

#include "Mesh_Worker.h"
#include "mesh/Mesh.h"
#include "mesh/Mesh_IO.h"
#include "mesh/AnalyzeMesh.hpp"
#include "topology/geometryData.h"
#include "mesh/MeshGenerator.h"
#include "mesh/AutoMeshSize.h"
#include "misc/ProgressIndicator.hpp"
#include <functional>
#include <optional>
#include "setup/physics.hpp"
#include "cenos_exception.h"
#include "nlohmann/json.hpp"

using json = nlohmann::json;

Mesh_Worker::Mesh_Worker(std::shared_ptr<Job>& j) : Worker(j) 
{
}


messagedata Mesh_Worker::run(MessageQueue_ messageQueue)
{
	try
	{
		std::optional<json> wdir_json = assignedJob_->getArg("caseDir");

		std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>();

		std::function<void(messagedata)> message_bind = std::bind(&MessageQueue::push, messageQueue, std::placeholders::_1);
		ProgressIndicator progress_indicator(message_bind, assignedJob_->guid());

		//analyze commands
		if (assignedJob_->command() == "generateMesh")
		{
			if (!assignedJob_->getArg("physicsParams").has_value())
			{
				throw(cenos_exception("Error in data passed to Mesh_Worker:: no physicsParams found. This is internal error. Please contact Cenos support!"));
			}
			if (!assignedJob_->getArg("meshParams").has_value())
			{
				throw(cenos_exception("Error in data passed to Mesh_Worker:: no meshParams found. This is internal error. Please contact Cenos support!"));
			}

			std::optional<json> geomdata_json = assignedJob_->getArg("geometryData");

			progress_indicator.sendLog("Exploring geometry");
			std::shared_ptr<GeometryData> geom_data = std::make_shared<GeometryData>(geomdata_json.value(), wdir_json.value().get<std::string>());

			progress_indicator.sendLog("Preparing geometry for meshing");

			std::shared_ptr<MeshGenerator> mg = std::make_shared<MeshGenerator>(geom_data);
			mg->setWdir(wdir_json.value().get<std::string>());
			mg->setProgressIndicator(std::make_shared<ProgressIndicator>(progress_indicator));

			 
			json phys_json = assignedJob_->getArg("physicsParams").value();
			json mesh_params = assignedJob_->getArg("meshParams").value();

			AutoMeshSize ams(geom_data, mg, phys_json, mesh_params);

			//std::string wdir = wdir_json.value().get<std::string>();
			//std::string json_path = wdir + "\\meshSetup.json";
			//std::cout << wdir + "\\meshSetup.json" << std::endl;
			//if (!std::filesystem::exists(json_path)) {
			//	// json not found, creating a new one
			//	json meshSetup = ams.generateMeshSetupJson();
			//	std::ofstream o(json_path);
			//	o << std::setw(4) << meshSetup << std::endl;
			//	throw cenos_exception("new meshSetup json was created in case folder");
			//	//ams.applyMeshSetupJson(meshSetup);
			//}
			//else {
			//	// json found, applying that one
			//	std::ifstream ifs(json_path);
			//	json meshSetup = json::parse(ifs);
			//	ams.applyMeshSetupJson(meshSetup);
			//}


			// The userMeshSetup contains an addition "sizeMultiplier" key for each refinment
			// This multplier mush be applied to the actual sizes before being passed to the
			// mesh generator as "technicalMeshSetup"
			json userMeshSetup = ams.generateMeshSetupJson();
			json technicalMeshSetup = ams.applyMultiplier(userMeshSetup);
			//std::cout << meshSetup.dump(4) << std::endl;
			ams.applyMeshSetupJson(technicalMeshSetup);

			progress_indicator.sendLog("Generating mesh");

			std::shared_ptr<Mesh> mesh;

			bool stop = false;
			mg->setStopper(&stop);

			auto mesh_future = std::async(std::launch::async, &MeshGenerator::generateMesh, mg);

			do
			{
				Sleep(100);

				if (assignedJob_->isStopRequested())
				{
					stop = true;
					mesh_future.wait();
				}

			} while (mesh_future.wait_for(std::chrono::milliseconds(1)) != std::future_status::ready);

			mesh = mesh_future.get();

			std::string wDir = assignedJob_->getArg("caseDir").value().get<std::string>();
			AnalyzeMesh anl(mesh, technicalMeshSetup, wDir);
			anl.analyze();

			std::string filename = wdir_json.value().get<std::string>() + "\\geometry\\meshFile.msh";
			mesh->writeMesh(filename, std::string("GMSH"));
			geom_data->setMeshStats(mesh->getMeshStats());
			geom_data->setMeshFile("geometry\\meshFile.msh");

			json return_data;
			return_data["geometryData"] = geom_data->getJson();
			messagedata retData(messagedata::success, messagedata::response, return_data.dump());
			retData.guid = assignedJob_->guid();
			return retData;
		}
		else
		{
			return generateError("No command " + assignedJob_->command() + " defined");
		}
	}
	catch (std::exception &e)
	{
		return generateError("Error during meshing " + std::string(e.what()));
	}
}