#include "WorkpieceTemplate.h"

#include <GC_MakeSegment.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Wire.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Solid.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopoDS_Face.hxx>
#include <TopTools_MapOfShape.hxx>
#include <BRepAlgoAPI_BuilderAlgo.hxx>
#include <BRepAlgoAPI_Cut.hxx>
#include <STEPControl_Writer.hxx>
#include <TopExp_Explorer.hxx>
#include <GProp_GProps.hxx>
#include <BRepGProp.hxx>
#include <BRepPrimAPI_MakeSphere.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepBndLib.hxx>
#include <map>
#include <filesystem>
#include "easylogging++/easylogging++.h"
#include <TopTools_IndexedMapOfShape.hxx>
#include "topology/ShapeAlgorithms.h"
#include "vtkForCenos/topologyToVTK.h"
#include "misc/miscFunctions.hpp"
#include "mesh/MeshGenerator.h"
#include "mesh/AutoMeshSize.h"
#include "topology/shapePartition.h"

WorkpieceTemplate::WorkpieceTemplate(std::string path) : BasicTemplate(path)
{
    type = WORKPIECE;
}

void WorkpieceTemplate::parseInputJSON(json* j)
{
    json& data = *j;
    name = data["name"].get<std::string>();
    if (data["shapeType"].get<std::string>() == "workpieceFiniteRod")
    {
        wp_type = BILLET;
        workpiece_diameter = data["properties"]["workpiece_diameter"].get<double>();
        workpiece_height = data["properties"]["workpiece_height"].get<double>();
    }
    else if (data["shapeType"].get<std::string>() == "workpieceSteppedShaft")
    {
        wp_type = STEPPED_SHAFT;
        diameter1 = data["properties"]["diameter1"].get<double>();
        diameter2 = data["properties"]["diameter2"].get<double>();
        height1 = data["properties"]["height1"].get<double>();
        height2 = data["properties"]["height2"].get<double>();
    }
    else if (data["shapeType"].get<std::string>() == "workpieceTube")
    {
        wp_type = TUBE;
        diameter1 = data["properties"]["diameter1"].get<double>();
        diameter2 = data["properties"]["diameter2"].get<double>();
        workpiece_height = data["properties"]["workpiece_height"].get<double>();
    }

    else if (data["shapeType"].get<std::string>() == "workpieceShaftEnd")
        wp_type = SHAFT_END;

}

TopoDS_Face WorkpieceTemplate::makeBillet(TopTools_ListOfShape& wp_air, TopTools_ListOfShape& wp_symm)
{
    gp_Pnt pnt[4] = { gp_Pnt(0, -workpiece_height / 2.0, 0),
                        gp_Pnt(workpiece_diameter / 2.0, -workpiece_height / 2.0, 0),
                        gp_Pnt(workpiece_diameter / 2.0, workpiece_height / 2.0, 0),
                        gp_Pnt(0, workpiece_height / 2.0, 0) };
    BRepBuilderAPI_MakeWire b_wire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) b_segment = GC_MakeSegment(pnt[i], pnt[i == 3 ? 0 : i + 1]);
        TopoDS_Edge b_edge = BRepBuilderAPI_MakeEdge(b_segment);
        b_wire.Add(b_edge);
        i == 3 ? wp_symm.Append(b_edge) : wp_air.Append(b_edge);
    }
    TopoDS_Face billet = BRepBuilderAPI_MakeFace(b_wire.Wire());
    return billet;
}


TopoDS_Face WorkpieceTemplate::makeTube(TopTools_ListOfShape& wp_air)
{
    gp_Pnt pnt[4] = { gp_Pnt(diameter1 / 2.0, -workpiece_height / 2.0, 0),
                        gp_Pnt(diameter2 / 2.0, -workpiece_height / 2.0, 0),
                        gp_Pnt(diameter2 / 2.0, workpiece_height / 2.0, 0),
                        gp_Pnt(diameter1 / 2.0, workpiece_height / 2.0, 0) };
    BRepBuilderAPI_MakeWire b_wire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) b_segment = GC_MakeSegment(pnt[i], pnt[i == 3 ? 0 : i + 1]);
        TopoDS_Edge b_edge = BRepBuilderAPI_MakeEdge(b_segment);
        b_wire.Add(b_edge);
        wp_air.Append(b_edge);
    }

    TopoDS_Face tube = BRepBuilderAPI_MakeFace(b_wire.Wire());

    return tube;
}

TopoDS_Face WorkpieceTemplate::makeSteppedShaft(TopTools_ListOfShape& wp_air, TopTools_ListOfShape& wp_symm)
{
    gp_Pnt pnt[6] = { gp_Pnt(0, -height2, 0),
                        gp_Pnt(diameter2 / 2.0, -height2, 0),
                        gp_Pnt(diameter2 / 2.0, 0, 0),
                        gp_Pnt(diameter1 / 2.0, 0, 0),
                        gp_Pnt(diameter1 / 2, height1, 0),
                        gp_Pnt(0, height1, 0)};
    BRepBuilderAPI_MakeWire b_wire;
    for (int i = 0; i < 6; i++)
    {
        Handle(Geom_TrimmedCurve) b_segment = GC_MakeSegment(pnt[i], pnt[i == 5 ? 0 : i + 1]);
        TopoDS_Edge b_edge = BRepBuilderAPI_MakeEdge(b_segment);
        b_wire.Add(b_edge);

        i == 5 ? wp_symm.Append(b_edge) : wp_air.Append(b_edge);
    }
    TopoDS_Face stepped_shaft = BRepBuilderAPI_MakeFace(b_wire.Wire());

    return stepped_shaft;
}






void WorkpieceTemplate::generateGeometry()
{
    if (progress != nullptr)
        progress->sendLog("Generating workpiece template geometry ");

    TopoDS_Shape workpiece;
    //shapes for boundaries
    TopTools_ListOfShape wp_air;
    TopTools_ListOfShape wp_symm;
    TopTools_ListOfShape wp_inf;

    if (wp_type == BILLET)
    {
        workpiece = makeBillet(wp_air, wp_symm);
    }
    else if (wp_type == STEPPED_SHAFT)
    {
        workpiece = makeSteppedShaft(wp_air, wp_symm);
    }
    else if (wp_type == TUBE)
    {
        workpiece = makeTube(wp_air);
    }

    TopoEntity_ workpiece_ent = std::make_shared<TopoEntity>(workpiece, "workpiece_ent");
    geom_data->addEntity(workpiece_ent);
    DomainGroup_ workpiece_group = std::make_shared<DomainGroup>();
    workpiece_group->setName("workpiece");
    workpiece_group->setLabel("Workpiece");
    workpiece_group->setRole("workpiece");
    workpiece_group->addEntity(workpiece_ent);
    workpiece_group->update();
    geom_data->addDomainGroup(workpiece_group);

    //Boundaries
    if (!wp_symm.IsEmpty())
    {
        BoundaryGroup_ bc_group = std::make_shared<BoundaryGroup>();
        bc_group->setName("workpiece_axis");
        bc_group->setLabel("Workpiece Axis");
        bc_group->setRole("wp_axis");

        int sh_nr = 0;
        for (auto sh : wp_symm)
        {
            TopoEntity_ ent = std::make_shared<TopoEntity>(sh, "wp_symm_ent"+ std::to_string(++sh_nr));
            geom_data->addEntity(ent);
            bc_group->addEntity(ent);

        }

        bc_group->update();
        geom_data->addBoundaryGroup(bc_group);
    }

    if (!wp_air.IsEmpty())
    {
        BoundaryGroup_ bc_group = std::make_shared<BoundaryGroup>();
        bc_group->setName("workpiece_surface");
        bc_group->setLabel("Workpiece Surface");
        bc_group->setRole("wp_surface2D");
        //TO DO if make 3D template, assigne role to wp_surface3D

        int sh_nr = 0;
        for (auto sh : wp_air)
        {
            TopoEntity_ ent = std::make_shared<TopoEntity>(sh, "wp_air_ent" + std::to_string(++sh_nr));
            geom_data->addEntity(ent);
            bc_group->addEntity(ent);
        }

        bc_group->update();
        geom_data->addBoundaryGroup(bc_group);
    }

    if (!wp_inf.IsEmpty())
    {
        BoundaryGroup_ bc_group = std::make_shared<BoundaryGroup>();
        bc_group->setName("workpiece_infinity");
        bc_group->setLabel("Workpiece Infinity");
        bc_group->setRole("");

        int sh_nr = 0;
        for (auto sh : wp_inf)
        {
            TopoEntity_ ent = std::make_shared<TopoEntity>(sh, "wp_inf_ent" + std::to_string(++sh_nr));
            geom_data->addEntity(ent);
            bc_group->addEntity(ent);

        }

        bc_group->update();
        geom_data->addBoundaryGroup(bc_group);
    }
}

void WorkpieceTemplate::assignMeshSizes(MeshGenerator_ mg, GeometryData_ gd, double mesh_density_factor, double global_max)
{

    // for electromagnetics - calculate skin depth range for each conducting domain
    double skin_min = 0, skin_max = 0;
    bool assign_layers = false;
    for (auto phys : physics)
    {
        if (phys.getSolverKey() == "getdpEMGeneral2D" or phys.getSolverKey() == "getdpEMGeneral3D")
        {
            float frequency_min = phys.getTimeParameters()["frequency"].getMinValue();
            float frequency_max = phys.getTimeParameters()["frequency"].getMaxValue();

            for (auto dom : phys.getDomainsList())
            {
                if (dom->getName() != "workpiece")
                    continue;

                if (dom->getTypeKey() == "em_cc" or dom->getTypeKey() == "source_dom")
                {
                    auto mat = phys.getMaterialById(dom->getMaterialId());
                    auto skin_layers = mat.getSkinLayerThicknessRange(frequency_min, frequency_max);
                    skin_min = skin_layers.first / lengthToSI(gd->getLengthUnit());
                    skin_max = skin_layers.second / lengthToSI(gd->getLengthUnit());
                    assign_layers = true;
                }
            }
        }
    }


    //refinement on workpiece
    std::shared_ptr<MeshParameters> mp_wp = std::make_shared<MeshParameters>();
    double maxh_local, minh_local;

    if (wp_type == BILLET)
    {
        maxh_local = std::min({ global_max, workpiece_diameter / 20.0, workpiece_height / 10.0 });
        minh_local = std::min({ workpiece_diameter / 40.0, workpiece_height / 20.0 });
        if (assign_layers)
            mp_wp->calculateLayers(skin_min, skin_max, std::min(workpiece_diameter / 4.0, workpiece_height/ 4.0));
    }
    else if (wp_type == STEPPED_SHAFT)
    {
        maxh_local = std::min({ global_max, diameter1 / 20.0, diameter2 / 20.0, height1 / 10.0, height2 / 10.0 });
        minh_local = std::min({ diameter1 / 40.0, diameter2 / 40.0, height1 / 20.0, height2 / 20.0 });
        if (assign_layers)
            mp_wp->calculateLayers(skin_min, skin_max, std::min({ diameter1 / 4.0 , diameter2 / 4.0, height1 / 4.0, height2 / 4.0 }));
    }
    else if (wp_type == TUBE)
    {
        maxh_local = std::min({ global_max, abs(diameter1 - diameter2) / 20.0, workpiece_height / 10.0 });
        minh_local = std::min({ global_max, abs(diameter1 - diameter2) / 40.0, workpiece_height / 20.0 });
        if (assign_layers)
            mp_wp->calculateLayers(skin_min, skin_max, abs(diameter1 - diameter2) / 5.0);
    }


    if (minh_local * 1.5 > maxh_local)
        minh_local = minh_local / 2;

    mp_wp->setMaxH(maxh_local * mesh_density_factor);
    mp_wp->setMinH(minh_local * mesh_density_factor);

    auto wp_group = gd->getDomainByName("workpiece");

    for (auto ent : wp_group->getEntities())
    {
        mg->setParameters(mp_wp, ent);
        for (auto subent : gd->getEntities())
        {
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_wp, subent);
        }
    }
}

bool WorkpieceTemplate::validate(std::string& msg)
{
    if (wp_type == BILLET)
    {
        if (workpiece_diameter <= 0)
        {
            msg = "Workpiece diameter should be positive number.";
            return false;
        }

        if (workpiece_height <= 0)
        {
            msg = "Workpiece height should be positive number.";
            return false;
        }
    }
    else if (wp_type == TUBE)
    {
        if (diameter2 <= 0)
        {
            msg = "Tube outer diameter should be positive number.";
            return false;
        }

        if (diameter1 <= 0)
        {
            msg = "Tube inner diameter should be positive number.";
            return false;
        }

        if (workpiece_height <= 0)
        {
            msg = "Workpiece height should be positive number.";
            return false;
        }
    }
    else if (wp_type == STEPPED_SHAFT)
    {
        if (diameter2 <= 0  or diameter1 <= 0)
        {
            msg = "Shaft diameter should be positive number.";
            return false;
        }

        if (height1 <= 0 or height2 <= 0)
        {
            msg = "Workpiece height should be positive number.";
            return false;
        }
    }

    return true;
}
