/*
 * Physics.cpp
 *
 *  Created on: May 9, 2017
 *      Author: vadims
 */

#include "Physics.hpp"
#include "misc/Timer.hpp"
#include "cenos_exception.h"

std::map<std::string, InputValue> Physics::constants;

void Physics::readInput(json inp)
{

	std::string solverKey = inp.at("solver").get<std::string>();
	json jparams = inp.at("params").get<json>();

	// Read boundary conditions
	if (jparams.find("BCs") != jparams.end()) {
		json bc_json = jparams.at("BCs").get<json>();

		for (json::iterator it = bc_json.begin(); it != bc_json.end(); ++it) {
			boundariesList.push_back(std::make_shared<Bcs>(it.key(), it.value(), solverKey));
		}
	}

	// Read domains
	if (jparams.find("Domains") != jparams.end()) {
		json domains_json = jparams.at("Domains").get<json>();

		for (json::iterator it = domains_json.begin(); it != domains_json.end(); ++it) {

			domainsList.push_back(std::make_shared<Domains>(it.key(), it.value(), solverKey));
		}
	}

	// Read materials
	if (jparams.find("Materials") != jparams.end()) {
		json mat_json = jparams.at("Materials").get<json>();

		for (json::iterator it = mat_json.begin(); it != mat_json.end(); ++it) {
			matList.push_back(Material(it.key(), it.value()));
		}
	}

	// Read Physics
	if (jparams.find("Physics") != jparams.end()) {
		std::map<std::string, InputValue> timePar;
		std::map<std::string, InputValue> solverPar;

		if (inp.find("physics") != inp.end())
			physicsKey = inp.at("physics").get<std::string>();

		if (inp.find("subPhysics") != inp.end())
			subPhysicsKey = inp.at("subPhysics").get<std::string>();

		if (inp.find("basis") != inp.end())
			basisKey = inp.at("basis").get<std::string>();

		if (inp.find("solver") != inp.end())
			this->solverKey = inp.at("solver").get<std::string>();

		if (inp.find("multiSubPhysics") != inp.end())
			multiSubPhysicsKey = inp.at("multiSubPhysics").get<std::string>();


		// read probes for testing
		// TODO implement probes
		if (inp.find("probes") != inp.end())
		{
		/*	for (json::iterator jt = j_complete["probes"].begin(); jt != j_complete["probes"].end(); ++jt)
			{
				resultProbe probe;
				probe.readJson(jt.value());
				phys.probeList.push_back(probe);
			}*/
		}

		//read solver parameters
		if (inp.find("solverParams") != inp.end())
		{
			for (json::iterator jt = inp["solverParams"].begin(); jt != inp["solverParams"].end(); ++jt)
			{
				InputValue val;
				val.readJson(jt.value());
				solverParameters[jt.key()] = val;
			}
			InputValue val;
			val.readJson(inp["solver"]);
			solverParameters["solver"] = val;
		}

		//time type and symmetry type
		json phys_json = jparams.at("Physics").get<json>();
		if (phys_json.find("timeTypeId") != phys_json.end())
			timeTypeId = static_cast<Physics::timeType> (phys_json.at("timeTypeId").get<int>());

		if (phys_json.find("symmetryTypeId") != phys_json.end())
		{
			symmetryTypeId = static_cast<Physics::symmetryType> (phys_json.at("symmetryTypeId").get<int>());
		}
		else symmetryTypeId = static_cast < Physics::symmetryType>(1);

		if (phys_json.find("symmetryProperties") != phys_json.end())
		{
			json symmProps = phys_json.at("symmetryProperties").get<json>();
			if (symmProps.find("depthZ") != symmProps.end())
			{
				depthZ = symmProps.at("depthZ").get<float>();
			}
			else
			{
				depthZ = 1.0;
			}

			if (symmProps.find("symmetryFactor") != symmProps.end())
			{
				symmFactor = 360 / symmProps.at("symmetryFactor").get<float>();
			}
			else
			{
				symmFactor = 1;
			}

		}
		else
		{
			depthZ = 1.0;
			symmFactor = 1;
		}

		//time params
		if (phys_json.find("timeParameters") != phys_json.end())
		{
			for (json::iterator jt = phys_json["timeParameters"].begin(); jt != phys_json["timeParameters"].end(); ++jt)
			{
				InputValue val;
				val.readJson(jt.value());
				timeParameters[jt.key()] = val;
			}
			for (json::iterator jt = inp["computation"].begin(); jt != inp["computation"].end(); ++jt)
			{
				InputValue val;
				val.readJson(jt.value());
				timeParameters[jt.key()] = val;
			}
			InputValue val;
			val.readJson(inp["solver"]);
			timeParameters["solver"] = val;
		}
	}

	//establish connections

	for (auto& dom : domainsList)
	{
		for (auto& mat : matList)
		{
			if (dom->getMaterialId() == mat.getId())
			{
				dom->setMaterial(mat);
			}
		}
	}
}


void Physics::readPhysicsConfig(json physicsConfigJson)
{
	for (json::iterator jt = physicsConfigJson.begin(); jt != physicsConfigJson.end(); ++jt)
	{
		json dt_data = jt.value();
		// add configured domain types
		Domains::addDomainTypes(dt_data);

		// add configured boundary types
		if (dt_data.find("boundaryTypes") != dt_data.end()) 
		{
			Bcs::addBoundaryTypes(dt_data["boundaryTypes"]);
		}
	}
}

void Physics::readConstants(json physicsConfigJson)
{
	if (physicsConfigJson.find("physicalConstants") != physicsConfigJson.end()) {
		json constantJson = physicsConfigJson.at("physicalConstants").get<json>();
		for (json::iterator it = constantJson.begin(); it != constantJson.end(); ++it) {
			json constantData = it.value();
			if (constantData.find("default") != constantData.end())
			{
				InputValue iv = InputValue();
				iv.readJson(constantData.at("default").get<json>());
				constants[constantData.at("key").get<std::string>()] = iv;
			}
		}
	}
}


void Physics::readPostValues(json j_complete, json j_postprocessing)
{
	if (j_complete.find("postValues") != j_complete.end())
	{

		for (json::iterator jt = j_complete.at("postValues").begin(); jt != j_complete.at("postValues").end(); ++jt)
		{
			json postVal = jt.value();
			postValueList.push_back(std::make_shared<PostValues>(jt.key(), postVal));
			postValueList.back()->setPhysicsKey(physicsKey);
			postValueList.back()->setSolverKey(solverKey);
		}
	}

	json postValJson;
	if (j_postprocessing.find("postValues") != j_postprocessing.end())
		postValJson = j_postprocessing["postValues"];
	else
		postValJson = j_postprocessing;

	for (auto& pst : postValueList)
	{
		if (postValJson.find(pst->getStringId()) != postValJson.end())
		{
			json postObj = postValJson[pst->getStringId()];
			if (!postObj["enabled"])
			{
				pst->setEnabled(false);
			}
			else
			{
				pst->setEnabled(true);
			}
		}
	}

}



std::string Physics::getSolverKey()
{
	return solverKey;
}
bool Physics::getMultiInput()
{
	return multiInput;
}
std::string Physics::getLastPort()
{
	size_t count = 0;
	for (int i = 0; i < boundariesList.size(); i++)
	{
		if ((boundariesList[i]->getTypeKey()).find("Sur_UNIFPORT_N") != std::string::npos)
			count++;

	}
	
	return std::to_string(count);
}

std::string Physics::getPhysicsKey()
{
	return physicsKey;
}

std::string Physics::getSubPhysicsKey()
{
	return subPhysicsKey;
}

std::string Physics::getMultiSubPhysicsKey()
{
	return multiSubPhysicsKey;
}

std::string Physics::getBasisKey()
{
	return basisKey;
}

std::vector<std::shared_ptr<PostValues>> Physics::getPostValues()
{
	return postValueList;
}

std::vector<std::shared_ptr<PostValues>>  Physics::getEnabledPostValueList()
{
	std::vector<std::shared_ptr<PostValues>>  returnVec;
	for (auto pv : postValueList)
	{
		if (pv->isEnabled())
			returnVec.push_back(pv);
	}

	return returnVec;
}

std::vector<std::string> Physics::getGroupNamesOfBoundaryType(std::string bcTypeName)
{
	std::vector<std::string> retVec;
	for (auto& bt : Bcs::existingBoundaryTypes)
	{
		if (bt.key == bcTypeName)
		{
			retVec.push_back(bt.boundaryGroup);
		}
	}
	return retVec;
}

float Physics::getGeometricFactor()
{
	if (symmetryTypeId == AXISYMMETRIC_2D)
		return  2 * 3.14;
	else if (symmetryTypeId == PLANAR_2D)
		return depthZ;
	else  // for 3D
		return 1;
}


float  Physics::getSymmFactor()
{
	return symmFactor;
}



Physics::symmetryType Physics::getSymmetryType()
{
	return symmetryTypeId;
}

Physics::timeType Physics::getTimeType()
{
	return timeTypeId;
}

Material Physics::getMaterialById(unsigned int matId)
{
	for (auto& mat : matList)
	{
		if (mat.getId() == matId)
			return mat;
	}
	return Material();
}

std::vector<Bcs_> Physics::getBoundariesList()
{
	return boundariesList;
}

std::vector<Domains_> Physics::getDomainsList()
{
	return domainsList;
}

std::vector<Material> Physics::getMaterialsList()
{
	return matList;
}

Bcs_ Physics::getBoundaryByName(std::string n)
{
	for (int i = 0; i < boundariesList.size(); i++)
	{
		if (boundariesList[i]->getName() == n)
			return boundariesList[i];

	}
	std::string message = "Error in physics data: Could not find boundary " + n + ". Check your physics input!";
	throw cenos_exception(message);
}


std::map<std::string, InputValue> Physics::getConstants()
{
	return constants;
}

std::vector<Flag> Physics::getFlags()
{
	return flags;
}

std::map<std::string, InputValue> Physics::getTimeParameters()
{
	return timeParameters;
}

std::map<std::string, InputValue> Physics::getSolverParameters()
{
	return solverParameters;
}

void Physics::setTimeParameter(std::string key, float value)
{
	timeParameters[key] = value;
}

bool Physics::hasNonlinearProps()
{
	for (auto& fl : flags)
	{
		if (fl.getStringValue() == "1")
			return true;
	}
	return false;
}


void Physics::addDomain(Domains_ dom)
{
	domainsList.push_back(dom);
}

void Physics::addBoundary(Bcs_ bnd)
{
	boundariesList.push_back(bnd);
}

void Physics::addFlag(Flag fl)
{
	flags.push_back(fl);
}

void Physics::setFlagValue(std::string flagCondition, bool isActive)
{
	for (auto& fl : flags)
	{
		if (flagCondition == fl.getCondition())
		{
			fl.setValue(isActive);
			return;
		}
	}
}

void Physics::changeInitializationForRestart()
{
	for (auto& dom : domainsList)
	{
		for (auto& ic : dom->getInitialConditions())
		{
			json ijson;
			ijson = "InitFromResolution";
			InputValue iv;
			iv.readJson(ijson);
			dom->addInitialCondition(ic.first, iv);
		}
	}
}

void Physics::validateInput()
{
	//antennas check
	if (physicsKey == "physicsMicrowaves")
	{
		//check if end frequency is larger than start f
		if (timeTypeId == Physics::MULTIHARMONIC)
		{
			if (timeParameters["frequency"].getDoubleValue() >= timeParameters["fend"].getDoubleValue())
			{
				std::string message = "End frequency should be smaller than starting frequency";
				throw cenos_exception(message);
			}
		}

		//check if there is a feed
		bool feed_found = false;
		for (auto bnd : boundariesList)
		{
			if (bnd->getTypeKey() == "uniformPort" or bnd->getTypeKey() == "coaxialPort")
				feed_found = true;
		}
		if (!feed_found)
		{
			std::string message = "There is no port or feed defined in this model! Please check input data!";
			throw cenos_exception(message);
		}

	}
}

