#include "config.h"
#include "config_mesh_3.h"

#ifdef CGAL_LAB_DEMO_USE_SURFACE_MESHER
#include <CGAL/Three/CGAL_Lab_plugin_interface.h>
#include <CGAL/Three/Three.h>
#include <CGAL/Three/Scene_group_item.h>
#include "Messages_interface.h"

#include <QObject>
#include <QAction>
#include <QApplication>
#include <QtPlugin>
#include "Scene_c3t3_item.h"
#include <QInputDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QDesktopServices>
#include <QUrl>
#include <QVariant>
#include <fstream>

#include <gsl/pointers>

// Small addition from GSL v2.0.0:
template <class T>
auto make_not_null(T&& t) {
    return gsl::not_null<std::remove_cv_t<std::remove_reference_t<T>>>{std::forward<T>(t)};
}

#include <boost/variant/variant.hpp>
#include <optional>
#include "Scene_polylines_item.h"

#ifdef CGAL_MESH_3_DEMO_ACTIVATE_IMPLICIT_FUNCTIONS
#include "Scene_implicit_function_item.h"
#endif
#ifdef CGAL_MESH_3_DEMO_ACTIVATE_SEGMENTED_IMAGES
#include "Scene_image_item.h"
#include "Image_type.h"
#ifdef CGAL_USE_ITK
#include <CGAL/Mesh_3/generate_label_weights.h>
#endif

#endif

#include "Meshing_thread.h"

#include "ui_Meshing_dialog.h"

using namespace CGAL::Three;

#include "Mesh_3_plugin_cgal_code.h" // declare functions `cgal_code_mesh_3`
#include "split_polylines.h"
#include <CGAL/Mesh_facet_topology.h>

enum Protection { BORDERS = 1, FEATURES = 2 };
using Protection_flags = QFlags<Protection>;
Q_DECLARE_METATYPE(Protection_flags)

class Mesh_3_plugin :
  public QObject,
  protected CGAL_Lab_plugin_interface
{
  Q_OBJECT
  Q_INTERFACES(CGAL::Three::CGAL_Lab_plugin_interface)
  Q_PLUGIN_METADATA(IID "com.geometryfactory.CGALLab.PluginInterface/1.0" FILE "mesh_3_plugin.json")

  Q_PROPERTY(double angle READ get_angle WRITE set_angle)
  Q_PROPERTY(double sharp_edges_angle_bound
             READ get_sharp_edges_angle_bound
             WRITE set_sharp_edges_angle_bound)
  Q_PROPERTY(double edges_sizing READ get_edges_sizing WRITE set_edges_sizing)
  Q_PROPERTY(double edges_min_sizing READ get_edges_min_sizing WRITE set_edges_min_sizing)
  Q_PROPERTY(double edges_approx READ get_edges_approx WRITE set_edges_approx)
  Q_PROPERTY(double facets_sizing READ get_facets_sizing WRITE set_facets_sizing)
  Q_PROPERTY(double approx READ get_approx WRITE set_approx)
  Q_PROPERTY(double tets_sizing READ get_tets_sizing WRITE set_tets_sizing)
  Q_PROPERTY(double tets_shape READ get_tets_shape WRITE set_tets_shape)
  Q_PROPERTY(bool protect_features READ get_protect_features WRITE set_protect_features)
  Q_PROPERTY(bool protect_borders READ get_protect_borders WRITE set_protect_borders)
  Q_PROPERTY(bool manifold_criterion READ get_manifold_criterion WRITE set_manifold_criterion)

  typedef CGAL::Mesh_facet_topology Mesh_facet_topology;
  Q_ENUMS(Mesh_facet_topology)
  Q_PROPERTY(Mesh_facet_topology facet_topology
             READ get_facet_topology
             WRITE set_facet_topology)

public:
  void init(QMainWindow* mainWindow,
            CGAL::Three::Scene_interface* scene_interface,
            Messages_interface* msg_interface)
  {
    this->scene = scene_interface;
    this->mw = mainWindow;

    actionMesh_3 = new QAction("Create a Tetrahedral Mesh", mw);
    if(actionMesh_3) {
      actionMesh_3->setProperty("subMenuName", "Tetrahedral Mesh Generation");
      connect(actionMesh_3, SIGNAL(triggered()),
              this, SLOT(mesh_3_volume()));
    }

    actionMesh_3_surface = new QAction("Create a Surface Triangle Mesh", mw);
    if (actionMesh_3_surface){
      actionMesh_3_surface->setProperty("subMenuName", "Tetrahedral Mesh Generation");
      connect(actionMesh_3_surface, SIGNAL(triggered()),
              this, SLOT(mesh_3_surface()));
    }
    actionSplitPolylines = new QAction("Build Features Graph for Mesh_3", mw);
    actionSplitPolylines->setProperty("subMenuName",
                                      "Tetrahedral Mesh Generation");
    connect(actionSplitPolylines, &QAction::triggered,
            this, &Mesh_3_plugin::splitPolylines);

    this->msg = msg_interface;
  }

  QList<QAction*> actions() const {
    return QList<QAction*>()
      << actionMesh_3
      << actionMesh_3_surface
      << actionSplitPolylines;
  }

  bool applicable(QAction* a) const {
    if(a == actionSplitPolylines) {
      return qobject_cast<Scene_polylines_item*>
        (scene->item(scene->mainSelectionIndex())) != nullptr;
    }
    return !get_items_or_return_error_string();
  }

public Q_SLOTS:
  std::optional<QString> get_items_or_return_error_string() const;
  void set_defaults();
  void mesh_3_volume();
  void mesh_3_surface();
  void mesh_3_surface_with_defaults() {
    mesh_3(Mesh_type::SURFACE_ONLY, Dialog_choice::NO_DIALOG);
  }
  void mesh_3_volume_with_defaults() {
    mesh_3(Mesh_type::VOLUME, Dialog_choice::NO_DIALOG);
  }
  void mesh_3(bool with_dialog) { // compatibility with old Qt Scripts
    return mesh_3(
        Mesh_type::VOLUME,
        with_dialog ? Dialog_choice::DIALOG : Dialog_choice::NO_DIALOG);
  }
  void splitPolylines();
  void meshing_done(Meshing_thread* t);
  void status_report(QString str);

public Q_SLOTS:
  void set_angle(const double v) { angle = v; };
  void set_sharp_edges_angle_bound(const double v) {
    sharp_edges_angle_bound = v;
  }
  void set_edges_sizing(const double v) { edges_sizing = v; };
  void set_edges_min_sizing(const double v) { edges_min_sizing = v; };
  void set_edges_approx(const double v) { edges_approx = v; };
  void set_facets_sizing(const double v) { facets_sizing = v; };
  void set_approx(const double v) { approx = v; };
  void set_tets_sizing(const double v) { tets_sizing = v; };
  void set_tets_shape(const double v) { tets_shape = v; };
  void set_manifold_criterion(const bool v) { manifold_criterion = v; }
  void set_facet_topology(const CGAL::Mesh_facet_topology v) {  facet_topology = v; }
  void set_protect_features(const bool v) { protect_features = v; };
  void set_protect_borders(const bool v) { protect_borders = v; };

  double get_angle() { return angle; };
  double get_sharp_edges_angle_bound() { return sharp_edges_angle_bound; }
  double get_edges_sizing() { return edges_sizing; };
  double get_edges_min_sizing() { return edges_min_sizing; };
  double get_edges_approx() { return edges_approx; };
  double get_facets_sizing() { return facets_sizing; };
  double get_approx() { return approx; };
  double get_tets_sizing() { return tets_sizing; };
  double get_tets_shape() { return tets_shape; };
  bool get_manifold_criterion() { return manifold_criterion; };
  CGAL::Mesh_facet_topology get_facet_topology() { return facet_topology; };
  bool get_protect_features() { return protect_features; };
  bool get_protect_borders() { return protect_borders; };


private:
  enum class Mesh_type : bool { VOLUME, SURFACE_ONLY };
  enum class Dialog_choice : bool { NO_DIALOG, DIALOG };
  // This is a helper class for an interface row.
  //  A row is composed of a label, a widget.
  //  It can also have a minimum, maximum and default value.
  //  It can also have a list of checkboxes that enable/disable the row.
  class Setup_ui_row : public QObject
  {
    QWidget* label_;
    QWidget* edit_;
    std::vector<QAbstractButton*> toggles_;

    void connect_and_update_ui_row()
    {
      for( QAbstractButton* toggle : toggles_ )
      {
        connect(toggle, &QAbstractButton::toggled, this, &Setup_ui_row::update);
        connect(toggle, &QAbstractButton::toggled, this, &Setup_ui_row::update);
      }
      update();
    }
  public:
    Setup_ui_row(QWidget* label, DoubleEdit* edit, double minimum, double maximum, double value, std::initializer_list<QAbstractButton*> toggles)
      : label_(label), edit_(edit), toggles_(toggles)
    {
      edit->setRange(minimum, maximum);
      edit->setValue(value);
      connect_and_update_ui_row();
    }
    Setup_ui_row(QWidget* label, QWidget* edit, std::initializer_list<QAbstractButton*> toggles)
    : label_(label), edit_(edit), toggles_(toggles)
    {
      connect_and_update_ui_row();
    }

    void update()
    {
      bool should_be_enabled = true;
      for( QAbstractButton* toggle : toggles_ )
      {
        should_be_enabled = should_be_enabled & toggle->isChecked();
        if (!should_be_enabled)
          break;
      }
      label_->setEnabled(should_be_enabled);
      edit_ ->setEnabled(should_be_enabled);
    }
  };

  void mesh_3(const Mesh_type mesh_type, const Dialog_choice dialog = Dialog_choice::DIALOG);
  void launch_thread(Meshing_thread* mesh_thread);
  void treat_result(Scene_item& source_item, Scene_c3t3_item* result_item) const;

private:
  QAction* actionMesh_3;
  QAction* actionMesh_3_surface;
  QAction* actionSplitPolylines;
  Messages_interface* msg;
  QMessageBox* message_box_;
  Scene_item* source_item_;
  QString source_item_name_;
  CGAL::Three::Scene_interface* scene;
  QMainWindow* mw;
  bool as_facegraph;

  double angle;
  double sharp_edges_angle_bound;
  int sizing_decimals;
  double approx;
  int approx_decimals;
  double edges_sizing;
  double edges_min_sizing;
  double edges_approx;
  double facets_sizing;
  double facets_min_sizing;
  double tets_sizing;
  double tets_min_sizing;
  double tets_shape;
  bool manifold_criterion;
  CGAL::Mesh_facet_topology facet_topology;
  bool protect_features;
  bool protect_borders;

  struct Polyhedral_mesh_items {
    Polyhedral_mesh_items() noexcept
      : sm_items{}, bounding_sm_item(nullptr), polylines_item(nullptr) {}
    QList<gsl::not_null<Scene_surface_mesh_item*>> sm_items;
    Scene_surface_mesh_item* bounding_sm_item;
    Scene_polylines_item* polylines_item;
    QList<std::pair<QVariant, QVariant>> incident_subdomains;
  };
  struct Image_mesh_items {
    Image_mesh_items(gsl::not_null<Scene_image_item*> ptr) : image_item(ptr) {}
    gsl::not_null<Scene_image_item*> image_item;
    Scene_polylines_item* polylines_item = nullptr;
  };
  struct Implicit_mesh_items {
    gsl::not_null<Scene_implicit_function_item*> function_item;
  };
  enum Item_types {
    POLYHEDRAL_MESH_ITEMS,
    IMAGE_MESH_ITEMS,
    IMPLICIT_MESH_ITEMS
  };
  mutable std::optional<std::variant<Polyhedral_mesh_items,
                                         Image_mesh_items,
                                         Implicit_mesh_items>>
      items;
  mutable bool features_protection_available = false;
  mutable Scene_item* item = nullptr;
  mutable CGAL::Three::Scene_interface::Bbox bbox = {};
}; // end class Mesh_3_plugin

double
get_approximate(double d, int precision, int& decimals)
{
    if ( d<0 ) { return 0; }

    double i = std::pow(10.,precision-1);

    decimals = 0;
    while ( d > i*10 ) { d = d/10.; ++decimals; }
    while ( d < i ) { d = d*10.; --decimals; }

    return std::floor(d)*std::pow(10.,decimals);
}

void Mesh_3_plugin::splitPolylines() {
  Scene_item* main_item = scene->item(scene->mainSelectionIndex());
  Scene_polylines_item* polylines_item =
    qobject_cast<Scene_polylines_item*>(main_item);
  if(polylines_item == 0) return;

  Scene_polylines_item* new_item = new Scene_polylines_item;
  auto new_polylines = split_polylines(polylines_item->polylines);
  new_item->polylines =
    Polylines_container{new_polylines.begin(), new_polylines.end()};
  new_item->setName(tr("%1 (split)").arg(polylines_item->name()));
  scene->addItem(new_item);
}

void Mesh_3_plugin::mesh_3_surface()
{
  mesh_3(Mesh_type::SURFACE_ONLY);
}
void Mesh_3_plugin::mesh_3_volume()
{
  mesh_3(Mesh_type::VOLUME);
}

std::optional<QString> Mesh_3_plugin::get_items_or_return_error_string() const
{
  items = {};
  features_protection_available = false;
  item = nullptr;
  Scene_polylines_item* polylines_item = nullptr;

  for (int ind : scene->selectionIndices())
  {
    try {
      if (auto sm_item =
              qobject_cast<Scene_surface_mesh_item*>(scene->item(ind))) {
        if (!items) items = Polyhedral_mesh_items{};
        auto& poly_items = get<Polyhedral_mesh_items>(*items);
        auto& sm_items = poly_items.sm_items;
        sm_items.push_back(make_not_null(sm_item));
        if (is_closed(*sm_item->polyhedron())) {
          poly_items.bounding_sm_item = sm_item;
        }
      }
#  ifdef CGAL_MESH_3_DEMO_ACTIVATE_IMPLICIT_FUNCTIONS
      else if (auto function_item = qobject_cast<Scene_implicit_function_item*>(
                   scene->item(ind))) {
        if (!items)
          items = Implicit_mesh_items{make_not_null(function_item)};
        else
          return tr(
              "An implicit function cannot be mixed with other items type");
      }
#  endif
#  ifdef CGAL_MESH_3_DEMO_ACTIVATE_SEGMENTED_IMAGES
      else if (auto image_item =
                   qobject_cast<Scene_image_item*>(scene->item(ind))) {
        if (!items)
          items = Image_mesh_items{make_not_null(image_item)};
        else
          return tr("An image items cannot be mixed with other items type");
      }
#  endif
      else if ((polylines_item =
                qobject_cast<Scene_polylines_item*>(scene->item(ind))))
      {
        if (!items)
          continue;
        auto poly_items_ptr = std::get_if<Polyhedral_mesh_items>(&items.value());
        if(poly_items_ptr) {
          if (poly_items_ptr->polylines_item) {
            return tr("Only one polyline item is accepted");
          } else {
            poly_items_ptr->polylines_item = polylines_item;
          }
        }
        else {
          if(auto image_items_ptr = std::get_if<Image_mesh_items>(&items.value()))
          {
            if (image_items_ptr->polylines_item) {
              return tr("Only one polyline item is accepted");
            }
            else {
              image_items_ptr->polylines_item = polylines_item;
            }
          }
        }
      }
      else if (nullptr !=
        qobject_cast<CGAL::Three::Scene_group_item*>(scene->item(ind))) {
        continue;
      }
      else {
        return tr("Wrong selection of items");
      }
    } catch (const boost::bad_get&) { return tr("Wrong selection of items"); }
  } // end for loop on selected items

  //attach polylines_item to one or the other item
  //if it could not be done in the for loop
  //because of selection order
  if (polylines_item != nullptr && items != std::nullopt)
  {
    auto poly_items_ptr = std::get_if<Polyhedral_mesh_items>(&items.value());
    auto image_items_ptr = std::get_if<Image_mesh_items>(&items.value());
    if(poly_items_ptr != nullptr)
      poly_items_ptr->polylines_item = polylines_item;
    else if(image_items_ptr != nullptr )
      image_items_ptr->polylines_item = polylines_item;
  }

  if (!items) { return tr("Selected objects can't be meshed"); }
  item = nullptr;
  features_protection_available = false;
  if (auto poly_items = std::get_if<Polyhedral_mesh_items>(&items.value())) {
    auto& sm_items = poly_items->sm_items;
    if(sm_items.empty()) {
      return tr("ERROR: there must be at least one surface mesh item.");
    }
    for (auto sm_item : sm_items) {
      if (nullptr == sm_item->polyhedron()) {
        return tr("ERROR: no data in selected item %1").arg(sm_item->name());
      }
      if (!is_triangle_mesh(*sm_item->polyhedron())) {
        return tr("Selected Scene_surface_mesh_item %1 is not triangulated.")
            .arg(sm_item->name());
      }
      if (sm_item->getNbIsolatedvertices() != 0) {
        return tr("ERROR: there are isolated vertices in this mesh.");
      }
    }
    if (!sm_items.empty()) item = sm_items.front();
    features_protection_available = true;
  }
#  ifdef CGAL_MESH_3_DEMO_ACTIVATE_IMPLICIT_FUNCTIONS
  else if (auto implicit_mesh_items = std::get_if<Implicit_mesh_items>(&items.value())) {
    item = implicit_mesh_items->function_item;
  }
#  endif
#  ifdef CGAL_MESH_3_DEMO_ACTIVATE_SEGMENTED_IMAGES
  else if (auto image_mesh_items = std::get_if<Image_mesh_items>(&items.value())) {
    auto& image_item = image_mesh_items->image_item;
    item = image_item;
    features_protection_available = true;
  }
#  endif

  if(item) {
    bbox = item->bbox();
    if (auto poly_items = std::get_if<Polyhedral_mesh_items>(&items.value())) {
      for (auto it : poly_items->sm_items) {
        bbox = bbox + it->bbox();
      }
      if (poly_items->polylines_item)
        bbox = bbox + poly_items->polylines_item->bbox();
    }
  }
  return {};
}

void Mesh_3_plugin::set_defaults() {
  auto error = get_items_or_return_error_string();
  if(error) return;
  double diag = CGAL::sqrt((bbox.xmax()-bbox.xmin())*(bbox.xmax()-bbox.xmin()) + (bbox.ymax()-bbox.ymin())*(bbox.ymax()-bbox.ymin()) + (bbox.zmax()-bbox.zmin())*(bbox.zmax()-bbox.zmin()));
  double default_sizing = get_approximate(diag * 0.05,  2, sizing_decimals);
  double default_approx = get_approximate(diag * 0.005, 2, approx_decimals);
  //edge parameters
  sharp_edges_angle_bound = 60.;
  edges_sizing = default_sizing;
  edges_min_sizing = 0.1 * default_sizing;
  edges_approx = default_approx;
  //triangle parameters
  approx = default_approx;
  facets_sizing = default_sizing;
  facets_min_sizing = 0.1 * default_sizing;
  angle = 25.;
  //tetrahedra parameters
  tets_sizing = default_sizing;
  tets_min_sizing = 0.1 * default_sizing;
  tets_shape = 3.0;
}

void Mesh_3_plugin::mesh_3(const Mesh_type mesh_type,
                           const Dialog_choice dialog_choice) {
  CGAL_assertion(static_cast<bool>(items));
  auto error_string = get_items_or_return_error_string();
  if (error_string) {
    QApplication::restoreOverrideCursor();
    QMessageBox::warning(mw, tr("Mesh_3 plugin"), *error_string);
    return;
  }

  const bool more_than_one_item =
      std::get_if<Polyhedral_mesh_items>(&items.value()) &&
      (std::get_if<Polyhedral_mesh_items>(&items.value())->sm_items.size() > 1);

  Scene_image_item* image_item =
      std::get_if<Image_mesh_items>(&items.value())
          ? std::get_if<Image_mesh_items>(&items.value())->image_item.get()
          : nullptr;
  Scene_surface_mesh_item* bounding_sm_item =
      std::get_if<Polyhedral_mesh_items>(&items.value())
          ? std::get_if<Polyhedral_mesh_items>(&items.value())->bounding_sm_item
          : nullptr;
  Scene_polylines_item* polylines_item =
      std::get_if<Polyhedral_mesh_items>(&items.value())
          ? std::get_if<Polyhedral_mesh_items>(&items.value())->polylines_item
          : nullptr;
  if (polylines_item == nullptr && std::get_if<Image_mesh_items>(&items.value()) != nullptr)
    polylines_item = std::get_if<Image_mesh_items>(&items.value())->polylines_item;
  Scene_implicit_function_item* function_item =
      std::get_if<Implicit_mesh_items>(&items.value())
          ? std::get_if<Implicit_mesh_items>(&items.value())->function_item.get()
          : nullptr;
  // -----------------------------------
  // Create Mesh dialog
  // -----------------------------------
  QDialog dialog(mw);
  Ui::Meshing_dialog ui;
  ui.setupUi(&dialog);

  QString item_name =
      more_than_one_item ? QString("%1...").arg(item->name()) : item->name();

  ui.objectName->setText(item_name);
  ui.objectNameSize->setText(tr("Object bbox size (w,h,d):  <b>%1</b>,  <b>%2</b>,  <b>%3</b>")
                             .arg(bbox.xmax() - bbox.xmin(),0,'g',3)
                             .arg(bbox.ymax() - bbox.ymin(),0,'g',3)
                             .arg(bbox.zmax() - bbox.zmin(),0,'g',3) );

  const bool input_is_labeled_img = (image_item != nullptr && !image_item->isGray());
  const bool input_is_gray_img = (image_item != nullptr && image_item->isGray());

  set_defaults();
  double diag = CGAL::sqrt((bbox.xmax()-bbox.xmin())*(bbox.xmax()-bbox.xmin()) + (bbox.ymax()-bbox.ymin())*(bbox.ymax()-bbox.ymin()) + (bbox.zmax()-bbox.zmin())*(bbox.zmax()-bbox.zmin()));

  // Setup fields :
  // Set minimum, maximum and default values
  // Connect checkboxes to spinboxes
  double default_max =  std::numeric_limits<double>::infinity();

  //edge parameters
  std::vector<Setup_ui_row> v;
  Setup_ui_row s0(ui.protectLabel,         ui.protectEdges,                                                        {ui.protect});
  Setup_ui_row s1(ui.sharpEdgesAngleLabel, ui.sharpEdgesAngle, 0.0         , 180,         sharp_edges_angle_bound, {ui.protect});
  Setup_ui_row s2(ui.edgeLabel,            ui.edgeSizing,      0.0         , default_max, edges_sizing,            {ui.protect, ui.noEdgeSizing});
  Setup_ui_row s3(ui.edgeMinSizingLabel,   ui.edgeMinSizing,   0.0         , default_max, edges_min_sizing,        {ui.protect, ui.noEdgeMinSizing});
  Setup_ui_row s4(ui.edgeApproxLabel,      ui.edgeApprox,      diag * 10e-7, diag,        edges_approx,            {ui.protect, ui.noEdgeApprox});
  //connect protect edge checkbox
  connect(ui.protect, SIGNAL(toggled(bool)), ui.noEdgeSizing, SLOT(setEnabled(bool)));
  connect(ui.protect, SIGNAL(toggled(bool)), ui.noEdgeMinSizing, SLOT(setEnabled(bool)));
  connect(ui.protect, SIGNAL(toggled(bool)), ui.noEdgeApprox, SLOT(setEnabled(bool)));

  //triangle parameters
  Setup_ui_row s5(ui.approxLabel,    ui.approx,         diag * 10e-7, diag, approx,            {ui.noApprox});
  Setup_ui_row s6(ui.sizingLabel,    ui.facetSizing,    diag * 10e-6, diag, facets_sizing,     {ui.noFacetSizing});
  Setup_ui_row s7(ui.sizingMinLabel, ui.facetMinSizing, diag * 10e-6, diag, facets_min_sizing, {ui.noFacetMinSizing});
  Setup_ui_row s8(ui.angleLabel,     ui.facetAngle,     0.0,          30.0, angle,             {ui.noAngle});

  //tetrahedra parameters
  Setup_ui_row s9(ui.tetSizingLabel,    ui.tetSizing,    diag * 10e-6, diag,        tets_sizing,     {ui.noTetSizing});
  Setup_ui_row sA(ui.tetMinSizingLabel, ui.tetMinSizing, diag * 10e-6, diag,        tets_min_sizing, {ui.noTetMinSizing});
  Setup_ui_row sB(ui.tetShapeLabel,     ui.tetShape,     1.0,          default_max, tets_shape,      {ui.noTetShape});

  //gray image parameters
  Setup_ui_row sC(ui.label_3, ui.iso_value_spinBox, -65536.0, 65536.0, 0.0, {});


  // Setup domain specific parameters and groups
  ui.advanced->setVisible(false);
  connect(ui.facetTopologyLabel,
          &QLabel::linkActivated,
          &QDesktopServices::openUrl);

  ui.approx->setToolTip(tr("Approximation error: in [%1; %2]")
                       .arg(diag * 10e-7).arg(diag));

  ui.protect->setEnabled(features_protection_available);
  ui.protect->setChecked(features_protection_available);

  ui.facegraphCheckBox->setVisible(mesh_type == Mesh_type::SURFACE_ONLY);
  ui.initializationGroup->setVisible(input_is_labeled_img || input_is_gray_img);
  ui.grayImgGroup->setVisible(input_is_gray_img);

  if(input_is_gray_img)
    ui.sharpFeaturesGroup->setEnabled(false);

  if (items->index() == POLYHEDRAL_MESH_ITEMS)
    ui.volumeGroup->setVisible(mesh_type == Mesh_type::VOLUME &&
                               nullptr != bounding_sm_item);
  else
    ui.volumeGroup->setVisible(mesh_type == Mesh_type::VOLUME);
  if (items->index() != POLYHEDRAL_MESH_ITEMS || polylines_item != nullptr) {
    ui.sharpEdgesAngleLabel->setVisible(false);
    ui.sharpEdgesAngle->setVisible(false);

    ui.facetTopology->setEnabled(false);
    ui.facetTopology->setToolTip(
        tr("<b>Notice:</b> "
           "This option is only available with a"
           " polyhedron or a surface mesh, when features are detected"
           " automatically"));
  }
  ui.noEdgeSizing->setChecked(ui.protect->isChecked());
  ui.noEdgeMinSizing->setChecked(false);
  ui.noEdgeApprox->setChecked(ui.protect->isChecked());

  dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint |
                        Qt::WindowCloseButtonHint);
  connect(ui.buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
  connect(ui.buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));

  using Item = std::pair<QString, Protection_flags>;
  const Item sharp_and_boundary{"Sharp and Boundary edges", FEATURES};
  const Item boundary_only{"Boundary edges only", BORDERS};
  const Item sharp_edges{"Sharp edges", FEATURES};
  const Item input_polylines{"Input polylines only", Protection_flags{}};
  const Item on_cube{"Polylines on cube", BORDERS};
  const Item triple_lines{"Triple+ lines", FEATURES};
  if (features_protection_available) {
    if (items->index() == POLYHEDRAL_MESH_ITEMS) {
      auto v = [](Protection_flags f) { return QVariant::fromValue(f); };
      if (mesh_type == Mesh_type::SURFACE_ONLY) {
        ui.protectEdges->addItem(sharp_and_boundary.first, v(sharp_and_boundary.second));
        ui.protectEdges->addItem(boundary_only.first, v(boundary_only.second));
      } else
        ui.protectEdges->addItem(sharp_edges.first, v(sharp_edges.second));
      if (polylines_item != nullptr)
        ui.protectEdges->addItem(input_polylines.first, v(input_polylines.second));
    } else if (items->index() == IMAGE_MESH_ITEMS) {
      if (polylines_item != nullptr) {
        ui.protectEdges->addItem(input_polylines.first, QVariant::fromValue(input_polylines.second));
        ui.protectEdges->addItem(QString(on_cube.first).append(" and input polylines"),
                                 QVariant::fromValue(on_cube.second));
        ui.protectEdges->addItem(QString(triple_lines.first).append(" and input polylines"),
                                 QVariant::fromValue(triple_lines.second));
      }
      else {
        ui.protectEdges->addItem(on_cube.first, QVariant::fromValue(on_cube.second));
        ui.protectEdges->addItem(triple_lines.first, QVariant::fromValue(triple_lines.second));
      }
    }
  }

  //Labeled (weighted) image
  connect(ui.useWeights_checkbox, SIGNAL(toggled(bool)),
          ui.weightsSigma, SLOT(setEnabled(bool)));
  connect(ui.useWeights_checkbox, SIGNAL(toggled(bool)),
          ui.weightsSigma_label, SLOT(setEnabled(bool)));
  ui.labeledImgGroup->setVisible(input_is_labeled_img);
  if(image_item != nullptr && input_is_labeled_img)
    ui.weightsSigma->setValue(image_item->default_sigma_weights());

#ifndef CGAL_USE_ITK
  if (input_is_labeled_img)
  {
    ui.labeledImgGroup->setDisabled(true);
    ui.labeledImgGroup->setToolTip(
      QString("The use of weighted images is disabled "
        "because the Insight Toolkit (ITK) is not available."));
    ui.useWeights_checkbox->setDisabled(true);
    ui.weightsSigma_label->setDisabled(true);
    ui.weightsSigma->setDisabled(true);
  }
#endif

  // -----------------------------------
  // Get values
  // -----------------------------------

  // reset cursor from the code for the scripts
  QApplication::restoreOverrideCursor();
  if (dialog_choice == Dialog_choice::DIALOG) {
    int i = dialog.exec();
    if (i == QDialog::Rejected) { return; }
  }

  // 0 means parameter is not considered
  angle = !ui.noAngle->isChecked() ? 0 : ui.facetAngle->value();
  sharp_edges_angle_bound = ui.sharpEdgesAngle->value();
  std::cerr << "sharp_edges_angle_bound: " << sharp_edges_angle_bound << '\n';
  edges_sizing =
      !ui.noEdgeSizing->isChecked() ? DBL_MAX : ui.edgeSizing->value();
  edges_min_sizing =
      !ui.noEdgeMinSizing->isChecked() ? 0. : ui.edgeMinSizing->value();
  edges_approx =
      !ui.noEdgeApprox->isChecked() ? DBL_MAX : ui.edgeApprox->value();
  facets_sizing = !ui.noFacetSizing->isChecked() ? 0 : ui.facetSizing->value();
  facets_min_sizing = !ui.noFacetMinSizing->isChecked() ? 0 : ui.facetMinSizing->value();
  approx = !ui.noApprox->isChecked() ? 0 : ui.approx->value();
  tets_shape = !ui.noTetShape->isChecked() ? 0 : ui.tetShape->value();
  tets_sizing = !ui.noTetSizing->isChecked() ? 0 : ui.tetSizing->value();
  tets_min_sizing = !ui.noTetMinSizing->isChecked() ? 0 : ui.tetMinSizing->value();

  const auto pe_flags = ui.protectEdges->currentData().value<Protection_flags>();
  protect_borders = ui.protect->isChecked() && pe_flags.testFlag(BORDERS);
  protect_features = ui.protect->isChecked() && pe_flags.testFlag(FEATURES);
  const bool protect_polylines = ui.protect->isChecked() && polylines_item != nullptr;

  const bool detect_connected_components = ui.detectComponents->isChecked();
  const int manifold = (ui.manifoldCheckBox->isChecked() ? 1 : 0) +
                       (ui.facetTopology->isChecked() ? 2 : 0);
  const float iso_value = float(ui.iso_value_spinBox->value());
  const float value_outside = float(ui.value_outside_spinBox->value());
  const bool inside_is_less = ui.inside_is_less_checkBox->isChecked();
  const float sigma_weights = ui.useWeights_checkbox->isChecked()
                            ? ui.weightsSigma->value() : 0.f;

  as_facegraph = (mesh_type == Mesh_type::SURFACE_ONLY)
                     ? ui.facegraphCheckBox->isChecked()
                     : false;

  Meshing_thread* thread = nullptr;
  switch (items->index()) {
  case POLYHEDRAL_MESH_ITEMS: {
    auto& poly_items = get<Polyhedral_mesh_items>(*items);
    auto& sm_items = poly_items.sm_items;
    const auto bounding_sm_item = poly_items.bounding_sm_item;
    const auto polylines_item = poly_items.polylines_item;
    QList<const SMesh*> polyhedrons;
    QList<std::pair<int, int> > incident_sub;

    bool material_ids_valid = true;
    for (auto sm_item : sm_items)
    {
      if(!sm_item->property("inner material id").isValid()
         || !sm_item->property("outer material id").isValid())
      {
        material_ids_valid = false;
        break;
      }
      else
      {
        incident_sub.append(std::make_pair<int, int>(
            sm_item->property("inner material id").toInt(),
            sm_item->property("outer material id").toInt()));
      }
    }

    if(mesh_type != Mesh_type::SURFACE_ONLY
      && !material_ids_valid
      && bounding_sm_item != nullptr)
    {
      sm_items.removeAll(make_not_null(bounding_sm_item));
    }

    Scene_polylines_item::Polylines_container polylines_empty_container;
    SMesh* bounding_polyhedron = (bounding_sm_item == nullptr)
                                     ? nullptr
                                     : bounding_sm_item->polyhedron();

    std::transform(sm_items.begin(), sm_items.end(),
      std::back_inserter(polyhedrons),
      [](Scene_surface_mesh_item* item) {
        return item->polyhedron();
      });

    if(!incident_sub.empty())
    {
      thread = cgal_code_mesh_3(
        polyhedrons,
        incident_sub,
        item_name,
        angle,
        facets_sizing,
        facets_min_sizing,
        approx,
        tets_sizing,
        tets_min_sizing,
        edges_sizing,
        edges_min_sizing,
        edges_approx,
        tets_shape,
        protect_features,
        protect_borders,
        sharp_edges_angle_bound,
        manifold,
        mesh_type == Mesh_type::SURFACE_ONLY);
    }
    else
    {
      thread = cgal_code_mesh_3(
        polyhedrons,
        protect_polylines ? polylines_item->polylines : polylines_empty_container,
        bounding_polyhedron,
        item_name,
        angle,
        facets_sizing,
        facets_min_sizing,
        approx,
        tets_sizing,
        tets_min_sizing,
        edges_sizing,
        edges_min_sizing,
        edges_approx,
        tets_shape,
        protect_features,
        protect_borders,
        sharp_edges_angle_bound,
        manifold,
        mesh_type == Mesh_type::SURFACE_ONLY);
    }
    break;
  }//end case POLYHEDRAL_MESH_ITEMS
  // Implicit functions
#  ifdef CGAL_MESH_3_DEMO_ACTIVATE_IMPLICIT_FUNCTIONS
  case IMPLICIT_MESH_ITEMS: {
    const Implicit_function_interface* pFunction = function_item->function();
    if (nullptr == pFunction) {
      QMessageBox::critical(mw, tr(""), tr("ERROR: no data in selected item"));
      return;
    }

    thread = cgal_code_mesh_3(pFunction,
                              angle,
                              facets_sizing,
                              facets_min_sizing,
                              approx,
                              tets_sizing,
                              tets_min_sizing,
                              edges_sizing,
                              edges_min_sizing,
                              edges_approx,
                              tets_shape,
                              manifold,
                              mesh_type == Mesh_type::SURFACE_ONLY);
    break;
  }//end case IMPLICIT_MESH_ITEMS
#  endif
  // Images
#  ifdef CGAL_MESH_3_DEMO_ACTIVATE_SEGMENTED_IMAGES
  case IMAGE_MESH_ITEMS: {
    const Image* pImage = image_item->image();
    auto& image_items = get<Image_mesh_items>(*items);
    const auto img_polylines_item = image_items.polylines_item;

    if (nullptr == pImage) {
      QMessageBox::critical(mw, tr(""), tr("ERROR: no data in selected item"));
      return;
    }
#ifdef CGAL_USE_ITK
    if ( sigma_weights > 0
      && sigma_weights != image_item->sigma_weights())
    {
      CGAL::Image_3 weights = CGAL::Mesh_3::generate_label_weights(*pImage, sigma_weights);
      image_item->set_image_weights(weights, sigma_weights);
    }
#endif
    Image* pWeights = sigma_weights > 0
      ? image_item->image_weights()
      : nullptr;

    Scene_polylines_item::Polylines_container polylines_empty_container;

    thread = cgal_code_mesh_3(
        pImage,
        (img_polylines_item == nullptr) ? polylines_empty_container : img_polylines_item->polylines,
        angle,
        facets_sizing,
        facets_min_sizing,
        approx,
        tets_sizing,
        tets_min_sizing,
        edges_sizing,
        edges_min_sizing,
        edges_approx,
        tets_shape,
        protect_features,
        protect_borders,
        manifold,
        mesh_type == Mesh_type::SURFACE_ONLY,
        detect_connected_components,
        image_item->isGray(),
        iso_value,
        value_outside,
        inside_is_less,
        pWeights);
    break;
  }
  default:
    CGAL::Three::Three::error(tr("Mesh_3 plugin"),
                              tr("This type of item is not handled!"));
    return;
  } // end switch
#  endif

  if (nullptr == thread) {
    QMessageBox::critical(mw, tr(""), tr("ERROR: no thread created"));
    return;
  }

  // Launch thread
  source_item_ = item;
  source_item_name_ = item_name;
  CGAL::Three::Three::getMutex()->lock();
  CGAL::Three::Three::isLocked() = true;
  CGAL::Three::Three::getMutex()->unlock();

  launch_thread(thread);

  QApplication::restoreOverrideCursor();
}

void
Mesh_3_plugin::
launch_thread(Meshing_thread* mesh_thread)
{
  // -----------------------------------
  // Create message box with stop button
  // -----------------------------------
  message_box_ = new QMessageBox(QMessageBox::NoIcon,
                                 "Meshing",
                                 "Mesh generation in progress...",
                                 QMessageBox::Cancel,
                                 mw);

  message_box_->setDefaultButton(QMessageBox::Cancel);
  QAbstractButton* cancelButton = message_box_->button(QMessageBox::Cancel);
  cancelButton->setText(tr("Stop"));

  QObject::connect(cancelButton, &QAbstractButton::clicked,
                   this, [mesh_thread](){
    mesh_thread->stop();
    mesh_thread->wait();
    QApplication::restoreOverrideCursor(); // restores cursor set in mesh_thread stop() function
  });

  message_box_->open();

  // -----------------------------------
  // Connect main thread to meshing thread
  // -----------------------------------
  QObject::connect(mesh_thread, SIGNAL(done(Meshing_thread*)),
                   this,        SLOT(meshing_done(Meshing_thread*)));

  QObject::connect(mesh_thread, SIGNAL(status_report(QString)),
                   this,        SLOT(status_report(QString)));

  // -----------------------------------
  // Launch mesher
  // -----------------------------------
  mesh_thread->start();
}


void
Mesh_3_plugin::
status_report(QString str)
{
  if ( nullptr == message_box_ ) { return; }

  message_box_->setInformativeText(str);
}


void
Mesh_3_plugin::
meshing_done(Meshing_thread* thread)
{
  // Print message in console
  QString str = QString("Meshing of \"%1\" done in %2s<br>")
    .arg(source_item_name_)
    .arg(thread->time());

  for( QString param : thread->parameters_log() )
  {
    str.append(QString("( %1 )<br>").arg(param));
  }

  Scene_c3t3_item* result_item = thread->item();
  const Scene_item::Bbox& bbox = result_item->bbox();
  str.append(QString("BBox (x,y,z): [ %1, %2 ], [ %3, %4 ], [ %5, %6 ], <br>")
    .arg(bbox.xmin())
    .arg(bbox.xmax())
    .arg(bbox.ymin())
    .arg(bbox.ymax())
    .arg(bbox.zmin())
    .arg(bbox.zmax()));

  CGAL::Three::Three::information(qPrintable(str));

  // Treat new c3t3 item
  treat_result(*source_item_, result_item);

  // close message box
  message_box_->done(0);
  message_box_ = nullptr;

  // free memory
  // TODO: maybe there is another way to do that
  delete thread;
}


void
Mesh_3_plugin::
treat_result(Scene_item& source_item,
             Scene_c3t3_item* result_item) const
{
  if(!as_facegraph)
  {
    result_item->setName(tr("%1 [3D Mesh]").arg(source_item_name_));

    result_item->c3t3_changed();

    const Scene_item::Bbox& bbox = result_item->bbox();
    result_item->setPosition(float((bbox.xmin() + bbox.xmax())/2.f),
                            float((bbox.ymin() + bbox.ymax())/2.f),
                            float((bbox.zmin() + bbox.zmax())/2.f));

    bool input_is_labeled_img = dynamic_cast<Scene_image_item*>(&source_item) != nullptr;
    result_item->setUseSubdomainColors(input_is_labeled_img);
    result_item->setColor(source_item.color());
    result_item->setRenderingMode(source_item.renderingMode());
    result_item->set_data_item(&source_item);

    for(int ind : scene->selectionIndices()) {
      scene->item(ind)->setVisible(false);
    }
    const Scene_interface::Item_id index = scene->mainSelectionIndex();
    scene->itemChanged(index);
    scene->setSelectedItem(-1);
    Scene_interface::Item_id new_item_id = scene->addItem(result_item);
    scene->setSelectedItem(new_item_id);
  }
  else
  {
    Scene_surface_mesh_item* new_item = new Scene_surface_mesh_item;
    CGAL::facets_in_complex_3_to_triangle_mesh(result_item->c3t3(), *new_item->face_graph());
    new_item->setName(tr("%1 [Remeshed]").arg(source_item_name_));
    for(int ind : scene->selectionIndices()) {
      scene->item(ind)->setVisible(false);
    }
    const Scene_interface::Item_id index = scene->mainSelectionIndex();
    scene->itemChanged(index);
    scene->setSelectedItem(-1);
    Scene_interface::Item_id new_item_id = scene->addItem(new_item);
    new_item->invalidateOpenGLBuffers();
    new_item->redraw();
    scene->setSelectedItem(new_item_id);
    delete result_item;
  }
  CGAL::Three::Three::getMutex()->lock();
  CGAL::Three::Three::isLocked() = false;
  CGAL::Three::Three::getMutex()->unlock();
}

#include "Mesh_3_plugin.moc"

#endif // CGAL_LAB_DEMO_USE_SURFACE_MESHER
