/*
 * Material.cpp
 *
 *  Created on: May 8, 2017
 *      Author: vadims
 */

#include "Material.hpp"
#include <sstream>

Material::Material()
{
}

Material::~Material()
{
}

Material::Material(std::string matName, json j)
{
	 name = matName;
	 std::string ss = j.dump();
	 if (j.find("data") != j.end()) {
	 	 for (json::iterator jt = j["data"].begin(); jt != j["data"].end(); ++jt) {
				InputValue val;
				val.readJson(jt.value());
				materialData[jt.key()] = val;
	 	 }
	 }
	 if (j.find("id") != j.end())
		 id = j.at("id").get<int>();
}

Material::Material(const Material& that)
{
	id = that.id;
	name = that.name;
	materialData = that.materialData;
}

Material& Material::operator=(Material that)
{
	id = that.id;
	name = that.name;
	materialData = that.materialData;
	return *this;
}


void Material::addData(std::string propName, InputValue val)
{
	materialData[propName] = val;
}

std::string Material::getMaterialValue(std::string valueName)
{
	return materialData[valueName].getValue();
}

std::string Material::getMaterialValueWithEnds(std::string valueName)
{
	return materialData[valueName].getValueWithEnds();
}

std::string Material::getMaterialDerivativeWithEnds(std::string valueName)
{
	return materialData[valueName].getDerivativeWithEnds();
}





std::string Material::getName()
{
	return Material::name;
}

int Material::getId()
{
	return Material::id;
}


std::map<std::string, InputValue> Material::getMaterialData()
{
	return Material::materialData;
}

bool Material::getMaterialDataFlag(std::string flagName)
{
	for (auto &md: materialData)
	{
		if ( (flagName == md.first) and (md.second.getTypeId()==InputValue::BOOLEAN) )
			if (md.second.getValue() == "1")
				return true;
	}
	return false;
}

std::string Material::getMaterialValueByName(std::string fieldName)
{
	for (auto &md: materialData)
	{
		if ( (fieldName == md.first) )
				return md.second.getValue();
	}
	return "NaN";
}


bool Material::isNL()
{
	for (auto &md: materialData)
	{
		if (md.first == "BHModel")
			return true;
	}
	return false;
}

std::pair<double, double> Material::getSkinLayerThicknessRange(double freq_min, double freq_max)
{
	const double mu0 = 4 * 3.14 * 1e-7;
	bool is_conductive = false;
	double skin_thickness_max = 0, skin_thickness_min = 0;
	double mu_min = 1, mu_max = 1;
	double sigma_min, sigma_max;

	for (auto& md : materialData)
	{
		if ((md.first == "BHModel"))
		{
			std::vector<std::pair<double, double>> muHdata;
			auto BHdata = effectiveBH(this->getMaterialValue(md.first).substr(23));
			for (auto BHpoint : BHdata)
			{
				if ((BHpoint.first != 0) and (BHpoint.second != 0))
					muHdata.push_back(std::make_pair(BHpoint.first, BHpoint.second / (mu0 * BHpoint.first)));
			}
			auto maxMuPair = *std::max_element(muHdata.begin(), muHdata.end(),
				[](const std::pair<float, float>& left, const std::pair<float, float>& right) {
					return left.second < right.second; });
			mu_max = maxMuPair.second * mu0;
			mu_min = 1 * mu0;
		}
		else if ((md.first == "mu"))
		{
			mu_max = md.second.getMaxValue() * mu0;
			mu_min = md.second.getMinValue() * mu0;
		}
		else if ((md.first == "mu_C"))
		{
			mu_max = md.second.getMaxValue() * mu0;
			if (this->getMaterialDataFlag("useTForMagFlux"))
			{
				mu_min = 1 * mu0;
			}
			else
			{
				mu_min = md.second.getMinValue() * mu0;
			}
		}
		else if ((md.first == "sigma"))
		{
			is_conductive = true;
			sigma_max = md.second.getMaxValue();
			sigma_min = md.second.getMinValue();
		}
	}

	if (is_conductive)
	{
		skin_thickness_max = sqrt(1 / (3.14 * freq_min * sigma_min * mu_min));
		skin_thickness_min = sqrt(1 / (3.14 * freq_max * sigma_max * mu_max));
	}

	return std::pair<double, double>(skin_thickness_min, skin_thickness_max);
}





std::vector<std::pair<float, float>> Material::effectiveBH(std::string data)
{
	int method = 0;
	/* method = 1 - active energy method, else - simple energy method */

	std::vector<std::pair<float, float> > outVec;
	std::vector<float> bVec;
	std::vector<float> bVec_new;
	std::vector<float> hVec;
	std::vector<float> dataVec;
	std::stringstream bhStream(data.substr(1, data.size() - 2));

	float dp;
	while (bhStream >> dp)
	{
		dataVec.push_back(dp);

		if (bhStream.peek() == ',')
			bhStream.ignore();
	}


	for (size_t i = 0; i < dataVec.size() / 2; i++)
	{
		hVec.push_back(dataVec[i * 2]);
		bVec.push_back(dataVec[i * 2 + 1]);
	}

	if ((bVec[0] != 0) and (hVec[0] != 0))
	{
		bVec.insert(bVec.begin(), 0.0);
		hVec.insert(hVec.begin(), 0.0);
	}

	if (bVec.size() == 1)
		return { std::make_pair(hVec[0], bVec[0]) };

	bVec_new.push_back(bVec[0]);

	if (method != 1)
	{
		for (size_t i = 1; i < bVec.size(); i++)
		{
			float locSum = 0;
			for (size_t j = 1; j <= i; j++)
			{
				locSum = locSum + (hVec[j] - hVec[j - 1]) * (bVec[j - 1] + bVec[j]) / 2;
			}
			bVec_new.push_back((2 / hVec[i]) * locSum);
		}
	}
	else
	{
		//active energy method not implemented
	}

	for (size_t i = 0; i < bVec.size(); i++)
	{
		outVec.push_back(std::make_pair(hVec[i], bVec_new[i]));
	}
	return outVec;
}
