#include <QtCore/qglobal.h>
#include <QFileDialog>
#include <QColorDialog> 
#include <CGAL/Qt/manipulatedCameraFrame.h>
#include <CGAL/Qt/manipulatedFrame.h>

#include <fstream>


#include "Messages_interface.h"
#include "Scene_points_with_normal_item.h"
#include "Item_classification_base.h"
#include "Point_set_item_classification.h"
#include "Cluster_classification.h"
#include "Scene_surface_mesh_item.h"
#include "Surface_mesh_item_classification.h"
#include "Scene_polylines_item.h"
#include "Scene_polygon_soup_item.h"

#include <CGAL/Three/Scene_interface.h>
#include <CGAL/Three/Polyhedron_demo_plugin_helper.h>

#include <CGAL/Random.h>
#include <CGAL/Real_timer.h>

#include <QMultipleInputDialog.h>

#include "ui_Classification_widget.h"
#include "ui_Classification_advanced_widget.h"

#include <QAction>
#include <QMainWindow>
#include <QApplication>
#include <QCheckBox>
#include <QInputDialog>
#include <QMessageBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QSlider>

#include <map>

#include <boost/graph/adjacency_list.hpp>
#include <CGAL/boost/graph/split_graph_into_polylines.h>

using namespace CGAL::Three;

class Polyhedron_demo_classification_plugin :
  public QObject,
  public Polyhedron_demo_plugin_helper
{
  Q_OBJECT
    Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface)
    Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0")
    
  struct LabelButton
  {
    QPushButton* color_button;
    QMenu* menu;
    char shortcut;
    
    QColor color;

    QLabel* label2;
    QComboBox* effect;

    LabelButton (QWidget* parent,
                 const char* name,
                 const QColor& color,
                 const char shortcut)
      : shortcut (shortcut), color (color)
    {
      color_button = new QPushButton (tr("%1 (%2)").arg(name).arg((char)(std::toupper(shortcut))), parent);
      
      menu = new QMenu("Label Menu", color_button);

      QColor text_color (255, 255, 255);
      if (color.red() * 0.299 + color.green() * 0.587 + color.blue() * 0.114 > 128)
        text_color = QColor (0, 0, 0);
      
      QString s("QPushButton { font-weight: bold; background: #"
                + QString(color.red() < 16? "0" : "") + QString::number(color.red(),16)
                + QString(color.green() < 16? "0" : "") + QString::number(color.green(),16)
                + QString(color.blue() < 16? "0" : "") + QString::number(color.blue(),16)
                + "; color: #"
                + QString(text_color.red() < 16? "0" : "") + QString::number(text_color.red(),16)
                + QString(text_color.green() < 16? "0" : "") + QString::number(text_color.green(),16)
                + QString(text_color.blue() < 16? "0" : "") + QString::number(text_color.blue(),16)
                + "; }");

      color_button->setStyleSheet(s);
      color_button->setMenu(menu);
      
      
      label2 = new QLabel (name, parent);
      effect = new QComboBox;
      effect->addItem("Penalized");
      effect->addItem("Neutral");
      effect->addItem("Favored");
    }
    ~LabelButton ()
    {
    }
    void change_color (const QColor& color)
    {
      this->color = color;
      QColor text_color (255, 255, 255);
      if (color.red() * 0.299 + color.green() * 0.587 + color.blue() * 0.114 > 128)
        text_color = QColor (0, 0, 0);
      QString s("QPushButton { font-weight: bold; background: #"
                + QString(color.red() < 16? "0" : "") + QString::number(color.red(),16)
                + QString(color.green() < 16? "0" : "") + QString::number(color.green(),16)
                + QString(color.blue() < 16? "0" : "") + QString::number(color.blue(),16)
                + "; color: #"
                + QString(text_color.red() < 16? "0" : "") + QString::number(text_color.red(),16)
                + QString(text_color.green() < 16? "0" : "") + QString::number(text_color.green(),16)
                + QString(text_color.blue() < 16? "0" : "") + QString::number(text_color.blue(),16)
                + "; }");

      color_button->setStyleSheet(s);
      color_button->update();
    }
  };


public:
  bool applicable(QAction*) const { 
      return
        qobject_cast<Scene_points_with_normal_item*>(scene->item(scene->mainSelectionIndex()))
        || qobject_cast<Scene_surface_mesh_item*>(scene->item(scene->mainSelectionIndex()));
  }
  void print_message(QString message) { messages->information(message); }
  QList<QAction*> actions() const { return QList<QAction*>() << actionClassification; }
  
  using Polyhedron_demo_plugin_helper::init;
  void init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface* m) {

    mw = mainWindow;
    scene = scene_interface;
    messages = m;
    actionClassification = new QAction(tr("Classification"), mw);
    connect(actionClassification, SIGNAL(triggered()), this, SLOT(classification_action()));

    dock_widget = new QDockWidget("Classification", mw);
    dock_widget->setVisible(false);
    dock_widget_adv = new QDockWidget("Classification (Advanced)", mw);
    dock_widget_adv->setVisible(false);

    label_button = new QPushButton(QIcon(QString(":/cgal/icons/plus")), "", dock_widget);

    QMenu* label_menu = new QMenu("Label Menu", label_button);
    label_button->setMenu (label_menu);

    QAction* add_new_label = label_menu->addAction ("Add new label(s)");
    connect(add_new_label,  SIGNAL(triggered()), this,
            SLOT(on_add_new_label_clicked()));

    label_menu->addSeparator();
    
    QAction* use_config_building = label_menu->addAction ("Use configuration ground/vegetation/building");
    connect(use_config_building,  SIGNAL(triggered()), this,
            SLOT(on_use_config_building_clicked()));
    QAction* use_config_roof = label_menu->addAction ("Use configuration ground/vegetation/roof/facade");
    connect(use_config_roof,  SIGNAL(triggered()), this,
            SLOT(on_use_config_roof_clicked()));
    QAction* use_config_las = label_menu->addAction ("Use LAS standard configuration");
    connect(use_config_las,  SIGNAL(triggered()), this,
            SLOT(on_use_las_config_clicked()));

    label_menu->addSeparator();

    
    QAction* generate = label_menu->addAction ("Create one point set item per label");
    connect(generate,  SIGNAL(triggered()), this,
            SLOT(on_generate_items_button_clicked()));

    label_menu->addSeparator();
        
    QAction* clear_labels = label_menu->addAction ("Clear labels");
    connect(clear_labels,  SIGNAL(triggered()), this,
            SLOT(on_clear_labels_clicked()));
    
    ui_widget.setupUi(dock_widget);
    ui_widget_adv.setupUi(dock_widget_adv);
    addDockWidget(dock_widget);
    addDockWidget(dock_widget_adv);

#ifdef CGAL_LINKED_WITH_OPENCV
    ui_widget.classifier->addItem (tr("Random Forest (OpenCV %1.%2)")
                                   .arg(CV_MAJOR_VERSION)
                                   .arg(CV_MINOR_VERSION));
#endif

    color_att = QColor (75, 75, 77);

    ui_widget.menu->setMenu (new QMenu("Classification Menu", ui_widget.menu));

    connect(ui_widget.classifier,  SIGNAL(currentIndexChanged(int)), this,
            SLOT(on_classifier_changed(int)));
    
    QAction* compute_features = ui_widget.menu->menu()->addAction ("Compute features");
    connect(compute_features,  SIGNAL(triggered()), this,
            SLOT(on_compute_features_button_clicked()));

    ui_widget.menu->menu()->addSection ("Training");

    action_train = ui_widget.menu->menu()->addAction ("Train classifier");
    action_train->setShortcut(Qt::SHIFT | Qt::Key_T);
    connect(action_train,  SIGNAL(triggered()), this,
            SLOT(on_train_clicked()));
    
    action_reset_local = ui_widget.menu->menu()->addAction ("Reset training set of selection");
    connect(action_reset_local,  SIGNAL(triggered()), this,
            SLOT(on_reset_training_set_of_selection_clicked()));
    
    action_reset = ui_widget.menu->menu()->addAction ("Reset all training sets");
    connect(action_reset,  SIGNAL(triggered()), this,
            SLOT(on_reset_training_sets_clicked()));

    action_random_region = ui_widget.menu->menu()->addAction ("Select random region");
    action_random_region->setShortcut(Qt::SHIFT | Qt::Key_S);
    connect(action_random_region,  SIGNAL(triggered()), this,
            SLOT(on_select_random_region_clicked()));

    action_validate = ui_widget.menu->menu()->addAction ("Validate labels of current selection as training sets");
    connect(action_validate,  SIGNAL(triggered()), this,
            SLOT(on_validate_selection_clicked()));

    action_save_config = ui_widget.menu->menu()->addAction ("Save classifier's current configuration");
    action_load_config = ui_widget.menu->menu()->addAction ("Load configuration for classifier");
    connect(action_save_config,  SIGNAL(triggered()), this,
            SLOT(on_save_config_button_clicked()));
    connect(action_load_config,  SIGNAL(triggered()), this,
            SLOT(on_load_config_button_clicked()));

    ui_widget.menu->menu()->addSection ("Algorithms");

    action_run = ui_widget.menu->menu()->addAction ("Classification");
    connect(action_run,  SIGNAL(triggered()), this,
            SLOT(on_run_button_clicked()));

    action_run_smoothed = ui_widget.menu->menu()->addAction ("Classification with local smoothing");
    connect(action_run_smoothed,  SIGNAL(triggered()), this,
            SLOT(on_run_smoothed_button_clicked()));

    action_run_graphcut = ui_widget.menu->menu()->addAction ("Classification with Graph Cut");
    connect(action_run_graphcut,  SIGNAL(triggered()), this,
            SLOT(on_run_graphcut_button_clicked()));

    ui_widget.menu->menu()->addSeparator();

    QAction* close = ui_widget.menu->menu()->addAction ("Close");
    connect(close,  SIGNAL(triggered()), this,
            SLOT(ask_for_closing()));

    connect(ui_widget.display,  SIGNAL(currentIndexChanged(int)), this,
            SLOT(on_display_button_clicked(int)));

    connect(ui_widget_adv.selected_feature,  SIGNAL(currentIndexChanged(int)), this,
            SLOT(on_selected_feature_changed(int)));
    connect(ui_widget_adv.feature_weight,  SIGNAL(valueChanged(int)), this,
            SLOT(on_feature_weight_changed(int)));

    QObject* scene_obj = dynamic_cast<QObject*>(scene_interface);
    if(scene_obj)
    {
      connect(scene_obj, SIGNAL(itemAboutToBeDestroyed(CGAL::Three::Scene_item*)), this,
              SLOT(item_about_to_be_destroyed(CGAL::Three::Scene_item*)));
        
      connect(scene_obj, SIGNAL(itemIndexSelected(int)), this,
              SLOT(update_plugin(int)));
    }
  }
  virtual void closure()
  {
    dock_widget->hide();
    close_classification();
  }


public Q_SLOTS:
  
  void item_about_to_be_destroyed(CGAL::Three::Scene_item* scene_item) {
    Item_map::iterator it = item_map.find(scene_item);
    if (it != item_map.end())
      {
        Item_classification_base* classif = it->second;
        item_map.erase(it); // first erase from map, because scene->erase will cause a call to this function
        delete classif;
      }
  }

  void classification_action()
  { 
    dock_widget->show();
    dock_widget->raise();
    if (Scene_points_with_normal_item* points_item
             = qobject_cast<Scene_points_with_normal_item*>(scene->item(scene->mainSelectionIndex())))
      create_from_item(points_item);
    else if (Scene_surface_mesh_item* mesh_item
             = qobject_cast<Scene_surface_mesh_item*>(scene->item(scene->mainSelectionIndex())))
      create_from_item(mesh_item);
  }


  void ask_for_closing()
  {
    QMessageBox oknotok;
    oknotok.setWindowTitle("Closing classification");
    oknotok.setText("All computed data structures will be discarded.\nColored display will be reinitialized.\nLabels and training information will remain in the classified items.\n\nAre you sure you want to close?");
    oknotok.setStandardButtons(QMessageBox::Yes);
    oknotok.addButton(QMessageBox::No);
    oknotok.setDefaultButton(QMessageBox::Yes);

    if (oknotok.exec() == QMessageBox::Yes)
      close_classification();
  }
  
  void close_classification()
  {
    for (Item_map::iterator it = item_map.begin(); it != item_map.end(); ++ it)
      {
        Item_classification_base* classif = it->second;
        item_changed (classif->item());
        delete classif;
      }
    item_map.clear();
    dock_widget->hide();
  }

  void item_changed (Scene_item* item)
  {
    scene->itemChanged(item);
    item->invalidateOpenGLBuffers();
  }

  void update_plugin(int i)
  {
    if (dock_widget->isVisible())
      update_plugin_from_item(get_classification(scene->item(i)));
  }

  void disable_everything ()
  {
    ui_widget.menu->setEnabled(false);
    ui_widget.display->setEnabled(false);
    ui_widget.classifier->setEnabled(false);
    ui_widget.frame->setEnabled(false);
  }

  void enable_computation()
  {
    ui_widget.menu->setEnabled(true);
    action_train->setEnabled(false);
    action_reset_local->setEnabled(false);
    action_reset->setEnabled(false);
    action_random_region->setEnabled(false);
    action_validate->setEnabled(false);
    action_save_config->setEnabled(false);
    action_load_config->setEnabled(false);
    action_run->setEnabled(false);
    action_run_smoothed->setEnabled(false);
    action_run_graphcut->setEnabled(false);
    ui_widget.display->setEnabled(true);
    ui_widget.classifier->setEnabled(true);
  }

  void enable_classif()
  {
    action_train->setEnabled(true);
    action_reset_local->setEnabled(true);
    action_reset->setEnabled(true);
    action_random_region->setEnabled(true);
    action_validate->setEnabled(true);
    action_save_config->setEnabled(true);
    action_load_config->setEnabled(true);
    action_run->setEnabled(true);
    action_run_smoothed->setEnabled(true);
    action_run_graphcut->setEnabled(true);
    ui_widget.frame->setEnabled(true);
  }

  void update_plugin_from_item(Item_classification_base* classif)
  {
    disable_everything();
    if (classif != NULL)
      {
        enable_computation();
        
        // Clear class labels
        for (std::size_t i = 0; i < label_buttons.size(); ++ i)
          {
            ui_widget.labelGrid->removeWidget (label_buttons[i].color_button);
            label_buttons[i].color_button->deleteLater();
            label_buttons[i].menu->deleteLater();
            ui_widget_adv.gridLayout->removeWidget (label_buttons[i].label2);
            delete label_buttons[i].label2;
            ui_widget_adv.gridLayout->removeWidget (label_buttons[i].effect);
            delete label_buttons[i].effect;
          }
        label_buttons.clear();

        // Add labels
        for (std::size_t i = 0; i < classif->number_of_labels(); ++ i)
          add_new_label (LabelButton (dock_widget,
                                      classif->label(i)->name().c_str(),
                                      classif->label_color(i),
                                      get_shortcut (i, classif->label(i)->name().c_str())));
        add_label_button();
        
        // Enabled classif if features computed
        if (classif->features_computed())
          enable_classif();

        int index = ui_widget.display->currentIndex();
        ui_widget.display->clear();
        ui_widget.display->addItem("Real colors");
        ui_widget.display->addItem("Classification");
        ui_widget.display->addItem("Training sets");
        ui_widget_adv.selected_feature->clear();
        classif->fill_display_combo_box(ui_widget.display, ui_widget_adv.selected_feature);
        if (index >= ui_widget.display->count())
          ui_widget.display->setCurrentIndex(1);
        else
          ui_widget.display->setCurrentIndex(index);
        ui_widget_adv.selected_feature->setCurrentIndex(0);
      }
  }

  Item_classification_base* get_classification(Scene_item* item = NULL)
  {
    if (!item)
      item = scene->item(scene->mainSelectionIndex());

    if (!item)
      return NULL;

    Scene_polyhedron_selection_item* selection_item
      = qobject_cast<Scene_polyhedron_selection_item*>(item);
    if (selection_item)
      item = selection_item->polyhedron_item();
    
    Item_map::iterator it = item_map.find(item);

    if (it != item_map.end())
    {
      if (selection_item)
        dynamic_cast<Surface_mesh_item_classification*>(it->second)->set_selection_item(selection_item);
      return it->second;
    }
    else if (Scene_points_with_normal_item* points_item
             = qobject_cast<Scene_points_with_normal_item*>(item))
      return create_from_item(points_item);
    else if (Scene_surface_mesh_item* mesh_item
             = qobject_cast<Scene_surface_mesh_item*>(item))
      return create_from_item(mesh_item);

    return NULL;
  }
  

  Item_classification_base* create_from_item(Scene_points_with_normal_item* points_item)
  {
    if (item_map.find(points_item) != item_map.end())
      return item_map[points_item];

    bool use_clusters = false;
    
    if (points_item->point_set()->has_property_map<int> ("shape"))
    {
      QMessageBox::StandardButton reply
        = QMessageBox::question(NULL, "Point Set Classification",
                                "This point set is divided in clusters. Do you want to classify clusters instead of points?",
                                QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes);
      
      use_clusters = (reply == QMessageBox::Yes);
    }

    QApplication::setOverrideCursor(Qt::WaitCursor);

    Item_classification_base* classif;
    if (use_clusters)
      classif = new Cluster_classification (points_item);
    else
      classif = new Point_set_item_classification (points_item);

    item_map.insert (std::make_pair (points_item, classif));
    
    QApplication::restoreOverrideCursor();
    update_plugin_from_item(classif);
    return classif;
  }

  Item_classification_base* create_from_item(Scene_surface_mesh_item* mesh_item)
  {
    if (item_map.find(mesh_item) != item_map.end())
      return item_map[mesh_item];

    QApplication::setOverrideCursor(Qt::WaitCursor);
    Item_classification_base* classif
      = new Surface_mesh_item_classification (mesh_item);
    item_map.insert (std::make_pair (mesh_item, classif));
    QApplication::restoreOverrideCursor();
    update_plugin_from_item(classif);
    return classif;
  }
  
  void run (Item_classification_base* classif, int method,
            std::size_t subdivisions = 1,
            double smoothing = 0.5)
  {
    classif->run (method, ui_widget.classifier->currentIndex(), subdivisions, smoothing);
  }

  void on_classifier_changed (int index)
  {
    if (index == 0)
    {
      dock_widget_adv->show();
      dock_widget_adv->raise();
    }
    else
      dock_widget_adv->hide();
  }
    
  void on_compute_features_button_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }
    
    bool ok = false;
    int nb_scales = QInputDialog::getInt((QWidget*)mw,
                                         tr("Compute Features"), // dialog title
                                         tr("Number of scales:"), // field label
                                         5, 1, 99, 1, &ok);
    if (!ok)
      return;

    QApplication::setOverrideCursor(Qt::WaitCursor);

    classif->compute_features (std::size_t(nb_scales));

    update_plugin_from_item(classif);
    QApplication::restoreOverrideCursor();
    item_changed(classif->item());
  }

  void on_save_config_button_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    QString filename;

    if (ui_widget.classifier->currentIndex() == 0)
      filename = QFileDialog::getSaveFileName(mw,
                                              tr("Save classification configuration"),
                                              tr("%1 (CGAL classif config).xml").arg(classif->item()->name()),
                                              "CGAL classification configuration (*.xml);;");
    else if (ui_widget.classifier->currentIndex() == 1)
      filename = QFileDialog::getSaveFileName(mw,
                                              tr("Save classification configuration"),
                                              tr("%1 (ETHZ random forest config).gz").arg(classif->item()->name()),
                                              "Compressed ETHZ random forest configuration (*.gz);;");
#ifdef CGAL_LINKED_WITH_OPENCV
    else if (ui_widget.classifier->currentIndex() == 2)
      filename = QFileDialog::getSaveFileName(mw,
                                              tr("Save classification configuration"),
                                              tr("%1 (OpenCV %2.%3 random forest config).xml")
                                              .arg(classif->item()->name())
                                              .arg(CV_MAJOR_VERSION)
                                              .arg(CV_MINOR_VERSION),
                                              tr("OpenCV %2.%3 random forest configuration (*.xml);;")
                                              .arg(CV_MAJOR_VERSION)
                                              .arg(CV_MINOR_VERSION));
#endif
    
    if (filename == QString())
      return;

    
    QApplication::setOverrideCursor(Qt::WaitCursor);

    classif->save_config (filename.toStdString().c_str(),
                          ui_widget.classifier->currentIndex());
    
    QApplication::restoreOverrideCursor();

  }

  void on_load_config_button_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }
    QString filename;

    if (ui_widget.classifier->currentIndex() == 0)
      filename = QFileDialog::getOpenFileName(mw,
                                              tr("Open CGAL classification configuration"),
                                              ".",
                                              "CGAL classification configuration (*.xml);;All Files (*)");
    else if (ui_widget.classifier->currentIndex() == 1)
      filename = QFileDialog::getOpenFileName(mw,
                                              tr("Open ETHZ random forest configuration"),
                                              ".",
                                              "Compressed ETHZ random forest configuration (*.gz);;All Files (*)");
#ifdef CGAL_LINKED_WITH_OPENCV
    else if (ui_widget.classifier->currentIndex() == 2)
      filename = QFileDialog::getOpenFileName(mw,
                                              tr("Open OpenCV %2.%3 random forest configuration")
                                              .arg(CV_MAJOR_VERSION)
                                              .arg(CV_MINOR_VERSION),
                                              ".",
                                              tr("OpenCV %2.%3 random forest configuration (*.xml);;All Files (*)")
                                              .arg(CV_MAJOR_VERSION)
                                              .arg(CV_MINOR_VERSION));
#endif

    if (filename == QString())
      return;

    QApplication::setOverrideCursor(Qt::WaitCursor);

    classif->load_config (filename.toStdString().c_str(),
                          ui_widget.classifier->currentIndex());
    update_plugin_from_item(classif);
    run (classif, 0);
    
    QApplication::restoreOverrideCursor();
    item_changed(classif->item());
  }


  void on_display_button_clicked(int index)
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      return; 

    classif->change_color (index);
    item_changed(classif->item());
  }

  void on_run_button_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }
    QApplication::setOverrideCursor(Qt::WaitCursor);
    CGAL::Real_timer t;
    t.start();
    run (classif, 0);
    t.stop();
    std::cerr << "Raw classification computed in " << t.time() << " second(s)" << std::endl;
    QApplication::restoreOverrideCursor();
    item_changed(classif->item());
  }

  void on_run_smoothed_button_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    QApplication::setOverrideCursor(Qt::WaitCursor);
    CGAL::Real_timer t;
    t.start();
    run (classif, 1);
    t.stop();
    std::cerr << "Smoothed classification computed in " << t.time() << " second(s)" << std::endl;
    QApplication::restoreOverrideCursor();
    item_changed(classif->item());
  }
  
  void on_run_graphcut_button_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    QMultipleInputDialog dialog ("Classify with Graph Cut", mw);
    QSpinBox* subdivisions = dialog.add<QSpinBox> ("Number of subdivisons: ");
    subdivisions->setRange (1, 9999);
    subdivisions->setValue (16);

    QDoubleSpinBox* smoothing = dialog.add<QDoubleSpinBox> ("Regularization weight: ");
    smoothing->setRange (0.0, 100.0);
    smoothing->setValue (0.5);
    smoothing->setSingleStep (0.1);

    if (dialog.exec() != QDialog::Accepted)
      return;
    
    QApplication::setOverrideCursor(Qt::WaitCursor);
    CGAL::Real_timer t;
    t.start();
    run (classif, 2, std::size_t(subdivisions->value()), smoothing->value());
    t.stop();
    std::cerr << "Graph Cut classification computed in " << t.time() << " second(s)" << std::endl;
    QApplication::restoreOverrideCursor();
    item_changed(classif->item());
  }

  void on_create_point_set_item()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    QPushButton* label_clicked = qobject_cast<QPushButton*>(QObject::sender()->parent()->parent());
    
    if (label_clicked == NULL)
      std::cerr << "Error" << std::endl;
    else
    {
      int index = ui_widget.labelGrid->indexOf(label_clicked);
      int row_index, column_index, row_span, column_span;
      ui_widget.labelGrid->getItemPosition(index, &row_index, &column_index, &row_span, &column_span);

      int position = row_index * 3 + column_index;

      Scene_item* item =  classif->generate_one_item
        (classif->item()->name().toStdString().c_str(), position);

      Scene_points_with_normal_item* points_item
        = qobject_cast<Scene_points_with_normal_item*>(item);

      if (!points_item)
        return;

      if (points_item->point_set()->empty())
        delete points_item;
      else
        scene->addItem (points_item);
    }
  }
  
  void on_generate_items_button_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    
    QApplication::setOverrideCursor(Qt::WaitCursor);

    std::vector<Scene_item*> new_items;
    classif->generate_one_item_per_label
      (new_items, classif->item()->name().toStdString().c_str());

    for (std::size_t i = 0; i < new_items.size(); ++ i)
      {
        Scene_points_with_normal_item* points_item
          = qobject_cast<Scene_points_with_normal_item*>(new_items[i]);
        if (!points_item)
          continue;
        
        if (points_item->point_set()->empty())
          delete points_item;
        else
          scene->addItem (points_item);
      }
    
    QApplication::restoreOverrideCursor();
  }

  void clear_labels (Item_classification_base* classif)
  {
    classif->clear_labels();
  }
  
  void add_new_label (Item_classification_base* classif, const std::string& name)
  {
    add_new_label (LabelButton (dock_widget,
                                name.c_str(),
                                QColor (64 + rand() % 192,
                                        64 + rand() % 192,
                                        64 + rand() % 192),
                                get_shortcut (label_buttons.size(), name.c_str())));
    QColor color = classif->add_new_label (name.c_str());
    label_buttons.back().change_color (color);
  }
  
  void on_add_new_label_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    bool ok;
    QString name =
      QInputDialog::getText((QWidget*)mw,
                            tr("Add new labels"), // dialog title
                            tr("Names (separated by spaces):"), // field label
                            QLineEdit::Normal,
                            tr("label_%1").arg(label_buttons.size()),
                            &ok);
    if (!ok)
      return;

    std::istringstream iss (name.toStdString());

    std::string n;
    while (iss >> n)
      add_new_label (classif, n);

    add_label_button();
  }

  void on_use_config_building_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    if (classif->number_of_labels() != 0)
    {
      QMessageBox::StandardButton reply
        = QMessageBox::question(NULL, "Classification",
                                "Current labels will be discarded. Continue?",
                                QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes);
      
      if (reply == QMessageBox::No)
        return;
    }
    clear_labels (classif);
    add_new_label (classif, "ground");
    add_new_label (classif, "vegetation");
    add_new_label (classif, "building");
    update_plugin_from_item (classif);
  }

  void on_use_config_roof_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    if (classif->number_of_labels() != 0)
    {
      QMessageBox::StandardButton reply
        = QMessageBox::question(NULL, "Classification",
                                "Current labels will be discarded. Continue?",
                                QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes);
      
      if (reply == QMessageBox::No)
        return;
    }
    clear_labels (classif);
    add_new_label (classif, "ground");
    add_new_label (classif, "vegetation");
    add_new_label (classif, "roof");
    add_new_label (classif, "facade");
    update_plugin_from_item (classif);
  }

  void on_use_las_config_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    if (classif->number_of_labels() != 0)
    {
      QMessageBox::StandardButton reply
        = QMessageBox::question(NULL, "Classification",
                                "Current labels will be discarded. Continue?",
                                QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes);
      
      if (reply == QMessageBox::No)
        return;
    }
    clear_labels (classif);
    add_new_label (classif, "ground");
    add_new_label (classif, "low_veget");
    add_new_label (classif, "med_veget");
    add_new_label (classif, "high_veget");
    add_new_label (classif, "building");
    add_new_label (classif, "noise");
    add_new_label (classif, "reserved");
    add_new_label (classif, "water");
    add_new_label (classif, "rail");
    add_new_label (classif, "road_surface");
    add_new_label (classif, "reserved_2");
    add_new_label (classif, "wire_guard");
    add_new_label (classif, "wire_conduct");
    add_new_label (classif, "trans_tower");
    add_new_label (classif, "wire_connect");
    add_new_label (classif, "bridge_deck");
    add_new_label (classif, "high_noise");
    update_plugin_from_item (classif);
  }

  void on_clear_labels_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    if (classif->number_of_labels() != 0)
    {
      QMessageBox::StandardButton reply
        = QMessageBox::question(NULL, "Classification",
                                "Current labels will be discarded. Continue?",
                                QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes);
      
      if (reply == QMessageBox::No)
        return;
    }
    clear_labels (classif);
    update_plugin_from_item (classif);
  }

  void on_reset_training_sets_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    classif->reset_training_sets();

    item_changed(classif->item());
  }

  void on_reset_training_set_of_selection_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    classif->reset_training_set_of_selection();

    item_changed(classif->item());
  }

  void on_reset_training_set_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    QPushButton* label_clicked = qobject_cast<QPushButton*>(QObject::sender()->parent()->parent());
    if (label_clicked == NULL)
      std::cerr << "Error" << std::endl;
    else
    {
      int index = ui_widget.labelGrid->indexOf(label_clicked);
      int row_index, column_index, row_span, column_span;
      ui_widget.labelGrid->getItemPosition(index, &row_index, &column_index, &row_span, &column_span);

      int position = row_index * 3 + column_index;
      
      classif->reset_training_set
        (position);
    }
    
    item_changed(classif->item());
  }

  void on_validate_selection_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    classif->validate_selection();
  }

  void on_select_random_region_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    classif->select_random_region();
    item_changed(classif->item());

    CGAL::QGLViewer* viewer = *CGAL::QGLViewer::QGLViewerPool().begin();
    CGAL::Bbox_3 bbox = classif->bbox();
    const CGAL::qglviewer::Vec offset = static_cast<CGAL::Three::Viewer_interface*>(viewer)->offset();
    
    viewer->camera()->fitBoundingBox(CGAL::qglviewer::Vec (bbox.xmin(), bbox.ymin(), bbox.zmin()) + offset,
                                     CGAL::qglviewer::Vec (bbox.xmax(), bbox.ymax(), bbox.zmax()) + offset);
    


    viewer->camera()->setPivotPoint (CGAL::qglviewer::Vec ((bbox.xmin() + bbox.xmax()) / 2.,
                                                     (bbox.ymin() + bbox.ymax()) / 2.,
                                                     (bbox.zmin() + bbox.zmax()) / 2.) + offset);
  }

  void on_train_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    int nb_trials = 0;
    int num_trees = 0;
    int max_depth = 0;

    if (ui_widget.classifier->currentIndex() == 0)
    {
      bool ok = false;
      nb_trials = QInputDialog::getInt((QWidget*)mw,
                                       tr("Train Classifier"), // dialog title
                                       tr("Number of trials:"), // field label
                                       800, 1, 99999, 50, &ok);
      if (!ok)
        return;
    }
    else
    {
      QMultipleInputDialog dialog ("Train Random Forest Classifier", mw);
      QSpinBox* trees = dialog.add<QSpinBox> ("Number of trees: ");
      trees->setRange (1, 9999);
      trees->setValue (25);
      QSpinBox* depth = dialog.add<QSpinBox> ("Maximum depth of tree: ");
      depth->setRange (1, 9999);
      depth->setValue (20);

      if (dialog.exec() != QDialog::Accepted)
        return;
      num_trees = trees->value();
      max_depth = depth->value();
    }
    
    QApplication::setOverrideCursor(Qt::WaitCursor);
    CGAL::Real_timer t;
    t.start();
    classif->train(ui_widget.classifier->currentIndex(), nb_trials,
                   num_trees, max_depth);
    t.stop();
    std::cerr << "Done in " << t.time() << " second(s)" << std::endl;
    QApplication::restoreOverrideCursor();
    update_plugin_from_item(classif);
  }

  char get_shortcut (std::size_t position, const char* name)
  {
    std::set<char> used_letters;
    
    used_letters.insert('t'); // used for "train"
    used_letters.insert('s'); // used for "random select"
    for (std::size_t i = 0; i < label_buttons.size(); ++ i)
      if (i != position)
        used_letters.insert (label_buttons[i].shortcut);

    std::size_t idx = 0;
    while (name[idx] != '\0')
    {
      if (std::isalpha(name[idx]) &&
          used_letters.find (std::tolower(name[idx])) == used_letters.end())
        return std::tolower(name[idx]);
      ++ idx;
    }

    char fallback = 'a';
    while (used_letters.find (fallback) != used_letters.end())
      ++ fallback;

    return fallback;
  }
  
  void add_new_label (const LabelButton& label_button)
  {
    label_buttons.push_back (label_button);
    int position = static_cast<int>(label_buttons.size()) - 1;

    int x = position / 3;
    int y = position % 3;

    ui_widget.labelGrid->addWidget (label_buttons.back().color_button, x, y);

    QAction* add_selection = label_buttons.back().menu->addAction ("Add selection to training set"); 

    add_selection->setShortcut(Qt::SHIFT | (Qt::Key_A + (label_button.shortcut - 'a')));
//    add_selection->setShortcut(Qt::Key_0 + label_buttons.size() - 1);
    
    connect(add_selection,  SIGNAL(triggered()), this,
            SLOT(on_add_selection_to_training_set_clicked()));
    
    QAction* reset = label_buttons.back().menu->addAction ("Reset training set");
    connect(reset,  SIGNAL(triggered()), this,
            SLOT(on_reset_training_set_clicked()));

    label_buttons.back().menu->addSeparator();
    
    QAction* change_color = label_buttons.back().menu->addAction ("Change color");
    connect(change_color,  SIGNAL(triggered()), this,
            SLOT(on_color_changed_clicked()));

    QAction* create = label_buttons.back().menu->addAction ("Create point set item from labeled points");
    connect(create,  SIGNAL(triggered()), this,
            SLOT(on_create_point_set_item()));

    label_buttons.back().menu->addSeparator();

    QAction* remove_label = label_buttons.back().menu->addAction ("Remove label");
    connect(remove_label,  SIGNAL(triggered()), this,
            SLOT(on_remove_label_clicked()));

    ui_widget_adv.gridLayout->addWidget (label_buttons.back().label2, position + 1, 0);
    ui_widget_adv.gridLayout->addWidget (label_buttons.back().effect, position + 1, 2);

    connect(label_buttons.back().effect,  SIGNAL(currentIndexChanged(int)), this,
            SLOT(on_effect_changed(int)));
  }

  void add_label_button()
  {
    int position = static_cast<int>(label_buttons.size());
    int x = position / 3;
    int y = position % 3;

    label_button->setVisible (true);
    ui_widget.labelGrid->addWidget (label_button, x, y);
  }

  void on_remove_label_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    QPushButton* label_clicked = qobject_cast<QPushButton*>(QObject::sender()->parent()->parent());
    if (label_clicked == NULL)
      std::cerr << "Error" << std::endl;
    else
    {
      int index = ui_widget.labelGrid->indexOf(label_clicked);
      int row_index, column_index, row_span, column_span;
      ui_widget.labelGrid->getItemPosition(index, &row_index, &column_index, &row_span, &column_span);

      int position = row_index * 3 + column_index;

      classif->remove_label (position);
    
      ui_widget.labelGrid->removeWidget (label_buttons[position].color_button);
      label_buttons[position].color_button->deleteLater();
      label_buttons[position].menu->deleteLater();
            
      ui_widget_adv.gridLayout->removeWidget (label_buttons[position].label2);
      delete label_buttons[position].label2;
      ui_widget_adv.gridLayout->removeWidget (label_buttons[position].effect);
      delete label_buttons[position].effect;

      if (label_buttons.size() > 1)
        for (int i = position + 1; i < static_cast<int>(label_buttons.size()); ++ i)
        {
          int position = i - 1;
          int x = position / 3;
          int y = position % 3;
          
          ui_widget.labelGrid->addWidget (label_buttons[i].color_button, x, y);
          ui_widget_adv.gridLayout->addWidget (label_buttons[i].label2, (int)i, 0);
          ui_widget_adv.gridLayout->addWidget (label_buttons[i].effect, (int)i, 2);
        }

      label_buttons.erase (label_buttons.begin() + position);
      add_label_button();
    }

    item_changed(classif->item());
  }

  void on_color_changed_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    QPushButton* label_clicked = qobject_cast<QPushButton*>(QObject::sender()->parent()->parent());
    if (label_clicked == NULL)
      std::cerr << "Error" << std::endl;
    else
    {
      int index = ui_widget.labelGrid->indexOf(label_clicked);
      int row_index, column_index, row_span, column_span;
      ui_widget.labelGrid->getItemPosition(index, &row_index, &column_index, &row_span, &column_span);

      int position = row_index * 3 + column_index;
    
      QColor color = label_buttons[position].color;
      color = QColorDialog::getColor(color, (QWidget*)mw, "Change of color of label");
      label_buttons[position].change_color (color);
      classif->change_label_color (position,
                                   color);
    }
    classif->update_color ();
    item_changed(classif->item());
  }

  void on_add_selection_to_training_set_clicked()
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    QPushButton* label_clicked = qobject_cast<QPushButton*>(QObject::sender()->parent()->parent());
    if (label_clicked == NULL)
      std::cerr << "Error" << std::endl;
    else
    {
      int index = ui_widget.labelGrid->indexOf(label_clicked);
      int row_index, column_index, row_span, column_span;
      ui_widget.labelGrid->getItemPosition(index, &row_index, &column_index, &row_span, &column_span);

      int position = row_index * 3 + column_index;
      classif->add_selection_to_training_set
        (position);
    }
    
    item_changed(classif->item());
  }

  void on_selected_feature_changed(int v)
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }

    if (classif->number_of_features() <= (std::size_t)v)
      return;
    
    Item_classification_base::Feature_handle
      att = classif->feature(v);

    if (att == Item_classification_base::Feature_handle())
      return;

    ui_widget_adv.feature_weight->setValue ((int)(1001. * 2. * std::atan(classif->weight(att)) / CGAL_PI));

    for (std::size_t i = 0; i < classif->number_of_labels(); ++ i)
      {
        CGAL::Classification::Sum_of_weighted_features_classifier::Effect
          eff = classif->effect (classif->label(i), att);
        if (eff == CGAL::Classification::Sum_of_weighted_features_classifier::PENALIZING)
          label_buttons[i].effect->setCurrentIndex(0);
        else if (eff == CGAL::Classification::Sum_of_weighted_features_classifier::NEUTRAL)
          label_buttons[i].effect->setCurrentIndex(1);
        else
          label_buttons[i].effect->setCurrentIndex(2);
      }
  }


  void on_feature_weight_changed(int v)
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }
    Item_classification_base::Feature_handle
      att = classif->feature(ui_widget_adv.selected_feature->currentIndex());

    if (att == Item_classification_base::Feature_handle())
      return;

    classif->set_weight(att, std::tan ((CGAL_PI/2.) * v / 1001.));

    for (std::size_t i = 0; i < label_buttons.size(); ++ i)
      label_buttons[i].effect->setEnabled(classif->weight(att) != 0.);
  }

  void on_effect_changed (int v)
  {
    Item_classification_base* classif
      = get_classification();
    if(!classif)
      {
        print_message("Error: there is no point set classification item!");
        return; 
      }
    Item_classification_base::Feature_handle
      att = classif->feature(ui_widget_adv.selected_feature->currentIndex());

    if (att == Item_classification_base::Feature_handle())
      return;

    QComboBox* combo = qobject_cast<QComboBox*>(QObject::sender());
    for (std::size_t i = 0;i < label_buttons.size(); ++ i)
      if (label_buttons[i].effect == combo)
        {
          //          std::cerr << att->id() << " is ";
          if (v == 0)
            {
              classif->set_effect(classif->label(i),
                                  att, CGAL::Classification::Sum_of_weighted_features_classifier::PENALIZING);
            }
          else if (v == 1)
            {
              classif->set_effect(classif->label(i),
                                  att, CGAL::Classification::Sum_of_weighted_features_classifier::NEUTRAL);
            }
          else
            {
              classif->set_effect(classif->label(i),
                                  att, CGAL::Classification::Sum_of_weighted_features_classifier::FAVORING);
            }
          break;
        }
  }

private:
  Messages_interface* messages;
  QAction* actionClassification;

  QDockWidget* dock_widget;
  QDockWidget* dock_widget_adv;
  QAction* action_train;
  QAction* action_reset_local;
  QAction* action_reset;
  QAction* action_random_region;
  QAction* action_validate;
  QAction* action_save_config;
  QAction* action_load_config;
  QAction* action_run;
  QAction* action_run_smoothed;
  QAction* action_run_graphcut;
  
  std::vector<LabelButton> label_buttons;
  QPushButton* label_button;
  
  Ui::Classification ui_widget;
  Ui::ClassificationAdvanced ui_widget_adv;

  QColor color_att;

  typedef std::map<Scene_item*, Item_classification_base*> Item_map;
  Item_map item_map;


}; // end Polyhedron_demo_classification_plugin

#include "Classification_plugin.moc"
