#include "rclcpp/rclcpp.hpp"

#include <vector>

#include <QFileDialog>
#include <QFileInfo>
#include <QString>

#include <QListWidget>
#include <QMenu>
#include <QMouseEvent>
#include <QPixmap>
#include <QStyleFactory>
#include <Qt>

#include <OpenGl_GraphicDriver.hxx>

#include "ampi_project_ui/QCEditor.hpp"

#include <AIS_Shape.hxx>
#include <BRepTools.hxx>
#include <IGESControl_Reader.hxx>
#include <STEPControl_Reader.hxx>
#include <TColStd_HSequenceOfTransient.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Shape.hxx>
#include <V3d_View.hxx>

#include <StdSelect_EdgeFilter.hxx>
#include <StdSelect_FaceFilter.hxx>

#include <BRepAdaptor_Curve.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepTools_WireExplorer.hxx>
#include <ShapeBuild_ReShape.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS_Vertex.hxx>
#include <gp_Circ.hxx>

#include <Aspect_DisplayConnection.hxx>
#include <Aspect_Handle.hxx>

#ifdef WNT
#include <WNT_Window.hxx>
#elif defined(__APPLE__) && !defined(MACOSX_USE_GLX)
#include <Cocoa_Window.hxx>
#else
#undef Bool
#undef CursorShape
#undef None
#undef KeyPress
#undef KeyRelease
#undef FocusIn
#undef FocusOut
#undef FontChange
#undef Expose
#include <Xw_Window.hxx>
#endif

namespace ampi_project_ui {

static Handle(Graphic3d_GraphicDriver) & GetGraphicDriver() {
  static Handle(Graphic3d_GraphicDriver) aGraphicDriver;
  return aGraphicDriver;
}

OccView::OccView(QWidget *parent)
    : QWidget(parent), myXmin(0), myYmin(0), myXmax(0), myYmax(0),
      myCurrentMode(CurAction3d_DynamicRotation),
      myDegenerateModeIsOn(Standard_True) {
  // No Background
  setBackgroundRole(QPalette::NoRole);

  // set focus policy to threat QContextMenuEvent from keyboard
  setFocusPolicy(Qt::StrongFocus);
  setAttribute(Qt::WA_PaintOnScreen);
  setAttribute(Qt::WA_NoSystemBackground);

  // Enable the mouse tracking, by default the mouse tracking is disabled.
  setMouseTracking(true);

  init();
}

void OccView::init() {
  // Create Aspect_DisplayConnection
  Handle(Aspect_DisplayConnection) aDisplayConnection =
      new Aspect_DisplayConnection();

  // Get graphic driver if it exists, otherwise initialise it
  if (GetGraphicDriver().IsNull()) {
    GetGraphicDriver() = new OpenGl_GraphicDriver(aDisplayConnection);
  }

  // Get window handle. This returns something suitable for all platforms.
  WId window_handle = (WId)winId();

// Create appropriate window for platform
#ifdef WNT
  Handle(WNT_Window) wind = new WNT_Window((Aspect_Handle)window_handle);
#elif defined(__APPLE__) && !defined(MACOSX_USE_GLX)
  Handle(Cocoa_Window) wind = new Cocoa_Window((NSView *)window_handle);
#else
  Handle(Xw_Window) wind =
      new Xw_Window(aDisplayConnection, (Window)window_handle);
#endif

  // Create V3dViewer and V3d_View
  myViewer = new V3d_Viewer(GetGraphicDriver(), Standard_ExtString("viewer3d"));

  myView = myViewer->CreateView();

  myView->SetWindow(wind);
  if (!wind->IsMapped())
    wind->Map();

  // Create AISInteractiveContext
  myContext = new AIS_InteractiveContext(myViewer);

  // Set up lights etc
  myViewer->SetDefaultLights();
  myViewer->SetLightOn();

  myView->SetBackgroundColor(Quantity_NOC_BLACK);
  myView->MustBeResized();
  myView->TriedronDisplay(Aspect_TOTP_LEFT_LOWER, Quantity_NOC_GOLD, 0.08,
                          V3d_ZBUFFER);
  myView->SetProj(V3d_TypeOfOrientation_Yup_AxoLeft, Standard_True);

  // myContext->SetDisplayMode(AIS_Shaded, Standard_True);

  // Handle(Prs3d_Drawer) style = new Prs3d_Drawer();
  // style->SetColor(Quantity_NOC_GREEN);
  // myContext->SetSelectionStyle(style);
  // myContext->SetHighlightStyle(style);

  // myContext->DisplayActiveSensitive(myView);
}

const Handle(AIS_InteractiveContext) & OccView::getContext() const {
  return myContext;
}

/*!
Get paint engine for the OpenGL viewer. [ virtual public ]
*/
QPaintEngine *OccView::paintEngine() const { return 0; }

void OccView::paintEvent(QPaintEvent * /*theEvent*/) { myView->Redraw(); }

void OccView::resizeEvent(QResizeEvent * /*theEvent*/) {
  if (!myView.IsNull()) {
    myView->MustBeResized();
  }
}

void OccView::fitAll(void) {
  myView->FitAll();
  myView->ZFitAll();
  myView->Redraw();
}

void OccView::reset(void) { myView->Reset(); }

void OccView::pan(void) { myCurrentMode = CurAction3d_DynamicPanning; }

void OccView::zoom(void) { myCurrentMode = CurAction3d_DynamicZooming; }

void OccView::rotate(void) { myCurrentMode = CurAction3d_DynamicRotation; }

void OccView::mousePressEvent(QMouseEvent *theEvent) {
  if (theEvent->button() == Qt::LeftButton) {
    onLButtonDown((theEvent->buttons() | theEvent->modifiers()),
                  theEvent->pos());
    // } else if (theEvent->button() == Qt::MiddleButton) {
    //   onMButtonDown((theEvent->buttons() | theEvent->modifiers()),
    //                 theEvent->pos());
  } else if (theEvent->button() == Qt::RightButton) {
    onRButtonDown((theEvent->buttons() | theEvent->modifiers()),
                  theEvent->pos());
  }
}

void OccView::mouseReleaseEvent(QMouseEvent *theEvent) {
  if (theEvent->button() == Qt::LeftButton) {
    onLButtonUp(theEvent->buttons() | theEvent->modifiers(), theEvent->pos());
  } else if (theEvent->button() == Qt::MiddleButton) {
    //   onMButtonUp(theEvent->buttons() | theEvent->modifiers(),
    //   theEvent->pos());
    // } else if (theEvent->button() == Qt::RightButton) {
    onRButtonUp(theEvent->buttons() | theEvent->modifiers(), theEvent->pos());
  }
}

void OccView::mouseMoveEvent(QMouseEvent *theEvent) {
  onMouseMove(theEvent->buttons(), theEvent->pos());

  myContext->MoveTo(theEvent->pos().x(), theEvent->pos().y(), myView,
                    Standard_True);

  // myContext->Select();
}

void OccView::wheelEvent(QWheelEvent *theEvent) {
  onMouseWheel(theEvent->buttons(), theEvent->angleDelta().y(),
               theEvent->position().toPoint());
}

void OccView::onLButtonDown(const int /*theFlags*/, const QPoint thePoint) {
  // Save the current mouse coordinate in min.
  myXmin = thePoint.x();
  myYmin = thePoint.y();
  myXmax = thePoint.x();
  myYmax = thePoint.y();
}

void OccView::onRButtonDown(const int /*theFlags*/, const QPoint thePoint) {
  // Save the current mouse coordinate in min.
  myXmin = thePoint.x();
  myYmin = thePoint.y();
  myXmax = thePoint.x();
  myYmax = thePoint.y();

  if (myCurrentMode == CurAction3d_DynamicRotation) {
    myView->StartRotation(thePoint.x(), thePoint.y());
  }
}

// void OccView::onRButtonDown(const int /*theFlags*/, const QPoint
// /*thePoint*/) {

// }

void OccView::onMouseWheel(const int /*theFlags*/, const int theDelta,
                           const QPoint thePoint) {
  Standard_Integer aFactor = 16;

  Standard_Integer aX = thePoint.x();
  Standard_Integer aY = thePoint.y();

  if (theDelta > 0) {
    aX += aFactor;
    aY += aFactor;
  } else {
    aX -= aFactor;
    aY -= aFactor;
  }

  myView->Zoom(thePoint.x(), thePoint.y(), aX, aY);
}

void OccView::addItemInPopup(QMenu * /*theMenu*/) {}

void OccView::popup(const int /*x*/, const int /*y*/) {}

void OccView::onLButtonUp(const int theFlags, const QPoint thePoint) {
  inputEvent(thePoint.x(), thePoint.y());
}

// void OccView::onMButtonUp(const int /*theFlags*/, const QPoint thePoint) {
//   if (thePoint.x() == myXmin && thePoint.y() == myYmin) {
//     panByMiddleButton(thePoint);
//   }
// }

void OccView::onRButtonUp(const int /*theFlags*/, const QPoint thePoint) {
  popup(thePoint.x(), thePoint.y());
}

void OccView::onMouseMove(const int theFlags, const QPoint thePoint) {

  // Right button.
  if (theFlags & Qt::RightButton) {
    switch (myCurrentMode) {
    case CurAction3d_DynamicRotation:
      myView->Rotation(thePoint.x(), thePoint.y());
      break;

    case CurAction3d_DynamicZooming:
      myView->Zoom(myXmin, myYmin, thePoint.x(), thePoint.y());
      break;

    case CurAction3d_DynamicPanning:
      myView->Pan(thePoint.x() - myXmax, myYmax - thePoint.y());
      myXmax = thePoint.x();
      myYmax = thePoint.y();
      break;

    default:
      break;
    }
  }
}

void OccView::dragEvent(const int x, const int y) {
  myContext->Select(myXmin, myYmin, x, y, myView, Standard_True);

  emit selectionChanged();
}

void OccView::inputEvent(const int x, const int y) {
  Q_UNUSED(x);
  Q_UNUSED(y);

  myContext->Select(Standard_True);

  emit selectionChanged();
}

void OccView::panByMiddleButton(const QPoint &thePoint) {
  Standard_Integer aCenterX = 0;
  Standard_Integer aCenterY = 0;

  QSize aSize = size();

  aCenterX = aSize.width() / 2;
  aCenterY = aSize.height() / 2;

  myView->Pan(aCenterX - thePoint.x(), thePoint.y() - aCenterY);
}

QCEditor::QCEditor(QWidget *parent) : QDialog(parent) {

  layout_ = new QHBoxLayout;
  gdt_panel_ = new QVBoxLayout;
  gdt_list_ = new QListWidget();
  gdt_buttons_ = new QHBoxLayout;
  gdt_position_ = new QToolButton();
  gdt_flatness_ = new QToolButton();
  gdt_circularity_ = new QToolButton();

  occ_viewer_ = new OccView(this);

  occ_viewer_->setMinimumSize(500, 250);

  gdt_position_->setIcon(QPixmap(":gdt_svg/GDT_Position.svg"));
  gdt_position_->setIconSize(QSize(25, 25));
  gdt_position_->setToolTip("Position");
  gdt_flatness_->setIcon(QPixmap(":gdt_svg/GDT_Flatness.svg"));
  gdt_flatness_->setIconSize(QSize(25, 25));
  gdt_flatness_->setToolTip("Flatness");
  gdt_circularity_->setIcon(QPixmap(":gdt_svg/GDT_Circularity.svg"));
  gdt_circularity_->setIconSize(QSize(25, 25));
  gdt_circularity_->setToolTip("Circularity");

  gdt_buttons_->addWidget(gdt_position_);
  gdt_buttons_->addWidget(gdt_flatness_);
  gdt_buttons_->addWidget(gdt_circularity_);

  gdt_panel_->addWidget(gdt_list_);
  gdt_panel_->addLayout(gdt_buttons_);

  layout_->addWidget(occ_viewer_);
  layout_->addLayout(gdt_panel_);

  setLayout(layout_);

  LoadModel();
}

void QCEditor::LoadModel() {

  TopoDS_Shape result;

  QString fileName = QFileDialog::getOpenFileName(
      this, tr("Open Model"), "/home",
      tr("Parametric Models (*.iges *.igs *.step *.stp)"));

  QFileInfo file(fileName);

  if (file.suffix() == QString("step") ||
      file.suffix() == QString("stp")) // STEP File
  {
    STEPControl_Reader reader;
    reader.ReadFile(fileName.toStdString().c_str());
    reader.TransferRoots();
    result = reader.OneShape();
  } else // IGES File
  {
    IGESControl_Reader reader;
    reader.ReadFile(fileName.toStdString().c_str());
    Handle(TColStd_HSequenceOfTransient) list = reader.GiveList("iges-faces");
    reader.TransferList(list);
    result = reader.OneShape();
  }
  result.Free(true);
  ShapeBuild_ReShape reshape;
  TopExp_Explorer explorer(result, TopAbs_WIRE);
  while (explorer.More()) {
    BRepTools_WireExplorer wire(TopoDS::Wire(explorer.Current()));
    std::vector<BRepAdaptor_Curve> edges;
    while (wire.More()) {
      edges.push_back(BRepAdaptor_Curve(TopoDS::Edge(wire.Current())));
      wire.Next();
    }
    if (std::all_of(edges.begin(), edges.end(), [&](BRepAdaptor_Curve i) {
          return (i.GetType() == GeomAbs_Circle &&
                  i.Circle().Location().IsEqual(edges[0].Circle().Location(),
                                                1e-6));
        })) {
      if (edges.size() != 1) {
        gp_Circ replacement_circle =
            gp_Circ(edges[0].Circle().Position(), edges[0].Circle().Radius());
        reshape.Replace(explorer.Current(),
                        BRepBuilderAPI_MakeWire(
                            BRepBuilderAPI_MakeEdge(replacement_circle)));
        reshape.Apply(result);
      }
      TopoDS_Vertex vertex =
          BRepBuilderAPI_MakeVertex(edges[0].Circle().Location());
      Handle(AIS_Shape) ais_vertex = new AIS_Shape(vertex);
      occ_viewer_->getContext()->Display(ais_vertex, false);
    }
    explorer.Next();
  }
  model_shape_ = new AIS_Shape(result);
  occ_viewer_->getContext()->Display(model_shape_, false);
  occ_viewer_->fitAll();
  occ_viewer_->getContext()->SetDisplayMode(model_shape_, AIS_Shaded, false);
  Handle(StdSelect_FaceFilter) planeonly =
      new StdSelect_FaceFilter(StdSelect_Plane);
  Handle(StdSelect_EdgeFilter) circleonly =
      new StdSelect_EdgeFilter(StdSelect_Circle);
  // occ_viewer_->getContext()->AddFilter(planeonly);
  occ_viewer_->getContext()->AddFilter(circleonly);
  occ_viewer_->getContext()->Activate(model_shape_,
                                      AIS_Shape::SelectionMode(TopAbs_EDGE));
}

// QWidget *Menu::getParentWindow() { return this; }

void QCEditor::closeEvent(QCloseEvent *event) { QDialog::closeEvent(event); }

} // namespace ampi_project_ui