/**
 * FreeCadModule.cpp
 *
 *  Created on: Jul 13, 2020
 *      Author: vadims
 */

#include "FreeCadModule.h"
#include <boost/filesystem.hpp>
#include <boost/algorithm/string/join.hpp>
#include "misc/miscFunctions.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <ctime>


FreeCadModule::FreeCadModule() : Module()
{
	stopRequired = false;
}

FreeCadModule::~FreeCadModule()
{
}

void FreeCadModule::setWDir(std::string wDir_)
{
	wDir = wDir_;
}

void FreeCadModule::setPort(int port_)
{
	port = port_;
}

void FreeCadModule::setGuid(std::string guid_)
{
	guid = guid_;
}

void FreeCadModule::addCadFile(std::string cadfile)
{
	cadfiles.push_back(cadfile);
}

void FreeCadModule::setGeometryData(std::optional<json> geometryData_)
{
	geometryData = geometryData_;
}




void FreeCadModule::stop()
{
	stopRequired = true;
}

json openPipe(std::string inp) {
	char buffer[512];
	std::string result = "";
	std::shared_ptr<FILE> pipe2(_popen(inp.c_str(), "r"), _pclose);
	while (!feof(pipe2.get())) {
		if (fgets(buffer, 128, pipe2.get()) != NULL)
		{
			result += buffer;
		}

		json responseData;
		responseData["isClosed"] = true;
		responseData["message"] = "FreeCAD closed.";
		LOG(INFO) << "FreeCAD closed. ";
		return responseData;
	}
}

messagedata FreeCadModule::run()
{
	LOG(INFO) << "Executing run FreeCAD. ";

	// create geometry dir if does not exist
	try
	{
		misc::createFolder(wDir + "\\geometry");
		misc::createFolder(wDir + "\\vtk");
	}
	catch (std::string msg)
	{
		LOG(INFO) << msg;
		return messagedata(messagedata::error, messagedata::response, msg + " Please check read/write access to working folder! ", guid);
	}

	char const* up = getenv("USERPROFILE");
	std::string userProfile(up);
	std::string userCfg = userProfile + "\\.cenos-ANTENNAS\\config-freecad.cfg";

	char const* tmp = getenv("CENOS_CONFIG");
	std::string cenos_config(tmp);
	std::string freecad_addon = cenos_config + "\\freecad";
	std::string userConfigSource = freecad_addon + "\\user_source.cfg";

	// Reset user config if user has deleted original config.
	std::error_code ec;
	if (std::filesystem::exists(userCfg, ec) == false) {
		std::ifstream  src(userConfigSource, std::ios::binary);
		std::ofstream  dst(userCfg, std::ios::binary);
		dst << src.rdbuf();
	}

	/*
	 * FreecCAD user configs will have a version number starting from the release version (V1)
	 * If there is no version number in the existing config,
	 * replace it with the base config (userConfigSource)
	 * Otherwire upgrade the config to the latest version (versionNumber+1).
	 */


	std::ifstream ifs(userCfg);
	std::string word = "CenosConfigVersion";
	std::string line;
	bool hasVersion = false;

	while(getline(ifs, line) && hasVersion == false)
	{
		size_t pos = line.find(word);
		if (pos != std::string::npos) {
			hasVersion = true;
			LOG(INFO) << "FreeCAD config contains CenosConfigVersion: " << hasVersion;
		}
	}

	if (!hasVersion) {
		std::ifstream  src(userConfigSource, std::ios::binary);
		std::ofstream  dst(userCfg, std::ios::binary);
		dst << src.rdbuf();
	}

	auto configDir = misc::getCenosConfigDir();
	std::string startPy = configDir.value() + "/freecad/freecad/cenos_addon/start.py";
	messagedata msg = executeFreeCad(startPy, false);
	return 	msg;
}



messagedata FreeCadModule::executeFreeCad(std::string script, bool batchMode) {
	// the script argument specifys what to exectute upon startup
	// For launching it in regular UI mode use main.py
	// For laynching in batch mode for parameter update, use 

	LOG(INFO) << "Executing FreeCAD. ";

	char const* up = getenv("USERPROFILE");
	std::string userProfile(up);
	std::string userCfg = userProfile + "\\.cenos-ANTENNAS\\config-freecad.cfg";

	char const* tmp = getenv("CENOS_CONFIG");
	std::string cenos_config(tmp);

	std::string freecad_addon = cenos_config + "\\freecad";

	auto t = std::time(nullptr);
	auto tm = *std::localtime(&t);
	std::stringstream time_buffer;
	time_buffer << std::put_time(&tm, "%Y-%m-%d");

	//char const* tmp2 = getenv("TEMP");
	std::string cenos_temp(getenv("TEMP"));
	deleteFemTempFiles(cenos_temp);

	std::vector<std::string> escaspedCadfiles;
	for (int i = 0; i < cadfiles.size(); i++) {
		escaspedCadfiles.push_back("_" + cadfiles[i] + "_");
	}

	std::vector<std::string> cmdList;

	if (batchMode) {
		cmdList.push_back("FreeCADcmd");
	}
	else {
		cmdList.push_back("FreeCAD");
	}
	cmdList.push_back("-M \"" + freecad_addon + "\"");
	cmdList.push_back("-u \"" + userCfg + "\" ");
	cmdList.push_back("--log-file \"" + cenos_temp + "\\cenos\\" + time_buffer.str() + ".cenos-freecad.log\" ");
	cmdList.push_back("\""+ script + "\"");
	// Arguments for start.py
	cmdList.push_back(guid);
	cmdList.push_back("\"_" + wDir + "_\""); // Underscore is so that FreeCAD would not try to interpret argument as file
	cmdList.push_back("\"" + boost::algorithm::join(escaspedCadfiles, "|") + "\"");

	std::string cmd = boost::algorithm::join(cmdList, " ");

	LOG(INFO) << "Executing FreeCAD with " << cmd.c_str();
	json responseData = openPipe(cmd);
	return 	messagedata(messagedata::success, messagedata::response, responseData.dump(), guid);
}

messagedata FreeCadModule::update()
{
	std::string scriptPath = createUpdateScript();
	return executeFreeCad(scriptPath, true);
}


std::string FreeCadModule::createUpdateScript(){
	std::string scriptPath = wDir + "/runFreeCad.py";
	
	auto configDir = misc::getCenosConfigDir();
	std::ofstream pyfile;
	pyfile.open(scriptPath);
	std::string outString = "try:\n";
	outString.append("    import sys, os, FreeCAD, traceback \n");
	outString.append("    os.environ[\"SESSION_GUID\"] = \"" + guid + "\"\n");
	outString.append("    os.environ[\"W_DIR\"] = r\"" + wDir + "\"\n");
	outString.append("    sys.path.append(r\"" + configDir.value() + "\\freecad\\freecad\") \n");
	outString.append("    from cenos_addon import cenos_main\n");
	outString.append("    cenos_main.openFCStudy(r\""+ wDir +"\\geometry\\FC_Study.FCStd\")\n");
	outString.append("    cenos_main.init_observers()\n");
	std::string variables = geometryData.value()["variables"].dump(4);
	outString.append("    variables = " + variables + "\n");
	outString.append("    cenos_main.update_parameters(variables)\n");
	outString.append("except:\n");
	outString.append("    FreeCAD.Console.PrintError(traceback.format_exc())\n");

	pyfile << outString;
	pyfile.close();
	return scriptPath;
}

std::string getLogCmd(std::string cenosTemp) {
	auto t = std::time(nullptr);
	auto tm = *std::localtime(&t);
	std::stringstream timeBuffer;
	timeBuffer << std::put_time(&tm, "%Y-%m-%d");
	std::string logCmd = "--log-file \"" + cenosTemp + "\\cenos\\" + timeBuffer.str() + ".cenos-freecad.log\" ";
	return logCmd;
}

void FreeCadModule::deleteFemTempFiles(std::string tempPath) {
	// FreeCAD FEM will leave temporary files if it crashes
	// These can add up to multiple GB, so they are deleted
	std::vector<std::filesystem::path> pathsToDelete;
	for (auto& dir : std::filesystem::directory_iterator(tempPath)) {
		std::string dirName = dir.path().filename().u8string();
		if (dirName._Starts_with("fcfem_")) {
			LOG(INFO) << "Deleting leftover FEM temp folder: " << dirName;
			pathsToDelete.push_back(dir.path());
		}
	}
	std::error_code ec;
	for (auto& dir : pathsToDelete) {
		std::filesystem::remove_all(dir, ec);
	}
}
