#include"../phy/Phy_Base.h"
#include"../phy/PortModeSolver.h"
#include"../material/Material_Base.h"
#include"../mesh/Mesh_Base.h"
#include"../kernel/Assemble_Base.h"
#include"../solver/Solver_Base.h"
#include "../nlohmann/json.hpp"
#include "FEM_Interface.h"

#include "../Eigen/Dense"

#include <string>
#include <fstream>
#include <iostream>
#include <iomanip>


using namespace std;
using json = nlohmann::json;

int OpticsFEM_API::OpticsFEM_All(OpticsFEMData data)
{
	//???
	string str = data.data;
	json js = nlohmann::json::parse(str);
	int FemType = js.at("FemType");

	//???????
	Phy_WaveOpticsModel phy;
	MaterialLib matLab;
	Mesh_2D mesh;

	//??????????
	phy.Test_ReadData(str);
	matLab.Test_ReadData(str);
	string meshFile = js.at("MeshFile");
	mesh.GetMesh(meshFile,str);
	if (FemType == 0)
	{
		OpticsFEM_2D_EigenMode fem;
		Solver_EigenMode solver;
		Post_2D_EigenMode post;

		solver.GetSolver(str);

		fem.GetMaterial(&matLab);
		fem.GetMesh(&mesh);
		fem.GetPhy(&phy);
		fem.GetSolver(&solver);
		fem.GetPost(&post);

		fem.Assemble();
		fem.Run();

		string outFile = js.at("OutFile");
		fem.Post(outFile);
		//fem.Test_OutputMatrix();
		return 1;
	}
	else if (FemType == 1)
	{
		OpticsFEM_2D_EigenFreq fem;
		Solver_EigenFreq solver;
		Post_2D_EigenFreq post;

		solver.GetSolver(str);

		fem.GetMaterial(&matLab);
		fem.GetMesh(&mesh);
		fem.GetPhy(&phy);
		fem.GetSolver(&solver);
		fem.GetPost(&post);

		fem.Assemble();
		fem.Run();

		string outFile = js.at("OutFile");
		fem.Post(outFile);
		return 2;
	}
	else if (FemType == 2)
	{
		OpticsFEM_2D_Scatter fem;
		Solver_LdaDom solver;
		Post_2D_Scatter post;

		solver.GetSolver(str);

		fem.GetMaterial(&matLab);
		fem.GetMesh(&mesh);
		fem.GetPhy(&phy);
		fem.GetSolver(&solver);
		fem.GetPost(&post);

		fem.Assemble();
		fem.Run();
		fem.Test_OutputMatrix();
		string outFile = js.at("OutFile");
		fem.Post(outFile);
		//fem.Test_OutputMatrix();
		return 3;
	}
    else if (FemType == 3)
	{
		OpticsFEM_2D_Scatter fem;
		Solver_LdaDom solver;
		Post_2D_Scatter post;

		solver.GetSolver2(str);

		fem.GetMaterial(&matLab);
		fem.GetMesh(&mesh);
		fem.GetPhy(&phy);
		fem.GetSolver(&solver);
		fem.GetPost(&post);

		fem.Assemble();
		fem.Run();
		string outFile = js.at("OutFile");
		fem.Post(outFile);
		return 3;
	}
	else if (FemType == 4)
	{
		Mesh_3D mesh3d;
		OpticsFEM_3D_Scatter fem;
		Solver_LdaDom solver;
		Post_3D_Scatter post;

		solver.GetSolver(str);
		mesh3d.GetMesh(meshFile, str);

		// Optional: compute numeric port modes in-process (MATLAB BoundaryEigenMode)
		if (js.contains("port") && js.at("port").value("computeModes", false))
		{
			const json& port = js.at("port");
			Eigen::VectorXi din(phy.GetNbrPortinc());
			Eigen::VectorXi dout(phy.GetNbrPortout());
			for (int i = 0; i < phy.GetNbrPortinc(); ++i)
				din(i) = phy.GetPortincDomain(i);
			for (int i = 0; i < phy.GetNbrPortout(); ++i)
				dout(i) = phy.GetPortoutDomain(i);
			const double lam0 = js.at("lambda").get<double>();
			const double targetNeff = port.value("targetNeff", 2.6);
			const int modeNum = port.value("modeNum", 1);
			const bool normIn = port.value("normalizeInput", true);
			std::cout << "[OpticsFEM] computing port modes (targetNeff=" << targetNeff
				<< ", modeNum=" << modeNum << ")" << std::endl;
			if (!ComputePortModesFromMesh(&mesh3d, &matLab, din, dout, lam0, targetNeff,
				modeNum, phy.MutablePortModes(), normIn))
			{
				std::cerr << "[OpticsFEM] ComputePortModesFromMesh failed" << std::endl;
				return -4;
			}
			if (port.contains("modeFileOut"))
			{
				const std::string outMode = port.at("modeFileOut").get<std::string>();
				if (SavePortModesToFile(phy.MutablePortModes(), outMode))
					std::cout << "[OpticsFEM] wrote computed modes -> " << outMode << std::endl;
			}
		}

		fem.GetMaterial(&matLab);
		fem.GetMesh(&mesh3d);
		fem.GetPhy(&phy);
		fem.GetSolver(&solver);
		fem.GetPost(&post);

		string outFile = js.at("OutFile");
		fem.SetExportAsmDirectory(outFile + "_asm");
		fem.Assemble();
		fem.Test_OutputMatrix(outFile);
		fem.Run();
		fem.Post(outFile);
		return 4;
	}
	else if (FemType == 5)
	{
		Mesh_3D mesh3d;
		OpticsFEM_3D_EigenFreq fem;
		Solver_EigenFreq solver;
		Post_3D_EigenFreq post;

		solver.GetSolver(str);
		mesh3d.GetMesh(meshFile, str);

		fem.GetMaterial(&matLab);
		fem.GetMesh(&mesh3d);
		fem.GetPhy(&phy);
		fem.GetSolver(&solver);
		fem.GetPost(&post);

		fem.Assemble();
		string outFile = js.at("OutFile");
		fem.Test_OutputMatrix(outFile);
		fem.Run();
		fem.Post(outFile);
		return 5;
	}
	else
	{
		std::cout << "err" << std::endl;
		return 0;
	}
}

int OpticsFEM_API::OpticsFEM_Test(OpticsFEMData data)
{
	//???
	string str = data.data;
	json js = nlohmann::json::parse(str);
	string outFile = js.at("OutFile");
	outFile = outFile + "test";

	std::ofstream out;
	out.open(outFile, std::ios::out | std::ios::trunc);
	out << "test success" << endl;
	out.close();

	return 1;
}
