#include "AperturePatchAntennaTemplate.h"
#include <BRepBuilderAPI_MakeWire.hxx>
#include <GC_MakeSegment.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <TopExp_Explorer.hxx>
#include "mesh/AutoMeshSize.h"
#include "topology/ShapeAlgorithms.h"

AperturePatchAntennaTemplate::AperturePatchAntennaTemplate(std::string path) : BasicTemplate(path)
{
    type = APERTURE_PATCH_ANTENNA;
}

void AperturePatchAntennaTemplate::parseInputJSON(json* j)
{
    json& data = *j;
    name = data["name"].get<std::string>();
    substrate_width = data["properties"]["substrate_width"];
    substrate_length = data["properties"]["substrate_length"];
    patch_width = data["properties"]["patch_width"];
    patch_length = data["properties"]["patch_length"];
    slot_width = data["properties"]["slot_width"];
    slot_length = data["properties"]["slot_length"];
    upper_substrate_height = data["properties"]["upper_substrate_height"];
    lower_substrate_height = data["properties"]["lower_substrate_height"];
    feed_width = data["properties"]["feed_width"];
    feed_length = data["properties"]["feed_length"];

}

void AperturePatchAntennaTemplate::generateGeometry()
{
    if (progress != nullptr)
        progress->sendLog("Generating aperture fed 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);

    // STUB
    TopoDS_Face stub = makeStub();
    TopoEntity_ stub_ent = std::make_shared<TopoEntity>(stub, "stub_entity");
    geom_data->addEntity(stub_ent);

    BoundaryGroup_ stub_group = std::make_shared<BoundaryGroup>();
    stub_group->setName("stub");
    stub_group->setLabel("Trace");
    stub_group->setRole("trace");
    stub_group->addEntity(stub_ent);
    stub_group->update();
    geom_data->addBoundaryGroup(stub_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 antenna_substrate_solid = makeUpperSubstrate();
    TopoEntity_ antenna_substrate_ent = std::make_shared<TopoEntity>(antenna_substrate_solid, "antenna_substrate_entity");
    geom_data->addEntity(antenna_substrate_ent);

    DomainGroup_ antenna_substrate_group = std::make_shared<DomainGroup>();
    antenna_substrate_group->setName("antenna_substrate");
    antenna_substrate_group->setLabel("Upper Dielectric");
    antenna_substrate_group->setRole("dielectric");
    antenna_substrate_group->addEntity(antenna_substrate_ent);
    antenna_substrate_group->update();
    geom_data->addDomainGroup(antenna_substrate_group);

    TopoDS_Solid circuit_substrate_solid = makeLowerSubstrate();
    TopoEntity_ circuit_substrate_ent = std::make_shared<TopoEntity>(circuit_substrate_solid, "circuit_substrate_entity");
    geom_data->addEntity(circuit_substrate_ent);

    DomainGroup_ circuit_substrate_group = std::make_shared<DomainGroup>();
    circuit_substrate_group->setName("circuit_substrate");
    circuit_substrate_group->setLabel("Lower Dielectric");
    circuit_substrate_group->setRole("dielectric");
    circuit_substrate_group->addEntity(circuit_substrate_ent);
    circuit_substrate_group->update();
    geom_data->addDomainGroup(circuit_substrate_group);
}

void AperturePatchAntennaTemplate::assignMeshSizes(MeshGenerator_ mg, GeometryData_ gd, double mesh_density_factor, double global_max)
{
    // Manual meshing part.
    // Will switch to AMS when it is fixed
    // THIS PART IS EMPIRICAL! There is no theory behind element sizes
/*
    std::shared_ptr<MeshParameters> mp = std::make_shared<MeshParameters>(compound);
    double maxh_global = wavelength / 5;
    double minh_global = std::min(slot_width, feed_width);

    // reduce minimal size if it is close to maximal size
    // THIS IS EMPIRICAL!
    if (minh_global * 1.5 > maxh_global)
        minh_global = minh_global / 2;

    mp->setMaxH(maxh_global * mesh_density_factor);
    mp->setMinH(minh_global * mesh_density_factor);
    mg->setParameters(mp);

    */

    //refinement on substrates
    std::shared_ptr<MeshParameters> mp_substrate = std::make_shared<MeshParameters>();
    double maxh_local = std::min({ global_max, slot_width * 2.5, feed_width * 2.5 });
    double minh_local = std::min(upper_substrate_height, lower_substrate_height)*5;

    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);

    auto us_group = gd->getDomainByName("antenna_substrate");
    auto ls_group = gd->getDomainByName("circuit_substrate");

    for (auto ent : us_group->getEntities())
    {
        mg->setParameters(mp_substrate, ent);
        for (auto subent : gd->getEntities())
        {
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_substrate, subent);
        }
    }

    for (auto ent : ls_group->getEntities())
    {
        mg->setParameters(mp_substrate, ent);
        for (auto subent : gd->getEntities())
        {
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_substrate, subent);
        }
    }

    //refinement on stub and feed
    std::shared_ptr<MeshParameters> mp_stub = std::make_shared<MeshParameters>();
    maxh_local = std::min({ global_max, slot_width, feed_width });
    minh_local = std::min(slot_width, feed_width) / 3;

    if (minh_local * 1.5 > maxh_local)
        minh_local = minh_local / 2;

    mp_stub->setMaxH(maxh_local * mesh_density_factor);
    mp_stub->setMinH(minh_local * mesh_density_factor);

    auto tr_group = gd->getBoundaryByName("stub");
    auto f_group = gd->getBoundaryByName("feed");

    for (auto ent : tr_group->getEntities())
    {
        mg->setParameters(mp_stub, ent);
        //assign mp_stub to underlying edges
        for (auto subent : gd->getEntities())
        {
            if (subent->getType() != ShapeType::EDGE)
                continue;
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_stub, subent);
        }
    }

    for (auto ent : f_group->getEntities())
    {
        mg->setParameters(mp_stub, ent);
        //assign mp_stub to underlying edges
        for (auto subent : gd->getEntities())
        {
            if (subent->getType() != ShapeType::EDGE)
                continue;
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_stub, subent);
        }
    }

    //refinement on patch
    std::shared_ptr<MeshParameters> mp_patch = std::make_shared<MeshParameters>();
    maxh_local = std::min(global_max, std::min(patch_width, 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);

    auto p_group = gd->getBoundaryByName("patch");
    for (auto ent : p_group->getEntities())
    {
        mg->setParameters(mp_patch, ent);
        //assign mp_patch to underlying edges
        for (auto subent : geom_data->getEntities())
        {
            if (subent->getType() != ShapeType::EDGE)
                continue;
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_patch, subent);
        }

    }


    //refinement on edges
    std::shared_ptr<MeshParameters> mp_edges = std::make_shared<MeshParameters>();
    maxh_local = std::min(global_max/2, std::min(slot_width, feed_width) / 2);
    minh_local = std::min(slot_width, 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 : p_group->getEntities())
    {
        for (auto subent : gd->getEntities())
        {
            if (subent->getType() != ShapeType::EDGE)
                continue;
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp_edges, subent);
        }
    }

/*
    mg->setStopper(stopper);
    std::shared_ptr<Mesh> mesh = mg->generateMesh();

    std::ofstream meshOutStreamFile;
    meshOutStreamFile.open(project_path + "\\geometry\\meshFile.msh");
    mesh->writeMesh(meshOutStreamFile);
    meshOutStreamFile.close();


    geom_data.setMeshFile(project_path + "\\geometry\\meshFile.msh");
    geom_data.setMeshStats(mesh->getMeshStats());
    */
}

bool AperturePatchAntennaTemplate::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 (upper_substrate_height <= 0)
    {
        msg = "Upper substrate height should be positive number.";
        return false;
    }
    if (lower_substrate_height <= 0)
    {
        msg = "Lower 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 line width should be positive number.";
        return false;
    }
    if (feed_length <= 0)
    {
        msg = "Feed line length 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;
    }

    return true;
}



TopoDS_Face AperturePatchAntennaTemplate::makePatch() 
{
    gp_Pnt patch_pnts[4] = { gp_Pnt(-patch_length / 2.0 , -patch_width / 2.0, upper_substrate_height + lower_substrate_height),
                             gp_Pnt(-patch_length / 2.0, patch_width / 2.0, upper_substrate_height + lower_substrate_height),
                             gp_Pnt(patch_length / 2.0, patch_width / 2.0, upper_substrate_height + lower_substrate_height),
                             gp_Pnt(patch_length / 2.0, -patch_width / 2.0, upper_substrate_height + lower_substrate_height) };
    BRepBuilderAPI_MakeWire patch_wire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) segment = GC_MakeSegment(patch_pnts[i], patch_pnts[i == 3 ? 0 : i + 1]);
        TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(segment);
        patch_wire.Add(edge);
    }
    TopoDS_Face patch = BRepBuilderAPI_MakeFace(patch_wire.Wire());

    for (TopExp_Explorer expEdges(patch, TopAbs_EDGE); expEdges.More(); expEdges.Next())
    {
        edges_for_refinement.Append(expEdges.Current());
    }

    return patch;
}


TopoDS_Face AperturePatchAntennaTemplate::makeFeed() {
    gp_Pnt feed_pnts[4] = { gp_Pnt(-feed_width / 2.0 , -substrate_length / 2.0, 0),
                            gp_Pnt(-feed_width / 2.0, -substrate_length / 2.0, lower_substrate_height),
                            gp_Pnt(feed_width / 2.0, -substrate_length / 2.0, lower_substrate_height),
                            gp_Pnt(feed_width / 2.0, -substrate_length / 2.0, 0) };
    BRepBuilderAPI_MakeWire feed_wire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) segment = GC_MakeSegment(feed_pnts[i], feed_pnts[i == 3 ? 0 : i + 1]);
        TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(segment);
        feed_wire.Add(edge);
    }
    TopoDS_Face feed = BRepBuilderAPI_MakeFace(feed_wire.Wire());
    return feed;
}

TopoDS_Face AperturePatchAntennaTemplate::makeStub() {
    gp_Pnt stub_pnts[4] = { gp_Pnt(-feed_width / 2.0 , -substrate_length / 2.0, 0),
                            gp_Pnt(-feed_width / 2.0, -substrate_length / 2.0 + feed_length, 0),
                            gp_Pnt(feed_width / 2.0, -substrate_length / 2.0 + feed_length, 0),
                            gp_Pnt(feed_width / 2.0, -substrate_length / 2.0, 0) };
    BRepBuilderAPI_MakeWire stub_wire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) segment = GC_MakeSegment(stub_pnts[i], stub_pnts[i == 3 ? 0 : i + 1]);
        TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(segment);
        stub_wire.Add(edge);
    }
    TopoDS_Face stub = BRepBuilderAPI_MakeFace(stub_wire.Wire());
    return stub;
}


TopoDS_Face AperturePatchAntennaTemplate::makeGround() 
{
    gp_Pnt ground_plane_pnts[4] = { gp_Pnt(-substrate_width / 2.0 , -substrate_length / 2.0, lower_substrate_height),
                                    gp_Pnt(-substrate_width / 2.0, substrate_length / 2.0, lower_substrate_height),
                                    gp_Pnt(substrate_width / 2.0, substrate_length / 2.0, lower_substrate_height),
                                    gp_Pnt(substrate_width / 2.0, -substrate_length / 2.0, lower_substrate_height) };
    BRepBuilderAPI_MakeWire ground_plane_wire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) segment = GC_MakeSegment(ground_plane_pnts[i], ground_plane_pnts[i == 3 ? 0 : i + 1]);
        TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(segment);
        ground_plane_wire.Add(edge);
    }

    //reversed order with respect to ground_plane_wire (to have opposite normal and cut hole out)
    gp_Pnt ground_slot_pnts[4] = { gp_Pnt(-slot_length  / 2.0 , -slot_width / 2.0, lower_substrate_height),
                                   gp_Pnt(slot_length / 2.0, -slot_width / 2.0, lower_substrate_height),
                                   gp_Pnt(slot_length / 2.0, slot_width / 2.0, lower_substrate_height),
                                   gp_Pnt(-slot_length / 2.0, slot_width / 2.0, lower_substrate_height) };

    BRepBuilderAPI_MakeWire ground_slot_wire;
    for (int i = 0; i < 4; i++)
    {
        Handle(Geom_TrimmedCurve) segment = GC_MakeSegment(ground_slot_pnts[i], ground_slot_pnts[i == 3 ? 0 : i + 1]);
        TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(segment);
        ground_slot_wire.Add(edge);
    }

    for (TopExp_Explorer expEdges(ground_slot_wire.Wire(), TopAbs_EDGE); expEdges.More(); expEdges.Next())
    {
        edges_for_refinement.Append(expEdges.Current());
    }

    BRepBuilderAPI_MakeFace ground_face(ground_plane_wire.Wire());
    ground_face.Add(ground_slot_wire);
    TopoDS_Shape ground_shape = ground_face.Shape();
    TopoDS_Face ground = TopoDS::Face(TopExp_Explorer(ground_shape, TopAbs_ShapeEnum::TopAbs_FACE).Current()); // there should be only one face in ground
    return ground;
}


TopoDS_Solid AperturePatchAntennaTemplate::makeUpperSubstrate() 
{
    gp_Pnt boxPnt1(-substrate_width / 2.0, -substrate_length / 2.0, lower_substrate_height);
    gp_Pnt boxPnt2(substrate_width / 2.0, substrate_length / 2.0, lower_substrate_height + upper_substrate_height);
    BRepPrimAPI_MakeBox substrate_box(boxPnt1, boxPnt2);
    substrate_box.Build();

    TopoDS_Solid antenna_substrate_solid = substrate_box.Solid();
    return antenna_substrate_solid;
}


TopoDS_Solid AperturePatchAntennaTemplate::makeLowerSubstrate()
{
    gp_Pnt boxPnt1(-substrate_width / 2.0, -substrate_length / 2.0, 0);
    gp_Pnt boxPnt2(substrate_width / 2.0, substrate_length / 2.0, lower_substrate_height);
    BRepPrimAPI_MakeBox substrate_box(boxPnt1, boxPnt2);
    substrate_box.Build();

    TopoDS_Solid circuit_substrate_solid = substrate_box.Solid();
    return circuit_substrate_solid;
}