/**
 * Module.cpp
 *
 *  Created on: Dec 17, 2019
 *      Author: vadims
 */

#include "SalomeModule.hpp"
#include "SalomeModuleFns.hpp"
#include <boost/filesystem.hpp>
#include "misc/miscFunctions.hpp"
#include <filesystem>
#include "topology/geometryData.h"
#include "setup/physics.hpp"
#include "mesh/MeshParameters.h"
#include <wtsapi32.h>
#include "cenos_exception.h"

#pragma comment(lib, "Wtsapi32.lib")

SalomeModule::SalomeModule() : Module()
{
	stopRequired = false;
}

SalomeModule::~SalomeModule()
{
}

void SalomeModule::setWDir(std::string wDir_)
{
	wDir = wDir_;
}

void SalomeModule::setGuid(std::string guid_)
{
	guid = guid_;
}

void SalomeModule::setRunMode(SalomeModule::RunMode rm)
{
	run_mode = rm;
}

std::optional<std::string> SalomeModule::getSalomePath()
{
	std::shared_ptr<FILE> pipe(_popen("where run_salome.bat ", "r"), _pclose);

	if (!pipe) {
		return {};	// could not start pipe
	}

	char buffer[512];
	std::string result = "";

	while (!feof(pipe.get())) {
		if (fgets(buffer, 128, pipe.get()) != NULL) {
			result += buffer;
		}
	}

	std::size_t found = result.find("run_salome.bat");
	if (found == std::string::npos)
		return {}; // could not find run_salome.bat

	return result.substr(0, found);
}

bool SalomeModule::writeDumpScript(json params)
{

	LOG(INFO) << "writeDumpScript";

	auto configDir = misc::getCenosConfigDir();
	if (!configDir.has_value())
		return false;

	std::string dumpStr;
	std::ifstream dumpFile;
	std::stringstream dumpStrStream;
	dumpFile.open(wDir + "\\geometry\\dump.py");
	dumpStrStream << dumpFile.rdbuf();
	dumpStr = dumpStrStream.str();
	dumpFile.close();

	std::ifstream shaperDumpFile;
	std::stringstream shaperDumpStrStream;
	shaperDumpFile.open(wDir + "\\geometry\\shaperDump.py");
	shaperDumpStrStream << shaperDumpFile.rdbuf();

	LOG(INFO) << "mergeDumpFiles";

	dumpStr = mergeDumpFiles(dumpStr, shaperDumpStrStream.str());

	shaperDumpFile.close();
	std::string meshName = getMeshName(wDir + "\\geometry\\meshFile.msh");

	LOG(INFO) << "replaceNotepadValue";
	dumpStr = replaceNotepadValue(dumpStr, params, meshName);

	LOG(INFO) << "updateStepPaths";
	dumpStr = updateStepPaths(dumpStr, wDir);

	LOG(INFO) << "write runSalome.py";

	// Create loadScript.py in the working directory.
	std::ofstream pyfile;
	pyfile.open(wDir + "/runSalome.py");
	std::string outString = "import sys\n";
	outString.append("import os\n");
	outString.append("import json\n");
	outString.append("os.environ[\"SESSION_GUID\"] = \"" + guid + "\"\n");
	outString.append("sys.path.append(r\"" + configDir.value() + "\\salome\\plugin\") \n");
	outString.append("import GmeshBuild\n");
	outString.append("import  SMESH\n");
	outString.append("GMSH= GmeshBuild.GmeshBuild()\n");
	outString.append("jsondata= {}\n");
	outString.append("wDir = r\"" + wDir + "\" \n");
	outString.append("meshName = GMSH.MESH_FILE_NAME \n");

	outString.append("\n");

	// enclose dumpStr in try - except
	dumpStr = addPythonIndent(dumpStr, "    ");
	outString.append("try:\n");
	outString.append(dumpStr);
	outString.append("except:\n");
	outString.append("    jsondata['status'] = \"error\"\n");
	outString.append("    jsondata['message'] = \"Could not generate geometry with input parameters.\"\n");
	outString.append("    with open(wDir + \"//geometry//batch_response.json\", \"w\") as write_file :\n");
	outString.append("        json.dump(jsondata, write_file)\n");
	outString.append("    if not salome.sg.hasDesktop():\n");
	outString.append("        from killSalomeWithPort import killMyPort\n");
	outString.append("        killMyPort(os.getenv('NSPORT'))\n\n");
	outString.append("        quit()\n\n");

	if (run_mode == BATCH)
		outString.append("GMSH.WriteMesh(wDir, \"BATCH\"," + meshName + ",meshName)\n");
	else if (run_mode == BATCH_NO_RESPONSE)
		outString.append("GMSH.WriteMesh(wDir, \"BATCH_NO_RESPONSE\"," + meshName + ",meshName)\n");

	outString.append("if not salome.sg.hasDesktop():\n");
	outString.append("    from killSalomeWithPort import killMyPort\n");
	outString.append("    killMyPort(os.getenv('NSPORT'))\n");
	outString.append("    quit()\n\n");

	pyfile << outString;
	pyfile.close();
	return true;
}

void SalomeModule::stop()
{
	stopRequired = true;
}

bool SalomeModule::writeLoadCadScript(std::vector<std::string> cadfiles)
{
	// Read loadCad template.
	auto configDir = misc::getCenosConfigDir();
	if (!configDir.has_value())
		return false;

	std::string templateStr;
	std::ifstream templateFile;
	std::stringstream strStream;
	templateFile.open(configDir.value() + "\\salome\\loadCad.py");
	strStream << templateFile.rdbuf();
	templateStr = strStream.str();
	templateFile.close();

	// Create loadCad.py in the working directory.
	std::ofstream pyfile;
	pyfile.open(wDir + "/runSalome.py");
	std::string outString = "files = []\n";

	// Iterate filenames.
	for (auto cadFile : cadfiles)
	{
		std::replace(cadFile.begin(), cadFile.end(), '\\', '/');
		outString.append("files.append('" + cadFile + "')\n");
	}

	// Append template and close loadCad.py.
	outString.append("\n");
	outString.append("import sys, os\n");
	outString.append("os.environ[\"SESSION_GUID\"] = \"" + guid + "\"\n");
	outString.append(templateStr);
	pyfile << outString;
	pyfile.close();

}

void SalomeModule::writeSalomeOpeningScript(std::optional<std::string> hdfFile )
{
	std::ofstream pyfile;
	pyfile.open(wDir + "//runSalome.py");
	std::string outString = "import os, inspect, SalomePyQt \n";
	outString.append("import salome, sys\n");
	outString.append("sg = SalomePyQt.SalomePyQt()\n");
	outString.append("os.environ[\"SESSION_GUID\"] = \""+ guid +"\"\n");

	if (hdfFile.has_value())
	{
		outString.append("hdfFile = r\"" + hdfFile.value() + "\" \n");
		outString.append("salome.openStudy(hdfFile)\n");
	}

	outString.append("sg.activateModule(\"Shaper\")\n");
	pyfile << outString;
	pyfile.close();
}

void copySalomeFix(std::string salomePath) {
	// This copies some files from configFiles\salome\salome_fix to salome installation folder
	// More information about the fixes is in salome_fix\readme.txt
	auto configDir = misc::getCenosConfigDir();
	if (!configDir.has_value()) {
		LOG(ERROR) << "Could not get config dir";
		return;
	}
	std::error_code ec, ec2, ec3;
	std::filesystem::copy(
		configDir.value() + R"(\salome\salome_fix\launchConfigureParser.py)",
		salomePath + R"(MODULES\INSTALL\KERNEL\RELEASE\KERNEL_INSTALL\bin\salome\launchConfigureParser.py)",
		std::filesystem::copy_options::overwrite_existing,
		ec);	
	if (ec) {
		LOG(ERROR) << "Could not copy launchConfigureParser.py into Salome directory: " << ec.message();
	}
	std::filesystem::copy(
		configDir.value() + R"(\salome\salome_fix\envSalome.py)",
		salomePath + R"(MODULES\INSTALL\KERNEL\RELEASE\KERNEL_INSTALL\bin\salome\envSalome.py)",
		std::filesystem::copy_options::overwrite_existing,
		ec2);
	if (ec2) {
		LOG(ERROR) << "Could not copy envSalome.py into Salome directory: " << ec2.message();
	}
	std::filesystem::copy(
		configDir.value() + R"(\salome\salome_fix\salomeContextUtils.py)",
		salomePath + R"(MODULES\INSTALL\KERNEL\RELEASE\KERNEL_INSTALL\bin\salome\salomeContextUtils.py)",
		std::filesystem::copy_options::overwrite_existing,
		ec3);
	if (ec3) {
		LOG(ERROR) << "Could not copy salomeContextUtils.py into Salome directory: " << ec3.message();
	}
}

void SalomeModule::writeUpdateSalomeScript(json geometryData)
{
	json params = geometryData["variables"];
	writeDumpScript(params);
}

/// <summary>
/// generates salome launch script which loads existing hdf file, 
/// and sends gemetryData to Cenos
/// </summary>
/// <param name="groupNames"> names of groups (domain and boundary groups) in geometryData </param>
void SalomeModule::writeGenerateGeometryDataScript(std::vector<std::string> groupNames)
{

	std::ofstream pyfile;
	pyfile.open(wDir + "//runSalome.py");
	std::string outString = "import os, inspect, SalomePyQt \n";
	outString.append("import salome, sys\n");
	auto configDir = misc::getCenosConfigDir();

	if (!configDir.has_value())
		throw(cenos_exception("Could not find CENOS_CONFIG variable! Please contact support!"));

	outString.append("sys.path.append(r\"" + configDir.value() + "\\salome\\plugin\") \n");
	outString.append("import GmeshBuild\n");
	outString.append("import  SMESH\n");
	outString.append("GMSH= GmeshBuild.GmeshBuild()\n");
	outString.append("sg = SalomePyQt.SalomePyQt()\n");
	outString.append("geompy = salome.geom.geomBuilder.New()\n");
	outString.append("smesh = salome.smesh.smeshBuilder.New()\n");
	outString.append("os.environ[\"SESSION_GUID\"] = \"" + guid + "\"\n");

	outString.append("hdfFile = r\"" + wDir + "\\geometry\\salome_study.hdf\" \n");
	outString.append("wDir = r\"" + wDir + "\" \n");

	//geometryData groups to string
	std::string groups;
	int i = 0;
	for (auto gr : groupNames)
	{
		if (i > 0) groups.append(" ,");
		groups.append("\"" + gr + "\"");
		i++;
	}

	outString.append("groupsInGeomData = [ "+ groups + " ]\n\n");

	outString.append("salome.myStudy.Open(hdfFile)\n");

	// do not know why but without this line which just creates a dump file, 
	// the loaded case is empty. When afte opening it is dumped, suddenly everything is ok.
	outString.append("salome.myStudy.DumpStudy( wDir + \"//geometry//\", \"dummydump\", True, False)\n");

	std::string meshName = getMeshName(wDir + "\\geometry\\meshFile.msh");
	outString.append("sObj = salome.myStudy.FindObject(\"" + meshName + "\")\n");
	outString.append("obj = sObj.GetObject()\n");
	outString.append("mesh = smesh.Mesh(obj)\n\n");

	// *****************************
	// copy geometry groups to mesh
	// *****************************

	outString.append("geom = mesh.GetShape()\n");
	outString.append("allGeomGroupList = geompy.GetGroups(geom)\n");

	outString.append("selectedGroups = [x for x in allGeomGroupList if x.GetName() in groupsInGeomData]\n");
	outString.append("GMSH.updateGroups(selectedGroups, mesh)\n");


	outString.append("GMSH.WriteMesh(wDir, \"BATCH\", mesh, \"meshFile.msh\")\n");

	outString.append("if not salome.sg.hasDesktop():\n");
	outString.append("    from killSalomeWithPort import killMyPort\n");
	outString.append("    killMyPort(os.getenv('NSPORT'))\n");
	outString.append("    quit()\n\n");

	pyfile << outString;
	pyfile.close();
}

void SalomeModule::writeGenerateGroupsFromGeometryData(json geom_data_json, json phys_json)
{
	// Read loadCad template.
	auto configDir = misc::getCenosConfigDir();
	if (!configDir.has_value())
		throw(cenos_exception("Could not find CENOS_CONFIG variable! Please contact support!"));

	std::string templateStr;
	std::ifstream templateFile;
	std::stringstream strStream;
	templateFile.open(configDir.value() + "\\salome\\generateCaseFromGeometryData.py");
	strStream << templateFile.rdbuf();
	templateStr = strStream.str();
	templateFile.close();

	GeometryData gd(geom_data_json, wDir);

	// *************************
	// Calculate viscous layers
	std::map<std::string, SalomeVLParams> vlayers;
	for (auto ph : phys_json["physicsList"])
	{
		Physics phys;
		phys.readInput(ph);
		phys.validateInput(); // throws exception if something is not ok

		if (phys.getSolverKey() == "getdpEMGeneral2D" or phys.getSolverKey() == "getdpEMGeneral3D")
		{
			float frequency_min = phys.getTimeParameters()["frequency"].getMinValue();
			float frequency_max = phys.getTimeParameters()["frequency"].getMaxValue();

			for (auto dom : phys.getDomainsList())
			{
				if (dom->getTypeKey() == "em_cc" or dom->getTypeKey() == "source_dom")
				{
					auto mat = phys.getMaterialById(dom->getMaterialId());
					auto skin_layers = mat.getSkinLayerThicknessRange(frequency_min, frequency_max);
					double skin_min = skin_layers.first / lengthToSI(gd.getLengthUnit());
					double skin_max = skin_layers.second / lengthToSI(gd.getLengthUnit());
					vlayers[dom->getName()] = MeshParameters::getSalomeVL(skin_min, skin_max);
					auto bnds = gd.getDomainByName(dom->getName())->getBoundaries();
					for (auto bnd : bnds)
					{
						if (bnd->getRole() == "terminal" or bnd->getRole() == "terminal2")
						{
							for (auto ent : bnd->getEntities())
								vlayers[dom->getName()].no_layers_on.push_back(ent->getName());
						}
					}
				}
			}

		}
	}
	// End viscous layers
	// *************************


	// Create runSalome.py in the working directory.
	std::ofstream pyfile;
	pyfile.open(wDir + "/runSalome.py");
	// domain_entities and boundary_entities contain map {shape name : shape file (BREP))
	int dimension = gd.getDimension();

	std::string domain_entities = "domain_entities = {";
	std::string boundary_entities = "boundary_entities = {";
	for (auto ent : gd.getEntities())
	{
		if (ent->getDimension() == dimension)
		{
			std::string name = ent->getName();
			domain_entities.append(" \"" + name + "\": r\"" + wDir + "/geometry/raw/" + name + ".brep\",");
		}

		if (ent->getDimension() == dimension - 1)
		{
			std::string name = ent->getName();
			boundary_entities.append(" \"" + name + "\": r\"" + wDir + "/geometry/raw/" + name + ".brep\",");
		}


	}
	domain_entities.pop_back();
	domain_entities.append("}\n");
	boundary_entities.pop_back();
	boundary_entities.append("}\n");


	std::string outString = "";
	outString.append(domain_entities);
	outString.append(boundary_entities);

	// domain_groups and boundary_groups contain map {group name : list of shape names assigned to group)
	std::string domain_groups = "domain_groups = {";
	std::string boundary_groups = "boundary_groups = {";
	std::string group_roles = "group_roles = {";

	for (auto dom : gd.getDomains())
	{
		domain_groups.append("\"" + dom->getName() + "\" : [");
		for (auto ent : dom->getEntities())
		{
			domain_groups.append("\"" + ent->getName() + "\",");
		}
		group_roles.append("\"" + dom->getName() + "\" : \"" + dom->getRole() + "\",");
		domain_groups.pop_back();
		domain_groups.append("],");
	}
	domain_groups.pop_back();
	domain_groups.append("}\n");

	for (auto bnd : gd.getBoundaries())
	{
		if (bnd->getRole() == "")
			continue;
		boundary_groups.append("\"" + bnd->getName() + "\" : [");
		for (auto ent : bnd->getEntities())
		{
			boundary_groups.append("\"" + ent->getName() + "\",");
		}
		group_roles.append("\"" + bnd->getName() + "\" : \"" + bnd->getRole() + "\",");

		boundary_groups.pop_back();
		boundary_groups.append("],");
	}
	boundary_groups.pop_back();
	boundary_groups.append("}\n");

	group_roles.pop_back();
	group_roles.append("}\n");
	outString.append(domain_groups);
	outString.append(boundary_groups);
	outString.append(group_roles);
	

	// write boundary layer parameters
	std::string vl_params = "vl_params = {";

	bool has_layers = false;
	for (auto vl : vlayers)
	{
		vl_params.append("\"" + vl.first + "\" : [" + std::to_string(vl.second.total_thickness) + ", " +
			std::to_string(vl.second.nr_of_layers) + ", " + 
			std::to_string(vl.second.expansion_factor) + ", [");
		
		bool ignored_bnds = false;
		for (auto f : vl.second.no_layers_on)
		{
			vl_params.append("\"" + f + "\",");
			ignored_bnds = true;
		}
		
		if (ignored_bnds)
			vl_params.pop_back();
		vl_params.append("]],");
		has_layers = true;
	}
	vl_params.pop_back();
	vl_params.append("}\n");
	outString.append(vl_params);


	// Append template and close loadCad.py.
	outString.append("\n");
	outString.append("import sys, os\n");
	outString.append("os.environ[\"SESSION_GUID\"] = \"" + guid + "\"\n");
	outString.append(templateStr);
	pyfile << outString;
	pyfile.close();

}

#include <atlstr.h>	 // for	CP-1511
bool SalomeModule::isSalomeRunning()
{
	WTS_PROCESS_INFO* pWPIs = NULL;
	DWORD dwProcCount = 0;
	if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, NULL, 1, &pWPIs, &dwProcCount))
	{
		//Go through all processes retrieved
		for (DWORD i = 0; i < dwProcCount; i++)
		{
			// CP-1511 fix
			//std::string str = std::string(pWPIs[i].pProcessName);		//original
			std::string str = CW2A(pWPIs[i].pProcessName);	 //fixed

			std::size_t found = str.find("SALOME_");

			if (found != std::string::npos)
				return true;

			//pWPIs[i].pProcessName = process file name only, no path!
			//pWPIs[i].ProcessId = process ID
			//pWPIs[i].SessionId = session ID, if you need to limit it to the logged in user processes
			//pWPIs[i].pUserSid = user SID that started the process
		}
	}

	//Free memory
	if (pWPIs)
	{
		WTSFreeMemory(pWPIs);
		pWPIs = NULL;
	}
	return false;
}


messagedata SalomeModule::runSalome()
{
	LOG(INFO) << "Executing runSalome. ";

	char buf[256];
	GetCurrentDirectoryA(256, buf);
	std::string currentDir(std::string(buf) + '\\');


	auto salomePath = getSalomePath();
	if (!salomePath )
	{
		LOG(INFO) << "Could not find Salome executable. " ;
		return messagedata(messagedata::error, messagedata::response, " Could not find Salome executable. Check CENOS configuration! ", guid);
	}

	copySalomeFix(salomePath.value());

	// 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);
	}

	std::string inp;
	if (run_mode == BATCH or run_mode == BATCH_NO_RESPONSE)
		inp = "cmd /c run_salome.bat --terminal --module=SHAPER,GEOM,SMESH \"" + wDir + "/runSalome.py\"";
	else
		inp = "cmd /c run_salome.bat --module=SHAPER,GEOM,SMESH \"" + wDir + "/runSalome.py\"";

	//CP-1133 fix.
	//If there are no running salome processes - we execute run_salome_cenos.bat; else -> run_salome.bat

	if (!isSalomeRunning())
	{
		try 
		{
			if (std::filesystem::remove(std::string(getenv("TEMP")) + "\\.salome_PortManager.cfg"))
				LOG(INFO) << "Salome Port Cleanup Success.\n";
			else
				LOG(INFO) << "Salome Port Config Not Found.\n";
		}
		catch (std::string msg) 
		{
			LOG(INFO) << msg;
			return messagedata(messagedata::error, messagedata::response, msg + "<= Salome Config Cleanup Exception ! ", guid);
		}
	}

	LOG(INFO) << "Starting Salome with " + inp;

	chdir(salomePath.value().c_str());

	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;
			if ( (run_mode == BATCH) and (stopRequired))
			{
				_pclose(pipe2.get());
				return messagedata(messagedata::error, messagedata::response, "Stopped by user", guid);
			}
		}
	}

	chdir(currentDir.c_str());

	if (run_mode == BATCH or run_mode == BATCH_NO_RESPONSE)
	{
		std::ifstream in_file(wDir + "/geometry/batch_response.json");
		if (!in_file.is_open())
		{
			return messagedata(messagedata::error, messagedata::response, "Error during reading salome response file. ", guid);
		}

		json j = json::parse(in_file);
		messagedata msg(j);
		if (msg.msgStatus == messagedata::error)
		{
			msg.guid = guid;
			return msg;
		}

		// for batch modes return log message to prevent response to guid
		return 	messagedata(messagedata::info_lvl, messagedata::log, "Salome update finished.");
	}

	json responseData;
	responseData["isClosed"] = true;
	responseData["message"] = "Salome closed.";
	LOG(INFO)  << "Salome closed. ";
	return 	messagedata(messagedata::success, messagedata::response, responseData.dump(), guid);
}