#include "GetdpSpecialInputs.h"
#include "topology/ShapeAlgorithms.h"
#include "base/base.h"
#include <TopExp_Explorer.hxx>
#include <TopoDS_Vertex.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepAlgoAPI_Section.hxx>
#include <BRep_Tool.hxx>
#include "topology/shapePartition.h"
#include "cenos_exception.h"

std::string uniformPort(std::string boundary_name, std::vector<Bcs_> bclist, std::vector<Domains_> domlist, std::shared_ptr<GeometryData> geom_data)
{
	//find feed shape
	TopoDS_Shape feed_shape;
	for (auto bnd : geom_data->getBoundaries())
	{
		if (bnd->getName() == boundary_name)
		{
			feed_shape = bnd->getShape();
			break;
		}
	}

	if (feed_shape.IsNull())
		throw cenos_exception("Could not find feed boundary shape! Please check input data!");
	


	//find list of conductors touched by feed
	TopTools_ListOfShape conductorlist;
	for (auto bc : bclist)
	{
		if (bc->getName() == boundary_name)
			continue;

		if (!(bc->getTypeKey() == "perfectConductor" or bc->getTypeKey() == "conductingBoundary") )
			continue;

		for (auto bnd : geom_data->getBoundaries())
		{
			if (bnd->getName() == boundary_name)
				continue;

			if (bnd->getName() != bc->getName())
				continue;

			TopoDS_Shape bnd_shape = bnd->getShape();
			if (ShapeAlgorithms::getMinDistance(feed_shape, bnd_shape) == 0 )
			{
				for (auto ent: bnd->getEntities())
					conductorlist.Append(ent->getShape());
			}
		}
	}

	if (conductorlist.IsEmpty())
		throw cenos_exception("Could not find conducting parts connected to feed! Please check input data!");

	//get edges between conductors and feed
	TopoDS_Shape conductors;
	ShapePartition part;
	for (auto sh : conductorlist)
	{
		part.addShape(sh);
	}
	conductors = part.getPartition();
	TopoDS_Shape shared_edges = BRepAlgoAPI_Section(feed_shape, conductors);
	auto edge_list = ShapeAlgorithms::sortEdges(shared_edges);

	if (edge_list.Size() < 2)
	{
		std::string message = "Automatic determination of uniform port parameters failed. Please uncheck the \"Automatic Feed Parameters\" checkbox under port boundary and specify parameters manually! ";
		throw cenos_exception(message);
	}

	//edges parallel to feed directions
	TopTools_ListOfShape feed_edges;
	for (TopExp_Explorer edge_explorer(feed_shape, TopAbs_ShapeEnum::TopAbs_EDGE); edge_explorer.More(); edge_explorer.Next())
	{
		TopoDS_Shape current = edge_explorer.Current();
		if (ShapeAlgorithms::have_common(current, shared_edges))
			continue;
		
		feed_edges.Append(current);
	}


	//start with one vertex on shared_edges, and move along the edge in feed_edges
	//until hit another vertex on shared_edges.

	TopoDS_Vertex starting_vertex = TopoDS::Vertex(TopExp_Explorer(shared_edges, TopAbs_ShapeEnum::TopAbs_VERTEX).Current());
	TopoDS_Vertex end_vertex;

	//for cases when feed is split perpendicular to feed direction and there are intermediate points
	TopoDS_Vertex starting_vertex_new = starting_vertex;

	int safety = 0;
	while (!ShapeAlgorithms::have_common_vertices(shared_edges, end_vertex))
	{
		safety++;
		//prevent beeing stuck in a loop
		if (safety > 100)
		{
			std::string message = "Automatic determination of uniform port parameters failed. Please uncheck the \"Automatic Feed Parameters\" checkbox under port boundary and specify parameters manually! ";
			throw cenos_exception(message);
		}
		for (auto edge : feed_edges)
		{
			if (ShapeAlgorithms::have_common_vertices(edge, starting_vertex_new))
			{
				TopoDS_Vertex v1 = TopExp::FirstVertex(TopoDS::Edge(edge));
				TopoDS_Vertex v2 = TopExp::LastVertex(TopoDS::Edge(edge));
				gp_Pnt p1 = BRep_Tool::Pnt(v1);
				gp_Pnt p2 = BRep_Tool::Pnt(v2);
				gp_Pnt starting_pnt = BRep_Tool::Pnt(starting_vertex_new);

				if (p1.IsEqual(starting_pnt, 1e-8))
				{
					end_vertex = v2;
				}
				else if (p2.IsEqual(starting_pnt, 1e-8))
				{
					end_vertex = v1;
				}
				starting_vertex_new = end_vertex;
				break;
			}
		}
	}

	//convert vertices to points
	gp_Pnt p_start = BRep_Tool::Pnt(starting_vertex);
	gp_Pnt p_end = BRep_Tool::Pnt(end_vertex);

	double distance = p_end.Distance(p_start);
	gp_Vec feed_vector(p_end, p_start);

	if (feed_vector.Magnitude() < gp::Resolution())
	{
		std::string message = "Automatic determination of uniform port parameters failed. Please uncheck the \"Automatic Feed Parameters\" checkbox under port boundary and specify parameters manually! ";
		throw cenos_exception(message);
	}

	if (distance <= 1e-9)
	{
		std::string message = "Automatic determination of uniform port parameters failed. Please uncheck the \"Automatic Feed Parameters\" checkbox under port boundary and specify parameters manually! ";
		throw cenos_exception(message);
	}

	feed_vector.Normalize();

	std::string output;
	output.append("  volt[" + boundary_name + "] = 1.0;\n");
	output.append("  gap[" + boundary_name + "] = " + std::to_string(distance * lengthToSI(geom_data->getLengthUnit())) + ";\n");
	output.append("  eDirX[" + boundary_name + "] = " + std::to_string(feed_vector.X()) + ";\n");
	output.append("  eDirY[" + boundary_name + "] = " + std::to_string(feed_vector.Y()) + ";\n");
	output.append("  eDirZ[" + boundary_name + "] = " + std::to_string(feed_vector.Z()) + ";\n");

	return output;
}



std::string coaxialPort(std::string boundary_name, std::vector<Bcs_> bclist, std::vector<Domains_> domlist,  std::shared_ptr<GeometryData> geom_data)
{
	//find feed shape
	TopoDS_Shape feed_shape;
	for (auto bnd : geom_data->getBoundaries())
	{
		if (bnd->getName() == boundary_name)
		{
			feed_shape = bnd->getShape();
			break;
		}
	}


	TopExp_Explorer wire_explorer(feed_shape, TopAbs_ShapeEnum::TopAbs_WIRE);
	TopoDS_Shape wire1 = wire_explorer.Current();
	wire_explorer.Next();
	TopoDS_Shape wire2 = wire_explorer.Current();

	gp_Pnt center = ShapeAlgorithms::getCenterPoint(feed_shape);
	double wire1_dist = ShapeAlgorithms::getMinDistance(wire1, TopoDS_Shape(BRepBuilderAPI_MakeVertex(center)));
	double wire2_dist = ShapeAlgorithms::getMinDistance(wire2, TopoDS_Shape(BRepBuilderAPI_MakeVertex(center)));


	double outer_radius = std::max(wire1_dist, wire2_dist);
	double inner_radius = std::min(wire1_dist, wire2_dist);
	
	if (outer_radius <= inner_radius)
	{
		std::string message = "Automatic determination of coaxial port parameters failed. Please uncheck the \"Automatic Feed Parameters\" checkbox under port boundary and specify parameters manually! ";
		throw cenos_exception(message);
	}
	if (outer_radius == 0 or inner_radius == 0)
	{
		std::string message = "Automatic determination of coaxial port parameters failed. Please uncheck the \"Automatic Feed Parameters\" checkbox under port boundary and specify parameters manually! ";
		throw cenos_exception(message);
	}

	std::string output;
	output.append("  volt[" + boundary_name + "] = 1.0;\n");
	output.append("  radius_in[" + boundary_name + "] = " + std::to_string(inner_radius * lengthToSI(geom_data->getLengthUnit())) + ";\n");
	output.append("  radius_out[" + boundary_name + "] = " + std::to_string(outer_radius * lengthToSI(geom_data->getLengthUnit())) + ";\n");

	return output;
}