#include "PatchAntennaTemplate.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_Vertex.hxx>
#include <TopTools_MapOfShape.hxx>
#include <BRepAlgoAPI_BuilderAlgo.hxx>
#include <STEPControl_Writer.hxx>
#include <TopExp_Explorer.hxx>
#include <GProp_GProps.hxx>
#include <BRepGProp.hxx>
#include <BRepPrimAPI_MakeSphere.hxx>
#include <BRepPrimAPI_MakePrism.hxx>
#include <BRepBuilderAPI_MakeSolid.hxx>
#include <BRepBndLib.hxx>
#include <BRepTools.hxx>
#include <map>
#include <filesystem>

#include "topology/ShapeAlgorithms.h"
#include "misc/miscFunctions.hpp"
#include "vtkForCenos/topologyToVTK.h"
#include "mesh/MeshGenerator.h"
#include "mesh/AutoMeshSize.h"


PatchAntennaTemplate::PatchAntennaTemplate(std::string path) : BasicTemplate(path)
{
    type = PATCH_ANTENNA;
}

void PatchAntennaTemplate::parseInputJSON(json* j)
{
    json& data = *j;
    name = data["name"].get<std::string>();
    substrate_width = data["properties"]["substrate_width"];
    substrate_length = data["properties"]["substrate_length"];
    substrate_height = data["properties"]["substrate_height"];
    patch_width = data["properties"]["patch_width"];
    patch_length = data["properties"]["patch_length"];
    feed_width = data["properties"]["feed_width"];
    has_inset = data["properties"]["hasInset"];
    if (has_inset)
    {
        inset_lenght = data["properties"]["inset_lenght"];
        inset_width = data["properties"]["inset_width"];
    }
    else
    {
        inset_lenght = 0;
        inset_width = 0;
    }
}

TopoDS_Face PatchAntennaTemplate::makePatch() 
{
    std::vector<gp_Pnt> points;
    points.push_back(gp_Pnt(patch_width / 2.0, patch_length / 2.0, substrate_height));
    points.push_back(gp_Pnt(-patch_width / 2.0, patch_length / 2.0, substrate_height));
    points.push_back(gp_Pnt(-patch_width / 2.0, -patch_length / 2.0, substrate_height));

    if (has_inset)
    {
        points.push_back(gp_Pnt(-inset_width - feed_width / 2.0, -patch_length / 2.0, substrate_height));
        points.push_back(gp_Pnt(-inset_width - feed_width / 2.0, -patch_length / 2.0 + inset_lenght, substrate_height));
        points.push_back(gp_Pnt(-feed_width / 2.0, -patch_length / 2.0 + inset_lenght, substrate_height));

    }
    else
    {
        points.push_back(gp_Pnt(-feed_width / 2.0, -patch_length / 2.0, substrate_height));
    }
    points.push_back(gp_Pnt(-feed_width / 2.0, -substrate_length / 2.0, substrate_height));
    points.push_back(gp_Pnt(feed_width / 2.0, -substrate_length / 2.0, substrate_height));

    if (has_inset)
    {
        points.push_back(gp_Pnt(feed_width / 2.0, -patch_length / 2.0 + inset_lenght, substrate_height));
        points.push_back(gp_Pnt(inset_width + feed_width / 2.0, -patch_length / 2.0 + inset_lenght, substrate_height));
        points.push_back(gp_Pnt(inset_width + feed_width / 2.0, -patch_length / 2.0, substrate_height));
    }
    else
    {
        points.push_back(gp_Pnt(feed_width / 2.0, -patch_length / 2.0, substrate_height));
    }

    points.push_back(gp_Pnt(patch_width / 2.0, -patch_length / 2.0, substrate_height));

    BRepBuilderAPI_MakeWire aWire;
    for (int i = 0; i < points.size(); i++)
    {
        Handle(Geom_TrimmedCurve) aSegment = GC_MakeSegment(points[i], points[i == points.size()-1 ? 0 : i + 1]);
        TopoDS_Edge anEdge = BRepBuilderAPI_MakeEdge(aSegment);
        aWire.Add(anEdge);
    }

    TopoDS_Face patch = BRepBuilderAPI_MakeFace(aWire.Wire());

    for (TopExp_Explorer expEdges(patch, TopAbs_EDGE); expEdges.More(); expEdges.Next())
    {
        edges_for_refinement.Append(expEdges.Current());
    }

    return patch;
}

TopoDS_Face PatchAntennaTemplate::makeFeed() {
    gp_Pnt fPnt[4] = { gp_Pnt(-feed_width/2.0 , -substrate_length / 2.0, 0),
                    gp_Pnt(-feed_width / 2.0, -substrate_length / 2.0, substrate_height),
                    gp_Pnt(feed_width / 2.0, -substrate_length / 2.0, substrate_height),
                    gp_Pnt(feed_width / 2.0, -substrate_length / 2.0, 0) };
    BRepBuilderAPI_MakeWire fWire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) fSegment = GC_MakeSegment(fPnt[i], fPnt[i == 3 ? 0 : i + 1]);
        TopoDS_Edge fEdge = BRepBuilderAPI_MakeEdge(fSegment);
        fWire.Add(fEdge);
    }
    TopoDS_Face feed = BRepBuilderAPI_MakeFace(fWire.Wire());
    return feed;
}

TopoDS_Solid PatchAntennaTemplate::makeSubstrate() {
    gp_Pnt boxPnt1(-substrate_width / 2.0, -substrate_length / 2.0, 0);
    gp_Pnt boxPnt2(substrate_width / 2.0, substrate_length / 2.0, substrate_height);
    BRepPrimAPI_MakeBox substrate_box(boxPnt1, boxPnt2);
    substrate_box.Build();

    TopoDS_Solid substrate_solid = substrate_box.Solid();
    return substrate_solid;
}

TopoDS_Face PatchAntennaTemplate::makeGround() {
    gp_Pnt aPnt[4] = { gp_Pnt(substrate_width / 2.0, substrate_length / 2.0, 0),
                    gp_Pnt(substrate_width / 2.0, -substrate_length / 2.0, 0),
                    gp_Pnt(-substrate_width / 2.0, -substrate_length / 2.0, 0),
                    gp_Pnt(-substrate_width / 2.0, substrate_length / 2.0, 0) };

    BRepBuilderAPI_MakeWire aWire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) aSegment = GC_MakeSegment(aPnt[i], aPnt[i == 3 ? 0 : i + 1]);
        TopoDS_Edge anEdge = BRepBuilderAPI_MakeEdge(aSegment);
        aWire.Add(anEdge);
    }

    TopoDS_Face ground = BRepBuilderAPI_MakeFace(aWire.Wire());
    return ground;
}

TopoDS_Face PatchAntennaTemplate::makeRectangeFace(double p1_X, double p1_Y, double p1_Z,
                                                    double p2_X, double p2_Y, double p2_Z,
                                                    double p3_X, double p3_Y, double p3_Z,
                                                    double p4_X, double p4_Y, double p4_Z) {
    gp_Pnt aPnt[4] = { gp_Pnt(p1_X, p1_Y, p1_Z),
                    gp_Pnt(p2_X, p2_Y, p2_Z),
                    gp_Pnt(p3_X, p3_Y, p3_Z),
                    gp_Pnt(p4_X, p4_Y, p4_Z) };

    BRepBuilderAPI_MakeWire aWire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) aSegment = GC_MakeSegment(aPnt[i], aPnt[i == 3 ? 0 : i + 1]);
        TopoDS_Edge anEdge = BRepBuilderAPI_MakeEdge(aSegment);
        aWire.Add(anEdge);
    }

    TopoDS_Face ground = BRepBuilderAPI_MakeFace(aWire.Wire());
    return ground;
}


void PatchAntennaTemplate::generateGeometry()
{
    if (progress != nullptr)
        progress->sendLog("Generating patch template geometry ");

    // PATCH
    TopoDS_Face patch = makePatch();
    TopoEntity_ patch_ent = std::make_shared<TopoEntity>(patch, "patch_entity");
    geom_data->addEntity(patch_ent);

    BoundaryGroup_ patch_group = std::make_shared<BoundaryGroup>();
    patch_group->setName("patch");
    patch_group->setLabel("Patch");
    patch_group->setRole("patch");
    patch_group->addEntity(patch_ent);
    patch_group->update();
    geom_data->addBoundaryGroup(patch_group);


    // FEED
    TopoDS_Face feed = makeFeed();
    TopoEntity_ feed_ent = std::make_shared<TopoEntity>(feed, "feed_entity");
    geom_data->addEntity(feed_ent);

    BoundaryGroup_ feed_group = std::make_shared<BoundaryGroup>();
    feed_group->setName("feed");
    feed_group->setLabel("Port");
    feed_group->setRole("feed");
    feed_group->addEntity(feed_ent);
    feed_group->update();
    geom_data->addBoundaryGroup(feed_group);


    // GROUND
    TopoDS_Face ground = makeGround();
    TopoEntity_ ground_ent = std::make_shared<TopoEntity>(ground, "ground_entity");
    geom_data->addEntity(ground_ent);

    BoundaryGroup_ ground_group = std::make_shared<BoundaryGroup>();
    ground_group->setName("ground");
    ground_group->setLabel("Ground");
    ground_group->setRole("ground");
    ground_group->addEntity(ground_ent);
    ground_group->update();
    geom_data->addBoundaryGroup(ground_group);

    TopoDS_Solid substrate_solid = makeSubstrate();
    TopoEntity_ substrate_ent = std::make_shared<TopoEntity>(substrate_solid, "substrate");
    geom_data->addEntity(substrate_ent);

    DomainGroup_ substrate_group = std::make_shared<DomainGroup>();
    substrate_group->setName("substrate");
    substrate_group->setLabel("Substrate");
    substrate_group->setRole("dielectric");
    substrate_group->addEntity(substrate_ent);
    substrate_group->update();
    geom_data->addDomainGroup(substrate_group);
}

void PatchAntennaTemplate::assignMeshSizes(MeshGenerator_ mg, GeometryData_ gd, double mesh_density_factor, double global_max)
{
    // substrate mesh sizing
    std::shared_ptr<MeshParameters> mp_substrate = std::make_shared<MeshParameters>();
    double maxh_local = std::min({ global_max, substrate_length / 10, substrate_width / 10, feed_width });
    double minh_local = std::min(feed_width / 2, substrate_height) / 2;

    if (minh_local * 1.5 > maxh_local)
        minh_local = minh_local / 2;

    mp_substrate->setMaxH(maxh_local * mesh_density_factor);
    mp_substrate->setMinH(minh_local * mesh_density_factor);

    // assign mp_substrate to entities and subentities of substrate
    auto s_group = gd->getDomainByName("substrate");
    for (auto ent : s_group->getEntities())
    {
        mg->setParameters(mp_substrate, ent);
        for (auto subent : gd->getEntities())
        {
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_substrate, subent);
        }
    }

    //patch mesh sizing
    std::shared_ptr<MeshParameters> mp_patch = std::make_shared<MeshParameters>();
    maxh_local = std::min({ global_max, patch_width / 12, patch_length / 12 });
    minh_local = std::min(patch_width, patch_length) / 25;

    if (minh_local * 1.5 > maxh_local)
        minh_local = minh_local / 2;

    mp_patch->setMaxH(maxh_local * mesh_density_factor);
    mp_patch->setMinH(minh_local * mesh_density_factor);

    // assign mp_patch to entities and subentities of patch
    auto b_patch = gd->getBoundaryByName("patch");
    for (auto ent : b_patch->getEntities())
    {
        mg->setParameters(mp_patch, ent);
        //assign mp_patch to underlying edges
        for (auto subent : gd->getEntities())
        {
            if (subent->getType() != ShapeType::EDGE)
                continue;
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_patch, subent);
        }
    }

    //refinement on patch edges
    std::shared_ptr<MeshParameters> mp_edges = std::make_shared<MeshParameters>();
    maxh_local = std::min({ global_max / 2, feed_width }) / 2;
    minh_local = std::min(substrate_height, feed_width) / 4;

    if (minh_local * 1.5 > maxh_local)
        minh_local = minh_local / 2;

    mp_edges->setMaxH(maxh_local * mesh_density_factor);
    mp_edges->setMinH(minh_local * mesh_density_factor);

    for (auto ent : b_patch->getEntities())
    {
        for (auto subent : gd->getEntities())
        {
            if (subent->getType() != ShapeType::EDGE)
                continue;
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_edges, subent);
        }
    }
}

bool PatchAntennaTemplate::validate(std::string& msg)
{

    if (substrate_width <= 0)
    {
        msg = "Substrate width should be positive number.";
        return false;
    }
    if (substrate_length <= 0)
    {
        msg = "Substrate length should be positive number.";
        return false;
    }
    if (substrate_height <= 0)
    {
        msg = "Substrate height should be positive number.";
        return false;
    }
    if (patch_width <= 0)
    {
        msg = "Patch width should be positive number.";
        return false;
    }
    if (patch_length <= 0)
    {
        msg = "Patch length should be positive number.";
        return false;
    }
    if (feed_width <= 0)
    {
        msg = "Feed width should be positive number.";
        return false;
    }

    if (has_inset)
    {
        if (inset_lenght == 0)
        {
            msg = "Inset length should be positive number.";
            return false;
        }
        if (inset_width == 0)
        {
            msg = "Inset width should be positive number.";
            return false;
        }
    }

    if (substrate_width <= patch_width)
    {
        msg = "Substrate width should be larger than patch width.";
        return false;
    }

    if (substrate_length <= patch_length)
    {
        msg = "Substrate length should be larger than patch length.";
        return false;
    }

    if (feed_width + 2 * inset_width >= patch_width)
    {
        msg = "Use narrower feed width!";
        return false;
    }

    if (inset_lenght >= patch_length)
    {
        msg = "Use shorter inset!";
        return false;
    }

    return true;
}


