#include <pcl/apps/cloud_composer/cloud_composer.h>
#include <pcl/apps/cloud_composer/cloud_view.h>
#include <pcl/apps/cloud_composer/cloud_viewer.h>
#include <pcl/apps/cloud_composer/commands.h>
#include <pcl/apps/cloud_composer/item_inspector.h>
#include <pcl/apps/cloud_composer/point_selectors/interactor_style_switch.h>
#include <pcl/apps/cloud_composer/project_model.h>
#include <pcl/apps/cloud_composer/signal_multiplexer.h>
#include <pcl/apps/cloud_composer/tool_interface/abstract_tool.h>
#include <pcl/apps/cloud_composer/tool_interface/tool_factory.h>
#include <pcl/apps/cloud_composer/toolbox_model.h>

#include <QActionGroup>
#include <QMessageBox>
#include <QPluginLoader>
#include <QUndoGroup>

/////////////////////////////////////////////////////////////
pcl::cloud_composer::ComposerMainWindow::ComposerMainWindow(QWidget* parent)
: QMainWindow(parent)
{
  setupUi(this);

  this->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
  this->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);

  // Register types in Qt
  qRegisterMetaType<pcl::PCLPointCloud2::Ptr>("PCLPointCloud2Ptr");
  qRegisterMetaType<GeometryHandler::ConstPtr>("GeometryHandlerConstPtr");
  qRegisterMetaType<ColorHandler::ConstPtr>("ColorHandlerConstPtr");
  qRegisterMetaType<Eigen::Vector4f>("EigenVector4f");
  qRegisterMetaType<Eigen::Quaternionf>("EigenQuaternionf");
  qRegisterMetaType<ProjectModel>("ProjectModel");
  qRegisterMetaType<CloudView>("CloudView");
  qRegisterMetaType<ConstItemList>("ConstComposerItemList");

  current_model_ = nullptr;

  multiplexer_ = new SignalMultiplexer(this);

  initializeCloudBrowser();
  initializeCloudViewer();
  initializeItemInspector();
  initializeToolBox();
  initializePlugins();

  undo_group_ = new QUndoGroup(this);
  undo_view_->setGroup(undo_group_);

  // Auto connect signals and slots
  //  QMetaObject::connectSlotsByName(this);
  connectFileActions();
  connectEditActions();
  connectViewActions();
}

pcl::cloud_composer::ComposerMainWindow::~ComposerMainWindow()
{
  foreach (ProjectModel* to_delete, name_model_map_.values())
    to_delete->deleteLater();
}

void
pcl::cloud_composer::ComposerMainWindow::connectFileActions()
{}

void
pcl::cloud_composer::ComposerMainWindow::connectEditActions()
{
  // Replace the actions in the menu with undo actions created using the undo group
  QAction* action_temp = undo_group_->createUndoAction(this);
  action_temp->setShortcut(action_undo_->shortcut());
  menuEdit->insertAction(action_redo_, action_temp);
  menuEdit->removeAction(action_undo_);
  action_undo_ = action_temp;

  action_temp = undo_group_->createRedoAction(this);
  action_temp->setShortcut(action_redo_->shortcut());
  menuEdit->insertAction(action_redo_, action_temp);
  menuEdit->removeAction(action_redo_);
  action_redo_ = action_temp;

  multiplexer_->connect(
      action_clear_selection_, SIGNAL(triggered()), SLOT(clearSelection()));

  multiplexer_->connect(
      action_delete_, SIGNAL(triggered()), SLOT(deleteSelectedItems()));
  multiplexer_->connect(
      SIGNAL(deleteAvailable(bool)), action_delete_, SLOT(setEnabled(bool)));

  multiplexer_->connect(
      this, SIGNAL(insertNewCloudFromFile()), SLOT(insertNewCloudFromFile()));
  multiplexer_->connect(this,
                        SIGNAL(insertNewCloudFromRGBandDepth()),
                        SLOT(insertNewCloudFromRGBandDepth()));
  multiplexer_->connect(
      this, SIGNAL(saveSelectedCloudToFile()), SLOT(saveSelectedCloudToFile()));

  mouse_style_group_ = new QActionGroup(this);
  mouse_style_group_->addAction(action_trackball_camera_style_);
  mouse_style_group_->addAction(action_manipulate_clicked_);
  mouse_style_group_->addAction(action_manipulate_selected_);
  mouse_style_group_->addAction(action_rectangular_frustum_select_);
  mouse_style_group_->setExclusive(true);
  multiplexer_->connect(mouse_style_group_,
                        SIGNAL(triggered(QAction*)),
                        SLOT(mouseStyleChanged(QAction*)));
  multiplexer_->connect(
      SIGNAL(mouseStyleState(interactor_styles::INTERACTOR_STYLES)),
      this,
      SLOT(setMouseStyleAction(interactor_styles::INTERACTOR_STYLES)));
  action_trackball_camera_style_->setData(
      QVariant::fromValue(interactor_styles::PCL_VISUALIZER));
  action_rectangular_frustum_select_->setData(
      QVariant::fromValue(interactor_styles::RECTANGULAR_FRUSTUM));
  action_manipulate_selected_->setData(
      QVariant::fromValue(interactor_styles::SELECTED_TRACKBALL));
  action_manipulate_clicked_->setData(
      QVariant::fromValue(interactor_styles::CLICK_TRACKBALL));
  // multiplexer_->connect (action_manipulate_selected_, SIGNAL (triggered ()), SLOT
  // (selectedTrackballInteractorStyle ()));

  multiplexer_->connect(action_new_cloud_from_selection_,
                        SIGNAL(triggered()),
                        SLOT(createNewCloudFromSelection()));
  multiplexer_->connect(SIGNAL(newCloudFromSelectionAvailable(bool)),
                        action_new_cloud_from_selection_,
                        SLOT(setEnabled(bool)));

  multiplexer_->connect(
      action_select_all_, SIGNAL(triggered()), SLOT(selectAllItems()));
}

void
pcl::cloud_composer::ComposerMainWindow::setMouseStyleAction(
    interactor_styles::INTERACTOR_STYLES selected_style)
{
  action_trackball_camera_style_->setChecked(false);
  action_rectangular_frustum_select_->setChecked(false);
  action_manipulate_selected_->setChecked(false);
  action_manipulate_clicked_->setChecked(false);

  switch (selected_style) {
  case interactor_styles::PCL_VISUALIZER:
    action_trackball_camera_style_->setChecked(true);
    break;
  case interactor_styles::CLICK_TRACKBALL:
    action_manipulate_clicked_->setChecked(true);
    break;
  case interactor_styles::RECTANGULAR_FRUSTUM:
    action_rectangular_frustum_select_->setChecked(true);
    break;
  case interactor_styles::SELECTED_TRACKBALL:
    action_manipulate_selected_->setChecked(true);
    break;
  default:
    action_trackball_camera_style_->setChecked(true);
  }
}

void
pcl::cloud_composer::ComposerMainWindow::connectViewActions()
{
  multiplexer_->connect(
      action_show_axes_, SIGNAL(toggled(bool)), SLOT(setAxisVisibility(bool)));
  multiplexer_->connect(
      SIGNAL(axisVisible(bool)), action_show_axes_, SLOT(setChecked(bool)));
}

void
pcl::cloud_composer::ComposerMainWindow::initializeCloudBrowser()
{
  cloud_browser_->setSelectionMode(QAbstractItemView::ExtendedSelection);

  cloud_browser_->setStyleSheet("selection-background-color: red;");
}

void
pcl::cloud_composer::ComposerMainWindow::initializeCloudViewer()
{
  // Signal emitted when user selects new tab (ie different project) in the viewer
  connect(cloud_viewer_,
          SIGNAL(newModelSelected(ProjectModel*)),
          this,
          SLOT(setCurrentModel(ProjectModel*)));
}

void
pcl::cloud_composer::ComposerMainWindow::initializeItemInspector()
{}

void
pcl::cloud_composer::ComposerMainWindow::initializeToolBox()
{
  tool_box_model_ = new ToolBoxModel(tool_box_view_, tool_parameter_view_, this);
  tool_selection_model_ = new QItemSelectionModel(tool_box_model_);
  tool_box_model_->setSelectionModel(tool_selection_model_);

  tool_box_view_->setModel(tool_box_model_);
  tool_box_view_->setSelectionModel(tool_selection_model_);
  tool_box_view_->setIconSize(QSize(32, 32));
  tool_box_view_->setIndentation(10);

  connect(tool_selection_model_,
          SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
          tool_box_model_,
          SLOT(selectedToolChanged(const QModelIndex&, const QModelIndex&)));

  connect(tool_box_model_,
          SIGNAL(enqueueToolAction(AbstractTool*)),
          this,
          SLOT(enqueueToolAction(AbstractTool*)));

  connect(this,
          SIGNAL(activeProjectChanged(ProjectModel*, ProjectModel*)),
          tool_box_model_,
          SLOT(activeProjectChanged(ProjectModel*, ProjectModel*)));

  // TODO : Remove this, tools should have a better way of being run
  connect(action_run_tool_, SIGNAL(clicked()), tool_box_model_, SLOT(toolAction()));
  // tool_box_view_->setStyleSheet("branch:has-siblings:!adjoins-item:image none");
  // tool_box_view_->setStyleSheet("branch:!has-children:!has-siblings:adjoins-item:image:
  // none");
}

void
pcl::cloud_composer::ComposerMainWindow::initializePlugins()
{
  QDir plugin_dir = QCoreApplication::applicationDirPath();
  qDebug() << plugin_dir.path() << "   "
           << QDir::cleanPath("../lib/cloud_composer_plugins");
#ifdef _WIN32
  if (!plugin_dir.cd(QDir::cleanPath("cloud_composer_plugins")))
#else
  if (!plugin_dir.cd(QDir::cleanPath("../lib/cloud_composer_plugins")))
#endif
  {
#ifdef _WIN32
    if (!plugin_dir.cd(QDir::cleanPath("cloud_composer_plugins")))
#else
    if (!plugin_dir.cd(QDir::cleanPath("../lib")))
#endif
    {
      qCritical() << "Could not find plugin tool directory!!!";
    }
  }
  QStringList plugin_filter;
#ifdef _WIN32
  plugin_filter << "pcl_cc_tool_*.dll";
#else
  plugin_filter << "libpcl_cc_tool_*.so";
#endif
  plugin_dir.setNameFilters(plugin_filter);
  foreach (QString filename, plugin_dir.entryList(QDir::Files)) {
    qDebug() << "Loading " << plugin_dir.relativeFilePath(filename);
    QPluginLoader loader(plugin_dir.absoluteFilePath(filename), this);
    // This is automatically deleted when the library is unloaded (on app exit)
    QObject* plugin = loader.instance();
    ToolFactory* tool_factory = qobject_cast<ToolFactory*>(plugin);
    if (tool_factory) {
      qWarning() << "Loaded " << tool_factory->getPluginName();
      // Create the action button for this tool
      tool_box_model_->addTool(tool_factory);
    }
    else {
      qDebug() << "Could not load " << plugin_dir.relativeFilePath(filename);
      qDebug() << loader.errorString();
    }
  }
}

void
pcl::cloud_composer::ComposerMainWindow::setCurrentModel(ProjectModel* model)
{
  emit activeProjectChanged(model, current_model_);
  current_model_ = model;
  // qDebug () << "Setting cloud browser model";
  cloud_browser_->setModel(current_model_);
  // qDebug () << "Setting cloud browser selection model";
  cloud_browser_->setSelectionModel(current_model_->getSelectionModel());
  // qDebug () << "Item inspector setting model";
  item_inspector_->setModel(current_model_);
  // qDebug () << "Setting active stack in undo group";
  undo_group_->setActiveStack(current_model_->getUndoStack());

  multiplexer_->setCurrentObject(current_model_);
}

void
pcl::cloud_composer::ComposerMainWindow::enqueueToolAction(AbstractTool* tool)
{
  if (current_model_)
    current_model_->enqueueToolAction(tool);
  else
    QMessageBox::warning(
        this, "No Project Open!", "Cannot use tool, no project is open!");
}
///////// FILE MENU SLOTS ///////////
void pcl::cloud_composer::ComposerMainWindow::on_action_new_project__triggered(
    /*QString name*/)
{
  QString name("unsaved project");

  qDebug() << "Creating New Project";
  ProjectModel* new_project_model = new ProjectModel(this);
  // Check if we have a project with this name already, append int if so
  if (name_model_map_.contains(name)) {
    int k = 2;
    while (name_model_map_.contains(name + tr("-%1").arg(k)))
      ++k;
    name += tr("-%1").arg(k);
  }
  // qDebug () << "Setting name";
  new_project_model->setName(name);
  // qDebug () << "Inserting into map";
  name_model_map_.insert(name, new_project_model);
  // qDebug () << "Adding to undo group";
  undo_group_->addStack(new_project_model->getUndoStack());
  // qDebug () << "Setting current model";
  cloud_viewer_->addNewProject(new_project_model);

  setCurrentModel(new_project_model);
  // qDebug () << "Project " <<name<<" created!";
}

void
pcl::cloud_composer::ComposerMainWindow::
    on_action_open_cloud_as_new_project__triggered()
{
  qDebug() << "Opening cloud as new project";
}

void
pcl::cloud_composer::ComposerMainWindow::on_action_open_project__triggered()
{
  qDebug() << "Opening Project";
}

void
pcl::cloud_composer::ComposerMainWindow::on_action_save_project__triggered()
{
  qDebug() << "Saving Project";
}

void
pcl::cloud_composer::ComposerMainWindow::on_action_save_project_as__triggered()
{
  qDebug() << "Saving Project As...";
}

void
pcl::cloud_composer::ComposerMainWindow::on_action_save_selected_cloud__triggered()
{
  if (current_model_)
    emit saveSelectedCloudToFile();
  else
    QMessageBox::warning(
        this, "No Project Open!", "Cannot save cloud, no project is open!");
}

void
pcl::cloud_composer::ComposerMainWindow::on_action_exit__triggered()
{
  qDebug() << "Exiting...";
}

///////// EDIT MENU SLOTS ////////////
void
pcl::cloud_composer::ComposerMainWindow::on_action_insert_from_file__triggered()
{
  if (!current_model_)
    action_new_project_->trigger();

  emit insertNewCloudFromFile();
}

void
pcl::cloud_composer::ComposerMainWindow::on_action_insert_from_rgb_depth__triggered()
{
  if (!current_model_)
    action_new_project_->trigger();

  emit insertNewCloudFromRGBandDepth();
}

void
pcl::cloud_composer::ComposerMainWindow::
    on_action_insert_from_openNi_source__triggered()
{
  qDebug() << "Inserting cloud from OpenNi Source...";
}
