/*
 * setup.cpp
 *
 *  Created on: Nov 23, 2017
 *      Author: vadims
 */
#include "setup.hpp"
#include "misc/Timer.hpp"
#include "cenos_exception.h"

Setup::Setup()
{
	hasMultipleCalculations_ = false;
	no_results = false;
	stopped_during_calculation = false;
};

Setup::~Setup()
{
};


void Setup::setWDir(std::string workingdir)
{
	wDir = workingdir;
}

void Setup::setGuid(std::string guid_)
{
	guid = guid_;
}

void Setup::setMessageQueue(MessageQueue_ m)
{
	messageQueue = m;
}

void Setup::setOutputAnalyzer(OutputAnalyzer_ o)
{
	outputAnalyzer = o;
}

void Setup::setStopper(Stopper s)
{
	stopper = s;
}


void Setup::setMesh(std::shared_ptr<Mesh> mesh_)
{
	this->mesh = mesh_;

	//update mesh ids to geometry_data ids
	for (auto ent : mesh->getEntities())
	{
		try
		{
			auto gd_ent = geom_data->getEntityByName(ent->getName());
			ent->setShape(gd_ent->getShape());
			ent->setId(gd_ent->getId());
		}
		catch (std::exception& e)
		{
			throw(cenos_exception("Mesh inconsistend with geometry data. Try sending mesh again from Salome or contact support!"));
		}
	}
}


void Setup::setGeometryData(std::shared_ptr<GeometryData> gd)
{
	this->geom_data = gd;
}

void Setup::transformMesh(std::shared_ptr<Mesh> newmesh)
{
	newmesh->setMeshScale(mesh->getMeshScale());

	std::map<std::string, std::string> old_to_new_names;
	for (auto dom : geom_data->getDomains())
	{
		std::shared_ptr<MeshEntity> e0 = std::make_shared<MeshEntity>(dom->getShape(), dom->getName(), dom->getId());
		//TO DO 2D case!!
		newmesh->addEntity(e0);

		for (auto sub_ent : dom->getEntities())
			old_to_new_names[sub_ent->getName()] = dom->getName();
	}

	for (auto bnd : geom_data->getBoundaries())
	{
		std::shared_ptr<MeshEntity> e0 = std::make_shared<MeshEntity>(bnd->getShape(), bnd->getName(), bnd->getId());
		//TO DO 2D case!!
		newmesh->addEntity(e0);

		for (auto sub_ent : bnd->getEntities())
			old_to_new_names[sub_ent->getName()] = bnd->getName();
	}

	for (auto node : mesh->getNodes())
	{
		std::shared_ptr<Node> n0 = std::make_shared<Node>(node->getId(), node->x(), node->y(), node->z());
		newmesh->addNode(n0);
	}


	for (auto element : mesh->getElements())
	{
		if (element->getDimension() == mesh->getDimension() - 2)
			continue;
		std::shared_ptr<Element> e0 = Element::createElement(static_cast<ElementTypes>(element->getElementType()));

		std::vector<std::shared_ptr<Node>> node_list;
		for (auto nd: element->getNodes())
		{
			node_list.push_back(newmesh->getNode(nd->getId()));
		}

		e0->setNodes(node_list);

		e0->setId(element->getId());

		std::shared_ptr<MeshEntity> ent0;

		ent0 = newmesh->getEntityByName(old_to_new_names[element->getOwnerEntity()->getName()]);


		if (ent0 == nullptr)
		{
				std::string message = "Mesh reading error: entity is nullptr in transformMesh()";
				LOG(ERROR) << element->getOwnerEntity()->getId();
				throw cenos_exception(message);
		}

		e0->setEntity(ent0);

		newmesh->addElement(e0);
	}

	mesh = newmesh;
}

void Setup::readInput(json input)
{

	// reads input for physics, also in case of multiphysics.
	json physicsListJson;
	if (input.find("physicsParams") != input.end())
	{
		physicsListJson = input["physicsParams"]["physicsList"];
		mesh->setMeshScale(input.at("physicsParams").at("meshUnitFactor").get<float>());
		caseNumerics.readInput(input["physicsParams"]["computation"]);
	}
	else
	{
		physicsListJson = input["physicsList"];
		mesh->setMeshScale(input.at("meshUnitFactor").get<float>());
		caseNumerics.readInput(input["computation"]);
	}
	
	if (input.find("physicsParams") != input.end())
	{
		if (input["physicsParams"].find("computation") != input["physicsParams"].end())
			if (input["physicsParams"]["computation"].find("solverScript") != input["physicsParams"]["computation"].end())
				solver_script = input["physicsParams"].at("computation").at("solverScript").get<std::string>();
	}
	else
	{
		if (input.find("computation") != input.end())
			if (input["computation"].find("solverScript") != input["computation"].end())
				solver_script = input.at("computation").at("solverScript").get<std::string>();
	}


	if (input.find("variables") != input.end())
	{
		json variablesJson = input.at("variables").get<json>();

		for (json::iterator it = variablesJson.begin(); it != variablesJson.end(); ++it)
		{
			json var = *it;
			Parameter par(var["name"].get<std::string>(), var["initialValue"].get<float>());
			// velocity can be present, if scanning is used, but can be absent if simple parametric geometry used
			if (var.find("velocity") !=var.end())
				par.setVelocity(var["velocity"].get<float>());
			par.setEnabled(var["enabled"].get<bool>());
			parameterList.push_back(par);
		}
	}


	for (json::iterator it = physicsListJson.begin(); it != physicsListJson.end(); ++it) 
	{
		Physics ph;
		ph.readInput(it.value());
		ph.validateInput();
		casePhysics.push_back(ph);
	}

	//update physics geometry
	for (auto phys : casePhysics)
	{
		for (auto& dom : phys.getDomainsList())
		{
			auto gd_dom = geom_data->getDomainByName(dom->getName());
			for (auto ent : gd_dom->getEntities())
				dom->addEntity(ent);
			dom->update();
			dom->setLabel(gd_dom->getLabel());
		}

		for (auto& bnd : phys.getBoundariesList())
		{
			auto gd_bnd = geom_data->getBoundaryByName(bnd->getName());
			for (auto ent : gd_bnd->getEntities())
				bnd->addEntity(ent);
			bnd->update();
			bnd->setLabel(gd_bnd->getLabel());
		}
	}


	if (input.find("motionList") != input.end())
	{
		json motionListJson = input["motionList"];

		for (json::iterator it = motionListJson.begin(); it != motionListJson.end(); ++it)
		{
			Motion mt(it.value());
			mt.setScale(mesh->getMeshScale());
			caseMotions.push_back(mt);
		}
	}

	validateInput();
}

void Setup::readPost(json input)
{
	postProcConfig = input;
}

json Setup::getPhysicsConfig()
{
	//read physycs.json from config
	char const* tmp = getenv("CENOS_CONFIG");
	if (!tmp) {
		throw cenos_exception("variable CENOS_CONFIG not set ");
	}

	std::string cenos_env(tmp);
	std::string str;
	std::ifstream inFile;
	inFile.open(cenos_env + "\\jsons\\_generated\\physics.json");
	if (inFile.is_open()) {
		std::stringstream strStream;
		strStream << inFile.rdbuf();
		str = strStream.str();
	}
	else {
		throw cenos_exception("No solver config file found. ");
	}
	inFile.close();

	json configJson;
	std::istringstream iss(str);
	iss >> configJson;
	return configJson;
}

json Setup::getSubPhysicsConfig(json configJson, std::string physicsKey, std::string subPhysicsKey, std::string basisKey, std::string multiSubPhysicsKey)
{

	json physicsJson = configJson.at("physics").get<json>();
	for (json::iterator it = physicsJson.begin(); it != physicsJson.end(); ++it)
	{
		std::string k = it.value().at("key").get<std::string>();
		if (it.value().at("key").get<std::string>() == physicsKey)
		{
			json subPhysicsJson = it.value().at("subPhysics").get<json>();
			for (json::iterator jt = subPhysicsJson.begin(); jt != subPhysicsJson.end(); ++jt)
			{
				if (jt.value().at("key").get<std::string>() == subPhysicsKey)
				{
					json basisJson = jt.value().at("basisList").get<json>();
					for (json::iterator kt = basisJson.begin(); kt != basisJson.end(); ++kt)
					{
						if (kt.value().at("basis").at("key").get<std::string>() == basisKey)
						{
							json basisPhysics = kt.value();
							for (json::iterator mt = basisPhysics.at("physicsList").begin(); mt != basisPhysics.at("physicsList").end(); ++mt)
							{
								std::string k = mt.value().at("key").get<std::string>();
								if (mt.value().at("key").get<std::string>() == multiSubPhysicsKey)
								{
									return  mt.value().at("domainTypes").get<json>();
								}
							}
						}
					}
				}
			}
		}
	}
}

std::optional<json> Setup::getLastParams(std::string wDir)
{
	std::string str;
	std::ifstream inFile;
	inFile.open(wDir + "//last-params.json");
	if (inFile.is_open()) {
		std::stringstream strStream;
		strStream << inFile.rdbuf();
		str = strStream.str();
	}
	else {
		return {};
	}
	inFile.close();

	json lastParams;
	std::istringstream iss(str);
	iss >> lastParams;
	return lastParams;
}

bool Setup::hasMultipleCalculations()
{
	return hasMultipleCalculations_;
}

bool Setup::stoppedDuringCalculation()
{
	return stopped_during_calculation;
}

bool Setup::hasNoResults()
{
	return no_results;
}


std::map<std::string, int> Setup::getBoundariesIdMap()
{
	std::map<std::string, int> returnMap;
	for (auto& phys : casePhysics)
	{
		for (auto & boundary : phys.getBoundariesList())
		{ 
			if (returnMap.find(boundary->getName()) == returnMap.end())
				returnMap[boundary->getName()] = boundary->getId();
		}
	}
	return returnMap;
}

std::map<std::string, int> Setup::getDomainsIdMap()
{
	std::map<std::string, int> returnMap;
	for (auto& phys : casePhysics)
	{
		for (auto& domain : phys.getDomainsList())
		{
			if (returnMap.find(domain->getName()) == returnMap.end())
				returnMap[domain->getName()] = domain->getId();
		}
	}
	return returnMap;
}


Physics Setup::getPhysics(std::string slvKey)
{
	for (auto& phys : casePhysics)
	{
		if (phys.getSolverKey() == slvKey)
			return phys;
	}
	throw cenos_exception("From getPhysics(std::string): No physics found with passed solver key.");
}

std::vector<Physics> Setup::getCasePhysics()
{
	return casePhysics;
}

bool Setup::hasTransientPhysics()
{
	for (auto& phys : casePhysics)
		if (phys.getTimeType() == Physics::TRANSIENT)
			return true;

	return false;
}

std::string Setup::getWdir()
{
	return wDir;
}

Physics& Setup::getTransientPhysics()
{
	for (auto& phys : casePhysics)
		if (phys.getTimeType() == Physics::TRANSIENT)
			return phys;
}

json Setup::getSolverConfig(std::string solverKey, std::string physicsKey, std::string subPhysicsKey, std::string basisKey)
{
	//read physycs.json from config
	char const* tmp = getenv("CENOS_CONFIG");
	if (!tmp) {
		throw cenos_exception("variable CENOS_CONFIG not set");
	}

	std::string cenos_env(tmp);
	std::string str;
	std::ifstream inFile;
	inFile.open(cenos_env + "\\jsons\\_generated\\physics.json");
	if (inFile.is_open()) {
		std::stringstream strStream;
		strStream << inFile.rdbuf();
		str = strStream.str();
	}
	else {
		throw cenos_exception("No solver config file found. ");
	}
	inFile.close();

	json configJson = json::parse(str);

	json slvJson = configJson.at("solvers").get<json>();
	json physJson = slvJson.at(physicsKey).get<json>();
	json subPhysJson = physJson.at(subPhysicsKey).get<json>();
	json basisJson = subPhysJson.at(basisKey).get<json>();


//	json basisJson = configJson["solvers"][physicsKey][subPhysicsKey][basisKey].get<json>();

	//expects only one object inside of basis

	for (json::iterator jt = basisJson.begin(); jt != basisJson.end(); ++jt)
	{
		json basisValueJson = jt.value();
		if (basisValueJson.find("isMultiPhysics") != basisValueJson.end())
		{
			if (basisValueJson.at("isMultiPhysics").get<bool>())
			{
				json basisSubSolverJson = basisValueJson.at("subSolvers");
				for (json::iterator it = basisSubSolverJson.begin(); it != basisSubSolverJson.end(); ++it)
				{
					std::string k = it.value().at("key").get<std::string>();
					if (it.value().at("key").get<std::string>() == solverKey)
						return it.value();
				}
			}
			else
			{
				return basisValueJson;
			}
		}
		else
		{
			return basisValueJson;
		}
	}
}

/*
// this factory function will have to be changed when new modules will be added!
setup* setup::setupFF(std::string inp) {
	if (1) {
		return new getdpSetup(inp);
	}
	else {
		std::cout << "Undefined class" << std::endl;
	}
}


void setup::addNetworkDomain()
{
	for (auto& network : phys.networkList)
	{
		std::map<std::string, std::vector<domains> > networkDomains; //group; vector of domains
		for (auto& dom : domainsList) {
			for (auto& src : dom.getSources())
			{
				if (src.first == network.getName())
				{
					std::string grName;

					if (dom.getGroupKey() == "none")
					{
						grName = dom.getName();
						dom.setGroupKey(grName);
					}
					else
					{
						grName = dom.getGroupKey();
					}
					networkDomains[grName].push_back(dom);
				}
			}
		}
		int crc = 0;
		for (auto nd : networkDomains)
		{
			int domid = getMaxEntityId() + 1;

			std::string domname = network.getName() + "_source" + std::to_string(crc);

			domainsList.push_back(domains(domid, domname,
				network.getSolverKey(), nd.second[0].getSources(), nd.first, network.getTypeKey()));
			crc++;

			domainTypes dt0;
			dt0.setId(domid);
			dt0.setName(network.getTypeKey());
			phys.dTypes.push_back(dt0);
		}
	}

}


int setup::getMaxEntityId()
{
	int maxid = 0;
	for (auto dom : domainsList)
	{
		if (maxid <= dom.getId())
			maxid = dom.getId();
	}
	for (auto bc : bcsList)
	{
		if (maxid <= bc.getId())
			maxid = bc.getId();
	}
	return maxid;
}


std::vector<std::string> setup::getDomainTypeList()
{
	std::vector<std::string> domainTypeList;
	for (auto& i : domainsList) {
		if (find(domainTypeList.begin(), domainTypeList.end(), i.getTypeKey()) == domainTypeList.end()) {
			domainTypeList.push_back(i.getTypeKey());
		}
	}
	return domainTypeList;
}

std::vector<material> setup::getMaterials()
{
	return matList;
}

std::vector<bcs> setup::getBCs()
{
	return bcsList;
}

std::vector<domains> setup::getDomains()
{
	return domainsList;
}

Physics setup::getPhysics()
{
	return phys;
}


bool setup::isMaterialNL(int matId)
{
	for (auto& material : matList)
	{
		if (matId == material.getId())
		{
			if (material.isNL())
				return true;
		}
	}

	return false;
}

std::vector<domains> setup::getNonLinearDomains()
{
	std::vector<domains> returnVec;
	for (auto& dom : domainsList)
	{
		if (find(returnVec.begin(), returnVec.end(), dom) == returnVec.end())
		{
			if (isMaterialNL(dom.getId()))
				returnVec.push_back(dom);
		}
	}
	return returnVec;
}



domains setup::getDomainById(int inpId)
{
	for (auto& dom : domainsList)
	{
		if (dom.getId() == inpId)
		{
			return dom;
		}
	}
}

domains setup::getDomainById(int inpId, std::string slkey)
{
	for (auto& dom : domainsList)
	{
		if ((dom.getId() == inpId) and (dom.getSolverKey() == slkey))
		{
			return dom;
		}
	}
}

bcs setup::getBcsById_Type(int inpId, std::string tKey)
{
	for (auto& bc : bcsList)
	{
		if ((bc.getId() == inpId) && (bc.getTypeKey() == tKey))
		{
			return bc;
		}
	}
	return bcs();
}

*/