/*******************************************************
 * 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 "Setup_Worker.h"
#include "mesh/Mesh.h"
#include "setup/SetupFactory.hpp"
#include "setup/setup.hpp"
#include "misc/batchRunner.hpp"
#include "misc/miscFunctions.hpp"
#include "mesh/MeshGenerator.h"
#include "nlohmann/json.hpp"
#include "misc/miscFunctions.hpp"

using json = nlohmann::json;

json getCaseJson(std::string wdir);

Setup_Worker::Setup_Worker(std::shared_ptr<Job>& j, PluginManager m) : Worker(j, m)
{
	pluginManager = m;
}


messagedata Setup_Worker::run(MessageQueue_ messageQueue)
{
	try

	{
		Timer t;
		t.setMessage("Setup_Worker execution time: ");

		std::optional<json> wDir_json = assignedJob_->getArg("caseDir");
		std::optional<json> meshFile_json = assignedJob_->getArg("meshFile");
		std::optional<json> input_json;
		std::optional<json> post_json;
		std::optional<json> geomdata_json;

		//Check if mesh file exists
		//try to fix by prepending working directory if mesh file does not exist
		if (meshFile_json.has_value() && !meshFile_json.value().is_null())
		{
			if (!misc::fileExist(meshFile_json.value()))
			{
				std::string fn_abs = wDir_json.value().get<std::string>() + "/" + meshFile_json.value().get<std::string>();
				if (misc::fileExist(fn_abs))
					meshFile_json = fn_abs;
			}
		}


		//create setup of certain type (currently internally only getdp, externally through sample-plugin)
		Setup* setup;
		std::shared_ptr<OutputAnalyzer> analyzer = nullptr;


		if (pluginManager.HasPlugin("sample-plugin"))
			setup = pluginManager.GetSetup("sample-plugin");
		else
		{
			setup = SetupFactory("GETDP").CreateSetup();
			try
			{
				analyzer = std::make_shared<GetdpOutputAnalyzer>(GetdpOutputAnalyzer(messageQueue));
			}
			catch (json::exception& exc)
			{
				delete setup;
				return generateError("Exception while initializing getdp output analyzer " + std::string(exc.what()) + " exception id: " + std::to_string(exc.id));
			}
		}

		setup->setMessageQueue(messageQueue);
		setup->setOutputAnalyzer(analyzer);
		setup->setStopper(assignedJob_->getStopper());

		setup->setGuid(assignedJob_->guid());

		if (assignedJob_->command() == "calculatePostValues" or assignedJob_->command() == "convertOldResultsJson")
		{
			input_json = Setup::getLastParams(wDir_json.value().get<std::string>());
			post_json = assignedJob_->getArg("postParams");

			json caseJson = getCaseJson(wDir_json.value().get<std::string>());
			std::string gd_hash = caseJson["results"]["geometryData"].get<std::string>();
			geomdata_json = caseJson["geometryDataMap"][gd_hash].get<json>();

			if (!meshFile_json.has_value())
				meshFile_json = assignedJob_->getArg("meshFile");
		}
		else if (assignedJob_->command() != "calculate")
		{
			delete setup;
			return generateError("No command " + assignedJob_->command() + " defined");
		}
		else //is calculate
		{
				input_json = assignedJob_->getArg("physicsParams");
				post_json = assignedJob_->getArg("postParams");
				geomdata_json = assignedJob_->getArg("geometryData");
		}

		setup->setWDir(wDir_json.value().get<std::string>());

		std::shared_ptr<GeometryData> gd = std::make_shared<GeometryData>(geomdata_json.value(), wDir_json.value().get<std::string>());

		setup->setGeometryData(gd);

		// create mesh
		std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>();
		//Check if mesh file exists
		//try to fix by prepending working directory if mesh file does not exist
		if (meshFile_json.has_value() && !meshFile_json.value().is_null())
		{
			if (!misc::fileExist(meshFile_json.value()))
			{
				std::string fn_abs = wDir_json.value().get<std::string>() + "/" + meshFile_json.value().get<std::string>();
				if (misc::fileExist(fn_abs))
					meshFile_json = fn_abs;
			}
		}

		try
		{
			messageQueue->push(messagedata(messagedata::info_lvl, messagedata::log, "Reading mesh data."));
			mesh->readMesh(meshFile_json.value().get<std::string>(), std::string("GMSH"));
		}
		catch (std::string errMsg)
		{
			delete setup;
			return generateError("Error reading mesh " + errMsg);
		}

		setup->setMesh(mesh);

		if (assignedJob_->isStopRequested())
		{
			delete setup;
			return generateError("Simulation aborted by user ");
		}

		// IMPORTANT: readInput has to be done after mesh is read!!
		// this is because mesh scale is read through the readInput. If the mesh is read after readInput,
		// the mesh scale will be reset to 1
		try
		{
			messageQueue->push(messagedata(messagedata::info_lvl, messagedata::log, "Reading physics input."));
			setup->readInput(input_json.value().get<json>());
			setup->readPost(post_json.value().get<json>());
			setup->readConfigurations();
			messageQueue->push(messagedata(messagedata::info_lvl, messagedata::log, "Preparing simulation setup."));
			setup->writeSetup();
		}
		catch (json::exception& e)
		{
			std::string errMsg = std::string(e.what()) + ", exception id: " + std::to_string(e.id);
			LOG(ERROR) << errMsg;
			messagedata retData(messagedata::error, messagedata::response, "Error running setup: " + errMsg);
			retData.guid = assignedJob_->guid();
			delete setup;
			return retData;
		}
		catch (std::exception& e)
		{
			std::string errMsg = std::string(e.what());
			LOG(ERROR) << errMsg;
			messagedata retData(messagedata::error, messagedata::response, "Error running setup: " + errMsg);
			retData.guid = assignedJob_->guid();
			delete setup;
			return retData;
		}

		if (assignedJob_->isStopRequested())
		{
			delete setup;
			return generateError("Simulation aborted by user ");
		}


		messagedata returnData;

		if (assignedJob_->command() == "convertOldResultsJson") {
			messageQueue->push(messagedata(messagedata::info_lvl, messagedata::log, "Converting old results to new format."));
			messagedata msg = setup->convertCaseNewResultJson();
			messageQueue->push(messagedata(messagedata::info_lvl, messagedata::log, "Conversion completed successfully."));
			msg.guid = assignedJob_->guid();
			delete setup;
			return msg;
		}
		

		if (assignedJob_->command() == "calculatePostValues")
		{
			messageQueue->push(messagedata(messagedata::info_lvl, messagedata::log, "Post processing results."));

			messagedata postOnlyReturn = setup->postProcess();
			messageQueue->push(messagedata(messagedata::info_lvl, messagedata::log, "Post processing finished."));

			postOnlyReturn.guid = assignedJob_->guid();
			delete setup;
			return postOnlyReturn;
		}

		messageQueue->push(messagedata(messagedata::info_lvl, messagedata::log, "Starting calculation."));

		// RUN loop
		// loop necessary if mesh motion is present. Otherwise, executed only once


		try
		{
			do
			{
				setup->remesh();

				returnData = setup->calculate();

				if (returnData.msgStatus != messagedata::success)
				{
					delete setup;
					returnData.guid = assignedJob_->guid();
					return returnData;
				}

				messagedata postReturnData = setup->postProcess();

				if (postReturnData.msgStatus != messagedata::success)
				{
					delete setup;
					postReturnData.guid = assignedJob_->guid();
					return postReturnData;
				}

				setup->finalize();
				if (assignedJob_->isStopRequested())
					break;
			} while (setup->update());
		}
		catch (std::exception& e)
		{
			delete setup;
			return generateError("Remeshing error: " + std::string(e.what()));
		}

		json outputJson;
		outputJson["calculationTime"] = t.duration_s().count();
		returnData = messagedata(messagedata::success, messagedata::response, outputJson.dump());
		returnData.guid = assignedJob_->guid();

		delete setup;
		return returnData;
	}
	catch (json::exception& e)
	{
		return generateError("Error during running simulation error: " + std::string(e.what()));
	}
	catch (std::exception e)
	{
		return generateError("Error during running simulation error: " + std::string(e.what()));

	}


}


json getCaseJson(std::string wdir)
{
	std::string str;
	std::ifstream inFile;
	inFile.open(wdir + "//case.json");
	if (inFile.is_open()) {
		std::stringstream strStream;
		strStream << inFile.rdbuf();
		str = strStream.str();
	}
	else {
		return json::object();
	}
	inFile.close();

	json caseJson;
	std::istringstream iss(str);
	iss >> caseJson;
	return caseJson;
}


