#include "DipoleAntennaTemplate.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"


namespace nglib {
#include "nglib.h"
}


DipoleAntennaTemplate::DipoleAntennaTemplate(std::string path) : BasicTemplate(path)
{
    type = DIPOLE_ANTENNA;
}

void DipoleAntennaTemplate::parseInputJSON(json* j)
{
    json& data = *j;
    name = data["name"].get<std::string>();
    half_length = data["properties"]["length"].get<double>()/2.0;
    feed_gap = data["properties"]["feed_gap"];
    wire_radius = data["properties"]["radius"].get<double>()/2.0;
}

TopoDS_Face DipoleAntennaTemplate::makeFeed()
{
    //feed - only add to faceMap for recognition
    gp_Pnt fPnt[4] = { gp_Pnt(wire_radius, 0, feed_gap / 2),
                        gp_Pnt(-wire_radius, 0, feed_gap / 2),
                        gp_Pnt(-wire_radius, 0, -feed_gap / 2),
                        gp_Pnt(wire_radius, 0, -feed_gap / 2) };
    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 DipoleAntennaTemplate::makeTopCylinder()
{
    gp_Pnt ptTop = gp_Pnt(0, 0, feed_gap / 2);
    gp_Dir vz = gp_Dir(0, 0, 1);
    gp_Dir vx = gp_Dir(1, 0, 0);

    gp_Ax2 axis_top = gp_Ax2(ptTop, vz, vx);
    TopoDS_Solid top_cylinder = BRepPrimAPI_MakeCylinder(axis_top, wire_radius, half_length - feed_gap / 2);
    return top_cylinder;

}

TopoDS_Solid DipoleAntennaTemplate::makeBottomCylinder()
{
    gp_Pnt ptBot = gp_Pnt(0, 0, -half_length);
    gp_Dir vz = gp_Dir(0, 0, 1);
    gp_Dir vx = gp_Dir(1, 0, 0);

    gp_Ax2 axis_bot = gp_Ax2(ptBot, vz, vx);
    TopoDS_Solid bot_cylinder = BRepPrimAPI_MakeCylinder(axis_bot, wire_radius, half_length - feed_gap / 2);
    return bot_cylinder;
}

TopoDS_Solid DipoleAntennaTemplate::makeFeedCylinder()
{
    gp_Pnt ptGap = gp_Pnt(0, 0, -feed_gap / 2);
    gp_Dir vz = gp_Dir(0, 0, 1);
    gp_Dir vx = gp_Dir(1, 0, 0);

    gp_Ax2 axis_gap = gp_Ax2(ptGap, vz, vx);
    TopoDS_Solid gap_cylinder = BRepPrimAPI_MakeCylinder(axis_gap, wire_radius, feed_gap);
    return gap_cylinder;
}

void DipoleAntennaTemplate::generateGeometry()
{
    if (progress != nullptr)
        progress->sendLog("Generating dipole template geometry ");

    TopoDS_Shape top_cylinder = makeTopCylinder();
    TopoEntity_ top_cylinder_ent = std::make_shared<TopoEntity>(top_cylinder, "top_cylinder_ent");
    top_cylinder_ent->setEnabled(false);
    geom_data->addEntity(top_cylinder_ent);
    //this one is only for cut!


    TopoDS_Shape bot_cylinder = makeBottomCylinder();
    TopoEntity_ bot_cylinder_ent = std::make_shared<TopoEntity>(bot_cylinder, "bot_cylinder_ent");
    bot_cylinder_ent->setEnabled(false);
    geom_data->addEntity(bot_cylinder_ent);
    //this one is only for cut!


    TopoDS_Shape feed_cylinder = makeFeedCylinder();
    TopoEntity_ feed_cylinder_ent = std::make_shared<TopoEntity>(feed_cylinder, "feed_cylinder_ent");
    feed_cylinder_ent->setEnabled(false);
    geom_data->addEntity(feed_cylinder_ent);

    TopTools_ListOfShape pecList;
    // add faces of top cylinder to pecList
    // except bottom surface
    {
        Bnd_Box box;
        box.SetGap(0.0);
        BRepBndLib::Add(top_cylinder, box);
        Standard_Real xmin, ymin, zmin, xmax, ymax, zmax;
        box.Get(xmin, ymin, zmin, xmax, ymax, zmax);
        for (TopExp_Explorer faceExplorer(top_cylinder, TopAbs_ShapeEnum::TopAbs_FACE); faceExplorer.More(); faceExplorer.Next())
        {
            TopoDS_Shape current = faceExplorer.Current();

            // if it is lower surface, skip
            GProp_GProps gprops1;
            BRepGProp::SurfaceProperties(current, gprops1);
            if (abs(gprops1.CentreOfMass().Z() - zmin) < 1e-5)
                continue;

            pecList.Append(current);
        }
    }

    {
        Bnd_Box box;
        box.SetGap(0.0);
        BRepBndLib::Add(bot_cylinder, box);
        Standard_Real xmin, ymin, zmin, xmax, ymax, zmax;
        box.Get(xmin, ymin, zmin, xmax, ymax, zmax);
        for (TopExp_Explorer faceExplorer(bot_cylinder, TopAbs_ShapeEnum::TopAbs_FACE); faceExplorer.More(); faceExplorer.Next())
        {
            TopoDS_Shape current = faceExplorer.Current();

            // if it is upper surface, skip
            GProp_GProps gprops1;
            BRepGProp::SurfaceProperties(current, gprops1);
            if (abs(gprops1.CentreOfMass().Z() - zmax) < 1e-5)
                continue;

            pecList.Append(current);
        }
    }

    BoundaryGroup_ pec_group = std::make_shared<BoundaryGroup>();
    pec_group->setName("wire_surface");
    pec_group->setLabel("Wire Surface");
    pec_group->setRole("wire");

    int sh_nr = 0;
    for (auto sh: pecList)
    {
        std::string ent_name = "pec_face_" + std::to_string(++sh_nr);
        TopoEntity_ ent = std::make_shared<TopoEntity>(sh, ent_name);
        geom_data->addEntity(ent);
        pec_group->addEntity(ent);
    }
    pec_group->update();
    geom_data->addBoundaryGroup(pec_group);


    TopoDS_ListOfShape feedList;
    {
        Bnd_Box box;
        box.SetGap(0.0);
        BRepBndLib::Add(feed_cylinder, box);
        Standard_Real xmin, ymin, zmin, xmax, ymax, zmax;
        box.Get(xmin, ymin, zmin, xmax, ymax, zmax);
        for (TopExp_Explorer faceExplorer(feed_cylinder, TopAbs_ShapeEnum::TopAbs_FACE); faceExplorer.More(); faceExplorer.Next())
        {
            TopoDS_Shape current = faceExplorer.Current();

            // if it is upper surface, skip
            GProp_GProps gprops1;
            BRepGProp::SurfaceProperties(current, gprops1);
            if (abs(gprops1.CentreOfMass().Z() - zmax) < 1e-5)
                continue;

            if (abs(gprops1.CentreOfMass().Z() - zmin) < 1e-5)
                continue;

            feedList.Append(current);
        }
    }

    BoundaryGroup_ feed_group = std::make_shared<BoundaryGroup>();
    feed_group->setName("feed");
    feed_group->setLabel("Port");
    feed_group->setRole("feed");

    sh_nr = 0;
    for (auto sh : feedList)
    {
        std::string ent_name = "feed_face_" + std::to_string(++sh_nr);
        TopoEntity_ ent = std::make_shared<TopoEntity>(sh, ent_name);
        geom_data->addEntity(ent);
        feed_group->addEntity(ent);
    }
    feed_group->update();
    geom_data->addBoundaryGroup(feed_group);

}

void DipoleAntennaTemplate::assignMeshSizes(MeshGenerator_ mg, GeometryData_ gd, double mesh_density_factor, double global_max)
{   
    // Manual meshing part. Automatic meshing works inconsistently for dipole antenna template.
    // Will switch back to AMS when it is fixed
    // THIS PART IS EMPIRICAL! There is no theory behind element sizes

    double maxh_global = global_max;
    double minh_global = std::min(wire_radius, feed_gap);

    //refinement on faces
    std::shared_ptr<MeshParameters> mp1 = std::make_shared<MeshParameters>();
    double maxh_local = std::min(global_max/2, wire_radius);
    double minh_local = std::min(wire_radius, feed_gap)/2;

    if (minh_local * 1.5 > maxh_local)
        minh_local = minh_local / 2;

    mp1->setMaxH(maxh_local * mesh_density_factor);
    mp1->setMinH(minh_local * mesh_density_factor);

    // set mp1 for all wire and feed entities
    auto feed_group = gd->getBoundaryByName("feed");
    auto wire_group = gd->getBoundaryByName("wire_surface");

    for (auto ent : feed_group->getEntities())
    {
        mg->setParameters(mp1, ent);
        for (auto subent : gd->getEntities())
        {
            if (subent->getType() != ShapeType::EDGE)
                continue;
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp1, subent);
        }
    }

    for (auto ent : wire_group->getEntities())
    {
        mg->setParameters(mp1, ent);
        for (auto subent : gd->getEntities())
        {
            if (subent->getType() != ShapeType::EDGE)
                continue;
            if (subent->isSubShapeOf(ent))
                mg->setParameters(mp1, subent);
        }
    }

}

bool DipoleAntennaTemplate::validate(std::string& msg)
{

    const int MAX_RATIO = 350;
    int ratio = (int)(((2 * half_length) / wire_radius) / 2);

    if (ratio > MAX_RATIO)
    {
        msg = "The diameter to length ratio is too large(1:" + std::to_string(ratio) + ").Please do not exceed 1:" + std::to_string(MAX_RATIO);
        return false;
    }


    if (half_length <= 0 )
    {
        msg = "Wire length should be positive number.";
        return false;
    }

    if (feed_gap <= 0)
    {
        msg = "Feed gap should be positive number.";
        return false;
    }

    if (wire_radius <= 0)
    {
        msg = "Wire radius should be positive number.";
        return false;
    }

    if (2*half_length <= feed_gap)
    {
        msg = "Use shorter gap!";
        return false;
    }

    return true;
}
