
#include "SurroundingBuilder.h"
#include "ShapeAlgorithms.h"
#include "ShapePartition.h"
#include "BRepBuilderAPI_Transform.hxx"
#include "GeomLProp_SLProps.hxx"
#include "GeomLib_IsPlanarSurface.hxx"
#include "BRepBuilderAPI_MakeWire.hxx"
#include "GProp_GProps.hxx"
#include "BRep_Tool.hxx"
#include "BRepPrimAPI_MakeBox.hxx"
#include "BRepLib_MakeFace.hxx"
#include "TopExp.hxx"
#include "BRepBuilderAPI_MakeVertex.hxx"
#include "BRepPrimAPI_MakeHalfSpace.hxx"
#include "BRepAlgoAPI_Cut.hxx"
#include "BRepBuilderAPI_MakeEdge.hxx"
#include "BRepBuilderAPI_MakeFace.hxx"
#include "Geom_TrimmedCurve.hxx"
#include "GC_MakeSegment.hxx"
#include "TopExp_Explorer.hxx"
#include "BRepGProp.hxx"
#include "BRepAlgoAPI_Common.hxx"
#include "cenos_exception.h"

SurroundingBuilder::SurroundingBuilder()
{
	padding = 0;
	type = Type::FULL;
}

void SurroundingBuilder::setEntities(std::vector<TopoEntity_> ents)
{
	entities = ents;
}

void SurroundingBuilder::setCutBoundaries(TopTools_ListOfShape cb)
{
	cut_boundaries = cb;
}

void SurroundingBuilder::setPadding(double p)
{
	padding = p;
}

void SurroundingBuilder::setType(Type t)
{
	type = t;
}

void SurroundingBuilder::build()
{
	if (type == Type::FULL)
		createSurrounding();
	else if (type == Type::PLANAR_2D)
		createSurrounding2D();
	else if (type == Type::AXYSIMMETRIC_2D)
		createSurrounding2Daxi();
}

TopoDS_Shape SurroundingBuilder::getSurrounding()
{
	return surrounding_shape;
}

TopoDS_Shape SurroundingBuilder::getInfinity()
{
	return infinity_shape;
}

TopoDS_Shape SurroundingBuilder::getSymmetry()
{
	return symmetry_shape;
}


void SurroundingBuilder::createSurrounding()
{
	ShapePartition p(entities);

	// fix for wire templates (CP-1296)
	// in these templates wire inside is disabled solid
	// adding disabled solids to partition to allow creating normal air
	for (auto ent : entities)
	{
		if (ent->getDimension() != 3)
			continue;

		if (!ent->isEnabled())
			p.addShape(ent->getShape());
	}
	// end of fix

	TopoDS_Shape partition_shape = p.getPartition();

	ShapeStats stat;
	ShapeAlgorithms::getShapeStatistics(&partition_shape, stat);
	double partition_diagonal = ShapeAlgorithms::getDiagonal(partition_shape);

	//create a box around existing objectswith gap on all sides
	double gap;
	if (padding <= 0)
	{
		gap = 0.5 * sqrt(pow(stat.maxX - stat.minX, 2) + pow(stat.maxY - stat.minY, 2) + pow(stat.maxZ - stat.minZ, 2));
	}
	else
	{
		gap = padding;
	}

	gp_Pnt box_pny(stat.minX - gap, stat.minY - gap, stat.minZ - gap);

	BRepPrimAPI_MakeBox enclosure_box(box_pny, stat.maxX - stat.minX + 2 * gap,
		stat.maxY - stat.minY + 2 * gap,
		stat.maxZ - stat.minZ + 2 * gap);
	enclosure_box.Build();
	TopoDS_Shape enclosure_solid = enclosure_box.Solid();



	//cut with boundaries
	for (auto sh : cut_boundaries)
	{
		ShapeStats facestat;
		ShapeAlgorithms::getShapeStatistics(&sh, facestat);

		//check if cut boundaries are planar
		Handle(Geom_Surface) surf = BRep_Tool::Surface(TopoDS::Face(sh));

		GeomLib_IsPlanarSurface check(surf);
		if (!check.IsPlanar())
			throw(cenos_exception("Input faces for air boundary (terminal) is not planar! Please check input boundaries!"));

		double diagonal = ShapeAlgorithms::getDiagonal(sh);
		double face_scale_factor = partition_diagonal / diagonal * 100;
		gp_Trsf scale_transform;
		scale_transform.SetScale(gp_Pnt(facestat.centerX, facestat.centerY, facestat.centerZ), face_scale_factor);
		TopoDS_Face face = TopoDS::Face(BRepBuilderAPI_Transform(sh, scale_transform).Shape());

		//get surface normal
		GeomLProp_SLProps props(surf, 0.5, 0.5, 1, 0.01);
		gp_Dir norm = props.Normal();

		// get vectpr between partition center and face
		TopoDS_Vertex face_center_vertex = BRepBuilderAPI_MakeVertex(gp_Pnt(facestat.centerX, facestat.centerY, facestat.centerZ));
		TopoDS_Vertex center_vertex = BRepBuilderAPI_MakeVertex(gp_Pnt(stat.centerX, stat.centerY, stat.centerZ));
		gp_Vec vector; // vector between face  center and partition center
		ShapeAlgorithms::getMinDistance(face_center_vertex, center_vertex, vector);

		//find angle between normal and vector
		//if angle smaller than pi/2, reverse normal
		//if angle larger than pi/2 use this normal orientation


		if (norm.Angle(vector) < 3.14 / 2)
			norm.Reverse();

		gp_Trsf transform;
		transform.SetTranslation(norm);
		BRepBuilderAPI_Transform tr_builder(face_center_vertex, transform);

		// make plane
		gp_Pln plane = gp_Pln(gp_Pnt(facestat.centerX, facestat.centerY, facestat.centerZ), gp_Vec(norm));

		//make face
		BRepLib_MakeFace new_face = BRepLib_MakeFace(plane);
		BRepPrimAPI_MakeHalfSpace hs(new_face, BRep_Tool::Pnt(TopoDS::Vertex(tr_builder.Shape())));
		hs.Build();
		BRepAlgoAPI_Cut cut;
		TopTools_ListOfShape arglist;
		arglist.Append(enclosure_solid);
		TopTools_ListOfShape toollist;
		toollist.Append(hs.Solid());

		cut.SetArguments(arglist);
		cut.SetTools(toollist);
		cut.Build();
		enclosure_solid = cut.Shape();
	}
	if (enclosure_solid.IsNull())
		throw(cenos_exception("Problem during adding air! Please check your input or contact support!"));

	ShapePartition part;

	for (TopExp_Explorer exp_faces(enclosure_solid, TopAbs_FACE); exp_faces.More(); exp_faces.Next())
	{
		part.addShape(exp_faces.Current());
	}		 

	//initial infinity shape
	infinity_shape = part.getPartition();

	//set shapes if they are cut
	for (auto& ent : entities)
	{
		if (ent->getDimension() != 3)
			continue;

		BRepAlgoAPI_Common common_builder;
		common_builder.SetRunParallel(true);
		TopTools_ListOfShape s1_list, s2_list;
		s1_list.Append(ent->getShape());
		s2_list.Append(enclosure_solid);
		common_builder.SetArguments(s1_list);
		common_builder.SetTools(s2_list);
		common_builder.Build();

		TopoDS_Shape common = common_builder.Shape();
		TopExp_Explorer solidExplorer(common, TopAbs_ShapeEnum::TopAbs_SOLID);	  // should contain only one solid
		TopoDS_Shape solid_shape = solidExplorer.Current();
		
		ent->setShape(solid_shape);
	}	  


	TopTools_IndexedMapOfShape smap;
	TopExp::MapShapes(partition_shape, TopAbs_SOLID, smap);
	for (int si = 1; si <= smap.Extent(); si++)
	{
		TopoDS_Shape cut = BRepAlgoAPI_Cut(enclosure_solid, smap.FindKey(si)).Shape();
		enclosure_solid = cut;
	}

	ShapePartition part_infinity;

	for (TopExp_Explorer exp_faces(enclosure_solid, TopAbs_FACE); exp_faces.More(); exp_faces.Next())
	{
		if (ShapeAlgorithms::have_common_faces(exp_faces.Current(), infinity_shape))
			part_infinity.addShape(exp_faces.Current());
	}

	infinity_shape = part_infinity.getPartition();

	surrounding_shape = enclosure_solid;
}


void SurroundingBuilder::createSurrounding2D()
{
	ShapePartition p(entities);
	TopoDS_Shape partition_shape = p.getPartition();

	ShapeStats stat;
	ShapeAlgorithms::getShapeStatistics(&partition_shape, stat);

	//create a box around existing objectswith gap on all sides
	double gap;
	if (padding <= 0)
	{
		gap = 0.5 * sqrt(pow(stat.maxX - stat.minX, 2) + pow(stat.maxY - stat.minY, 2) + pow(stat.maxZ - stat.minZ, 2));
	}
	else
	{
		gap = padding;
	}

	gp_Pnt box_pnts[4] = { gp_Pnt(stat.minX - gap, stat.minY - gap, 0),
					gp_Pnt(stat.maxX + gap, stat.minY - gap, 0),
					gp_Pnt(stat.maxX + gap, stat.maxY + gap, 0),
					gp_Pnt(stat.minX - gap, stat.maxY + gap, 0) };
	BRepBuilderAPI_MakeWire b_wire;
	for (int i = 0; i < 4; i++)
	{
		Handle(Geom_TrimmedCurve) b_segment = GC_MakeSegment(box_pnts[i], box_pnts[i == 3 ? 0 : i + 1]);
		TopoDS_Edge b_edge = BRepBuilderAPI_MakeEdge(b_segment);
		b_wire.Add(b_edge);
	}
	TopoDS_Face box_face = BRepBuilderAPI_MakeFace(b_wire.Wire());

	TopoDS_Shape box_shape = box_face;

	ShapePartition part;

	for (TopExp_Explorer exp_edges(box_shape, TopAbs_EDGE); exp_edges.More(); exp_edges.Next())
	{
		part.addShape(exp_edges.Current());
	}

	infinity_shape = part.getPartition();

	TopTools_IndexedMapOfShape fmap;
	TopExp::MapShapes(partition_shape, TopAbs_FACE, fmap);
	for (int fi = 1; fi <= fmap.Extent(); fi++)
	{
		TopoDS_Shape cut = BRepAlgoAPI_Cut(box_shape, fmap.FindKey(fi)).Shape();

		TopExp_Explorer faceExplorer(cut, TopAbs_ShapeEnum::TopAbs_FACE);

		box_shape = faceExplorer.Current();
	}

	surrounding_shape =  box_shape;
}


void SurroundingBuilder::createSurrounding2Daxi()
{
	ShapePartition p(entities);
	TopoDS_Shape partition_shape = p.getPartition();

	ShapeStats stat;
	ShapeAlgorithms::getShapeStatistics(&partition_shape, stat);

	if ((0.01 * stat.minX < -Precision::Confusion()))
		throw(cenos_exception("Error during creating of air. Axysimmetric geometry should be in XY plane with positive X"));

	// add additional tolerance 0.01. Uzing 1, it is sometimes not enough. Adjust if problems accur!
	if ((abs(0.01 * stat.minZ) > Precision::Confusion()) or (abs(0.01 * stat.maxZ) > Precision::Confusion()))
		throw(cenos_exception("Error during creating of air. Could not create air for non-planar geometry."));

	//create a box around existing objectswith gap on all sides
	double gap;
	if (padding <= 0)
	{
		gap = 0.5 * sqrt(pow(stat.maxX - stat.minX, 2) + pow(stat.maxY - stat.minY, 2) + pow(stat.maxZ - stat.minZ, 2));
	}
	else
	{
		gap = padding;
	}

	gp_Pnt box_pnts[4] = { gp_Pnt(0, stat.minY - gap, 0),
					gp_Pnt(stat.maxX + gap, stat.minY - gap, 0),
					gp_Pnt(stat.maxX + gap, stat.maxY + gap, 0),
					gp_Pnt(0, stat.maxY + gap, 0) };
	BRepBuilderAPI_MakeWire b_wire;
	for (int i = 0; i < 4; i++)
	{
		Handle(Geom_TrimmedCurve) b_segment = GC_MakeSegment(box_pnts[i], box_pnts[i == 3 ? 0 : i + 1]);
		TopoDS_Edge b_edge = BRepBuilderAPI_MakeEdge(b_segment);
		b_wire.Add(b_edge);
	}
	TopoDS_Face box_face = BRepBuilderAPI_MakeFace(b_wire.Wire());

	TopoDS_Shape box_shape = box_face;

	TopTools_IndexedMapOfShape fmap;
	TopExp::MapShapes(partition_shape, TopAbs_FACE, fmap);
	for (int fi = 1; fi <= fmap.Extent(); fi++)
	{
		TopoDS_Shape cut = BRepAlgoAPI_Cut(box_shape, fmap.FindKey(fi)).Shape();

		ShapePartition p;
		for (TopExp_Explorer face_explorer(cut, TopAbs_FACE); face_explorer.More(); face_explorer.Next())
		{
			p.addShape(face_explorer.Current());
		}

		box_shape = p.getPartition();
	}

	ShapePartition part_inf, part_symm;

	//assign shapes to infinity and symmetry
	for (TopExp_Explorer exp_edges(box_shape, TopAbs_EDGE); exp_edges.More(); exp_edges.Next())
	{
		//skip edges that belong to passed shape
		bool found = false;
		for (TopExp_Explorer exp_edges_shape(partition_shape, TopAbs_EDGE); exp_edges_shape.More(); exp_edges_shape.Next())
		{
			if (exp_edges_shape.Current().IsSame(exp_edges.Current()))
			{
				found = true;
				break;
			}
		}
		if (found)
			continue;

		GProp_GProps gprops1;
		BRepGProp::LinearProperties(exp_edges.Current(), gprops1);
		if (abs(gprops1.CentreOfMass().X()) < 1e-7)
		{
			part_symm.addShape(exp_edges.Current());
		}
		else
		{
			part_inf.addShape(exp_edges.Current());
		}

	}

	infinity_shape = part_inf.getPartition();
	symmetry_shape = part_symm.getPartition();

	surrounding_shape =  box_shape;
}