/*
 * Copyright 2015 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/
#include <functional>
#include <ignition/math/Helpers.hh>

#include "gazebo/transport/Node.hh"
#include "gazebo/transport/Publisher.hh"

#include "gazebo/rendering/UserCamera.hh"
#include "gazebo/rendering/Scene.hh"
#include "gazebo/rendering/COMVisual.hh"
#include "gazebo/rendering/Visual.hh"
#include "gazebo/rendering/SelectionObj.hh"
#include "gazebo/rendering/ApplyWrenchVisual.hh"

#include "gazebo/gui/Actions.hh"
#include "gazebo/gui/MainWindow.hh"
#include "gazebo/gui/MouseEventHandler.hh"
#include "gazebo/gui/GuiIface.hh"
#include "gazebo/gui/ApplyWrenchDialogPrivate.hh"
#include "gazebo/gui/ApplyWrenchDialog.hh"

using namespace gazebo;
using namespace gui;

/////////////////////////////////////////////////
ApplyWrenchDialog::ApplyWrenchDialog(QWidget *_parent)
  : QDialog(_parent), dataPtr(new ApplyWrenchDialogPrivate)
{
  this->setObjectName("ApplyWrenchDialog");
  this->dataPtr->mainWindow = gui::get_main_window();

  this->setWindowTitle(tr("Apply Force and Torque"));
  this->setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint |
      Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint);
  this->setWindowModality(Qt::NonModal);
  this->setStyleSheet(
      "QPushButton {\
          border-radius: 5px;\
          border-radius: 5px;\
      }");

  this->dataPtr->modelLabel = new QLabel();

  // Links list
  QHBoxLayout *linkLayout = new QHBoxLayout();
  QLabel *linkLabel = new QLabel(tr("<b>Apply to link:<b> "));
  this->dataPtr->linksComboBox = new QComboBox();
  this->dataPtr->linksComboBox->installEventFilter(this);
  this->dataPtr->linksComboBox->setMinimumWidth(200);
  connect(this->dataPtr->linksComboBox, SIGNAL(currentIndexChanged(QString)),
      this, SLOT(SetLink(QString)));

  linkLayout->addWidget(linkLabel);
  linkLayout->addWidget(this->dataPtr->linksComboBox);

  // Force
  QLabel *forceLabel = new QLabel(tr(
       "<font size=4>Force</font>"));
  forceLabel->setObjectName("forceLabel");
  forceLabel->setStyleSheet(
      "QLabel#forceLabel {\
          background-color: #444;\
          border-radius: 5px;\
          padding-left: 10px;\
          min-height: 40px;\
      }");

  // Force vector layout
  QGridLayout *forceVectorLayout = new QGridLayout();

  // Force Vector
  this->dataPtr->forceXSpin = new QDoubleSpinBox();
  this->dataPtr->forceYSpin = new QDoubleSpinBox();
  this->dataPtr->forceZSpin = new QDoubleSpinBox();

  std::vector<QDoubleSpinBox *> forceSpins;
  forceSpins.push_back(this->dataPtr->forceXSpin);
  forceSpins.push_back(this->dataPtr->forceYSpin);
  forceSpins.push_back(this->dataPtr->forceZSpin);

  for (unsigned int i = 0; i < forceSpins.size(); ++i)
  {
    QLabel *forceElementLabel = new QLabel();
    if (i == 0)
      forceElementLabel->setText(tr("X:"));
    else if (i == 1)
      forceElementLabel->setText(tr("Y:"));
    else if (i == 2)
      forceElementLabel->setText(tr("Z:"));
    QLabel *forceUnitLabel = new QLabel(tr("N"));

    forceSpins[i]->setRange(-ignition::math::MAX_D, ignition::math::MAX_D);
    forceSpins[i]->setSingleStep(100);
    forceSpins[i]->setDecimals(3);
    forceSpins[i]->setValue(0);
    forceSpins[i]->setMaximumWidth(100);
    forceSpins[i]->installEventFilter(this);
    connect(forceSpins[i], SIGNAL(valueChanged(double)), this,
        SLOT(OnForceChanged(double)));

    forceVectorLayout->addWidget(forceElementLabel, i, 0, Qt::AlignRight);
    forceVectorLayout->addWidget(forceSpins[i], i, 1);
    forceVectorLayout->addWidget(forceUnitLabel, i, 2);
  }

  // Force total
  QLabel *forceMagLabel = new QLabel(tr("Mag:"));
  QLabel *forceMagUnitLabel = new QLabel(tr("N"));

  this->dataPtr->forceMagSpin = new QDoubleSpinBox();
  this->dataPtr->forceMagSpin->setRange(0, ignition::math::MAX_D);
  this->dataPtr->forceMagSpin->setSingleStep(100);
  this->dataPtr->forceMagSpin->setDecimals(3);
  this->dataPtr->forceMagSpin->setValue(0);
  this->dataPtr->forceMagSpin->setMaximumWidth(100);
  this->dataPtr->forceMagSpin->installEventFilter(this);
  connect(this->dataPtr->forceMagSpin, SIGNAL(valueChanged(double)), this,
      SLOT(OnForceMagChanged(double)));

  forceVectorLayout->addWidget(forceMagLabel, 3, 0, Qt::AlignRight);
  forceVectorLayout->addWidget(this->dataPtr->forceMagSpin, 3, 1);
  forceVectorLayout->addWidget(forceMagUnitLabel, 3, 2);

  // Clear force
  QPushButton *forceClearButton = new QPushButton(tr("Clear"));
  connect(forceClearButton, SIGNAL(clicked()), this, SLOT(OnForceClear()));
  forceVectorLayout->addWidget(forceClearButton, 4, 0, 1, 3, Qt::AlignLeft);

  // Vertical separator
  QFrame *separator = new QFrame();
  separator->setFrameShape(QFrame::VLine);
  separator->setLineWidth(10);

  // Force Position
  QLabel *forcePosLabel = new QLabel(tr("Application Point:"));
  forcePosLabel->setObjectName("forcePosLabel");
  forcePosLabel->setStyleSheet(
      "QLabel#forcePosLabel {\
          max-height: 15px;\
      }");

  // CoM
  QLabel *comLabel = new QLabel(tr("Center of mass"));

  QLabel *comPixLabel = new QLabel();
  QPixmap comPixmap(":images/com.png");
  comPixmap = comPixmap.scaled(QSize(20, 20));
  comPixLabel->setPixmap(comPixmap);
  comPixLabel->setMask(comPixmap.mask());

  QHBoxLayout *comLabelLayout = new QHBoxLayout();
  comLabelLayout->addWidget(comLabel);
  comLabelLayout->addWidget(comPixLabel);

  this->dataPtr->comRadio = new QRadioButton();
  this->dataPtr->forcePosRadio = new QRadioButton();
  this->dataPtr->comRadio->setChecked(true);
  connect(this->dataPtr->comRadio, SIGNAL(toggled(bool)), this,
      SLOT(ToggleComRadio(bool)));

  // Force Position layout
  QGridLayout *forcePosLayout = new QGridLayout();
  forcePosLayout->setContentsMargins(0, 0, 0, 0);
  forcePosLayout->addWidget(forcePosLabel, 0, 0, 1, 4, Qt::AlignLeft);
  forcePosLayout->addWidget(this->dataPtr->comRadio, 1, 0);
  forcePosLayout->addLayout(comLabelLayout, 1, 1, 1, 3, Qt::AlignLeft);
  forcePosLayout->addWidget(this->dataPtr->forcePosRadio, 2, 0);

  // Force Position Vector
  this->dataPtr->forcePosXSpin = new QDoubleSpinBox();
  this->dataPtr->forcePosYSpin = new QDoubleSpinBox();
  this->dataPtr->forcePosZSpin = new QDoubleSpinBox();

  std::vector<QDoubleSpinBox *> forcePosSpins;
  forcePosSpins.push_back(this->dataPtr->forcePosXSpin);
  forcePosSpins.push_back(this->dataPtr->forcePosYSpin);
  forcePosSpins.push_back(this->dataPtr->forcePosZSpin);

  for (unsigned int i = 0; i < forcePosSpins.size(); ++i)
  {
    QLabel *forcePosElementLabel = new QLabel();
    if (i == 0)
      forcePosElementLabel->setText(tr("X:"));
    else if (i == 1)
      forcePosElementLabel->setText(tr("Y:"));
    else if (i == 2)
      forcePosElementLabel->setText(tr("Z:"));
    QLabel *forcePosUnitLabel = new QLabel(tr("m"));

    forcePosSpins[i]->setRange(-ignition::math::MAX_D, ignition::math::MAX_D);
    forcePosSpins[i]->setSingleStep(0.1);
    forcePosSpins[i]->setDecimals(3);
    forcePosSpins[i]->setValue(0);
    forcePosSpins[i]->setMaximumWidth(100);
    forcePosSpins[i]->installEventFilter(this);
    connect(forcePosSpins[i], SIGNAL(valueChanged(double)), this,
        SLOT(OnForcePosChanged(double)));

    forcePosLayout->addWidget(forcePosElementLabel, i+2, 1, Qt::AlignRight);
    forcePosLayout->addWidget(forcePosSpins[i], i+2, 2);
    forcePosLayout->addWidget(forcePosUnitLabel, i+2, 3);
  }

  // Apply force
  QPushButton *applyForceButton = new QPushButton("Apply Force");
  connect(applyForceButton, SIGNAL(clicked()), this, SLOT(OnApplyForce()));

  // Force layout
  QGridLayout *forceLayout = new QGridLayout();
  forceLayout->setContentsMargins(0, 0, 0, 0);
  forceLayout->addWidget(forceLabel, 0, 0, 1, 5);
  forceLayout->addItem(new QSpacerItem(10, 10), 1, 0, 1, 5);
  forceLayout->addLayout(forceVectorLayout, 2, 1);
  forceLayout->addWidget(separator, 2, 2);
  forceLayout->addLayout(forcePosLayout, 2, 3);
  forceLayout->addItem(new QSpacerItem(10, 10), 3, 0, 1, 5);
  forceLayout->addWidget(applyForceButton, 4, 1, 1, 3, Qt::AlignRight);
  forceLayout->addItem(new QSpacerItem(5, 10), 5, 0);
  forceLayout->addItem(new QSpacerItem(7, 10), 5, 4);

  QFrame *forceFrame = new QFrame();
  forceFrame->setLayout(forceLayout);
  forceFrame->setObjectName("forceLayout");
  forceFrame->setFrameShape(QFrame::StyledPanel);

  forceFrame->setStyleSheet(
      "QFrame#forceLayout {\
          background-color: #666;\
          border-radius: 10px;\
      }");

  QGraphicsDropShadowEffect *forceEffect = new QGraphicsDropShadowEffect;
  forceEffect->setBlurRadius(5);
  forceEffect->setXOffset(5);
  forceEffect->setYOffset(5);
  forceEffect->setColor(Qt::black);
  forceFrame->setGraphicsEffect(forceEffect);

  // Torque
  QLabel *torqueLabel = new QLabel(tr("<font size=4>Torque</font>"));
  torqueLabel->setObjectName("torqueLabel");
  torqueLabel->setStyleSheet(
      "QLabel#torqueLabel {\
          background-color: #444;\
          border-radius: 5px;\
          padding-left: 10px;\
          min-height: 40px;\
      }");

  // Torque vector layout
  QGridLayout *torqueVectorLayout = new QGridLayout();

  // Torque Vector
  this->dataPtr->torqueXSpin = new QDoubleSpinBox();
  this->dataPtr->torqueYSpin = new QDoubleSpinBox();
  this->dataPtr->torqueZSpin = new QDoubleSpinBox();

  std::vector<QDoubleSpinBox *> torqueSpins;
  torqueSpins.push_back(this->dataPtr->torqueXSpin);
  torqueSpins.push_back(this->dataPtr->torqueYSpin);
  torqueSpins.push_back(this->dataPtr->torqueZSpin);

  for (unsigned int i = 0; i < torqueSpins.size(); ++i)
  {
    QLabel *torqueElementLabel = new QLabel();
    if (i == 0)
      torqueElementLabel->setText(tr("X:"));
    else if (i == 1)
      torqueElementLabel->setText(tr("Y:"));
    else if (i == 2)
      torqueElementLabel->setText(tr("Z:"));
    QLabel *torqueUnitLabel = new QLabel(tr("Nm"));

    torqueSpins[i]->setRange(-ignition::math::MAX_D, ignition::math::MAX_D);
    torqueSpins[i]->setSingleStep(100);
    torqueSpins[i]->setDecimals(3);
    torqueSpins[i]->setValue(0);
    torqueSpins[i]->setMaximumWidth(100);
    torqueSpins[i]->installEventFilter(this);
    connect(torqueSpins[i], SIGNAL(valueChanged(double)), this,
        SLOT(OnTorqueChanged(double)));

    torqueVectorLayout->addWidget(torqueElementLabel, i, 0, Qt::AlignRight);
    torqueVectorLayout->addWidget(torqueSpins[i], i, 1);
    torqueVectorLayout->addWidget(torqueUnitLabel, i, 2);
  }

  // Torque magnitude
  QLabel *torqueMagLabel = new QLabel(tr("Mag:"));
  QLabel *torqueMagUnitLabel = new QLabel(tr("Nm"));

  this->dataPtr->torqueMagSpin = new QDoubleSpinBox();
  this->dataPtr->torqueMagSpin->setRange(0, ignition::math::MAX_D);
  this->dataPtr->torqueMagSpin->setSingleStep(100);
  this->dataPtr->torqueMagSpin->setDecimals(3);
  this->dataPtr->torqueMagSpin->setValue(0);
  this->dataPtr->torqueMagSpin->setMaximumWidth(100);
  this->dataPtr->torqueMagSpin->installEventFilter(this);
  connect(this->dataPtr->torqueMagSpin, SIGNAL(valueChanged(double)), this,
      SLOT(OnTorqueMagChanged(double)));

  torqueVectorLayout->addWidget(torqueMagLabel, 3, 0, Qt::AlignRight);
  torqueVectorLayout->addWidget(this->dataPtr->torqueMagSpin, 3, 1);
  torqueVectorLayout->addWidget(torqueMagUnitLabel, 3, 2);

  // Clear torque
  QPushButton *torqueClearButton = new QPushButton(tr("Clear"));
  connect(torqueClearButton, SIGNAL(clicked()), this, SLOT(OnTorqueClear()));
  torqueVectorLayout->addWidget(torqueClearButton, 4, 0, 1, 3, Qt::AlignLeft);

  // Apply torque
  QPushButton *applyTorqueButton = new QPushButton("Apply Torque");
  connect(applyTorqueButton, SIGNAL(clicked()), this, SLOT(OnApplyTorque()));

  // Torque layout
  QGridLayout *torqueLayout = new QGridLayout();
  torqueLayout->setContentsMargins(0, 0, 0, 0);
  torqueLayout->addWidget(torqueLabel, 0, 0, 1, 3);
  torqueLayout->addItem(new QSpacerItem(10, 10), 1, 0, 1, 3);
  torqueLayout->addLayout(torqueVectorLayout, 2, 1);
  torqueLayout->addItem(new QSpacerItem(10, 10), 3, 0, 1, 3);
  torqueLayout->addWidget(applyTorqueButton, 4, 1, 1, 1, Qt::AlignRight);
  torqueLayout->addItem(new QSpacerItem(5, 10), 5, 0);
  torqueLayout->addItem(new QSpacerItem(5, 10), 5, 2);

  QFrame *torqueFrame = new QFrame();
  torqueFrame->setLayout(torqueLayout);
  torqueFrame->setObjectName("torqueLayout");
  torqueFrame->setFrameShape(QFrame::StyledPanel);

  torqueFrame->setStyleSheet(
      "QFrame#torqueLayout {\
          background-color: #666;\
          border-radius: 10px;\
      }");

  QGraphicsDropShadowEffect *torqueEffect = new QGraphicsDropShadowEffect;
  torqueEffect->setBlurRadius(5);
  torqueEffect->setXOffset(5);
  torqueEffect->setYOffset(5);
  torqueEffect->setColor(Qt::black);
  torqueFrame->setGraphicsEffect(torqueEffect);

  // Buttons
  QPushButton *cancelButton = new QPushButton(tr("Cancel"));
  connect(cancelButton, SIGNAL(clicked()), this, SLOT(OnCancel()));

  QPushButton *applyAllButton = new QPushButton("Apply All");
  applyAllButton->setDefault(true);
  connect(applyAllButton, SIGNAL(clicked()), this, SLOT(OnApplyAll()));

  QHBoxLayout *buttonsLayout = new QHBoxLayout;
  buttonsLayout->addWidget(cancelButton);
  buttonsLayout->addWidget(applyAllButton);

  // Main layout
  QGridLayout *mainLayout = new QGridLayout();
  mainLayout->setSizeConstraint(QLayout::SetFixedSize);
  mainLayout->addWidget(this->dataPtr->modelLabel, 0, 0, 1, 2, Qt::AlignLeft);
  mainLayout->addLayout(linkLayout, 1, 0, 1, 2, Qt::AlignLeft);
  mainLayout->addWidget(forceFrame, 2, 0);
  mainLayout->addWidget(torqueFrame, 2, 1);
  mainLayout->addLayout(buttonsLayout, 3, 0, 1, 2, Qt::AlignRight);

  this->setLayout(mainLayout);

  // Transport
  this->dataPtr->node = transport::NodePtr(new transport::Node());
  this->dataPtr->node->TryInit(common::Time::Maximum());

  this->dataPtr->userCmdPub =
      this->dataPtr->node->Advertise<msgs::UserCmd>("~/user_cmd");

  this->dataPtr->comVector = ignition::math::Vector3d::Zero;
  this->dataPtr->forceVector = ignition::math::Vector3d::Zero;
  this->dataPtr->torqueVector = ignition::math::Vector3d::Zero;
}

/////////////////////////////////////////////////
ApplyWrenchDialog::~ApplyWrenchDialog()
{
  this->Fini();
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::Init(const std::string &_modelName,
    const std::string &_linkName)
{
  if (!this->SetModel(_modelName))
  {
    this->Fini();
    return;
  }

  if (!this->SetLink(_linkName))
  {
    this->Fini();
    return;
  }

  connect(this, SIGNAL(rejected()), this, SLOT(OnCancel()));

  if (g_rotateAct)
    connect(g_rotateAct, SIGNAL(triggered()), this, SLOT(OnManipulation()));
  if (g_translateAct)
    connect(g_translateAct, SIGNAL(triggered()), this, SLOT(OnManipulation()));
  if (g_scaleAct)
    connect(g_scaleAct, SIGNAL(triggered()), this, SLOT(OnManipulation()));

  this->move(QCursor::pos());
  this->show();
  this->ActivateWindow();
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::Fini()
{
  if (this->dataPtr->mainWindow)
    this->dataPtr->mainWindow->removeEventFilter(this);

  this->dataPtr->userCmdPub.reset();
  if (this->dataPtr->node)
    this->dataPtr->node->Fini();
  this->dataPtr->node.reset();
  this->dataPtr->connections.clear();

  if (this->dataPtr->applyWrenchVisual)
  {
    MouseEventHandler::Instance()->RemoveReleaseFilter(
        "dialog_"+this->dataPtr->applyWrenchVisual->Name());
    MouseEventHandler::Instance()->RemovePressFilter(
        "dialog_"+this->dataPtr->applyWrenchVisual->Name());
    MouseEventHandler::Instance()->RemoveMoveFilter(
        "dialog_"+this->dataPtr->applyWrenchVisual->Name());

    this->dataPtr->applyWrenchVisual->Fini();
  }
  this->dataPtr->applyWrenchVisual.reset();

  this->deleteLater();
}

/////////////////////////////////////////////////
bool ApplyWrenchDialog::SetModel(const std::string &_modelName)
{
  if (!gui::get_active_camera() || !gui::get_active_camera()->GetScene())
    return false;

  rendering::VisualPtr vis = gui::get_active_camera()->GetScene()->
      GetVisual(_modelName);

  if (!vis)
  {
    gzerr << "Model [" << _modelName << "] could not be found." << std::endl;
    return false;
  }

  this->dataPtr->modelName = _modelName;

  // Check if model/link hasn't been deleted on PreRender
  this->dataPtr->connections.push_back(
      event::Events::ConnectPreRender(
      std::bind(&ApplyWrenchDialog::OnPreRender, this)));

  this->dataPtr->modelLabel->setText(("<b>Model:</b> " + _modelName).c_str());

  // Don't fire signals while inserting items
  this->dataPtr->linksComboBox->blockSignals(true);
  this->dataPtr->linksComboBox->clear();

  for (unsigned int i = 0; i < vis->GetChildCount(); ++i)
  {
    rendering::VisualPtr childVis = vis->GetChild(i);

    // Skip all children that aren't links
    if (childVis->GetType() != rendering::Visual::VT_LINK)
    {
      continue;
    }

    std::string linkName = childVis->Name();

    // Issue #1553: This is failing to get real links sometimes:
    // uint32_t flags = childVis->GetVisibilityFlags();
    // if (!((flags != GZ_VISIBILITY_ALL) && (flags & GZ_VISIBILITY_GUI)))
    if (linkName.find("_GL_MANIP_") == std::string::npos)
    {
      size_t modelNamePos = linkName.find(_modelName);
      // Skip if the model name isn't in the scoped link name
      if (modelNamePos == std::string::npos)
      {
        continue;
      }
      std::string unscopedLinkName = linkName.substr(modelNamePos +
          + _modelName.size() + 2);

      this->dataPtr->linksComboBox->addItem(
          QString::fromStdString(unscopedLinkName));

      // Get CoM from link's COMVisual
      for (unsigned int j = 0; j < childVis->GetChildCount(); ++j)
      {
        rendering::COMVisualPtr comVis =
            std::dynamic_pointer_cast<rendering::COMVisual>(
            childVis->GetChild(j));

        if (comVis)
        {
          this->dataPtr->linkToCOMMap[linkName] = comVis->InertiaPose().Pos();
          break;
        }
      }
    }
  }

  // Sort alphabetically
  QSortFilterProxyModel *proxy = new QSortFilterProxyModel(
      this->dataPtr->linksComboBox);
  proxy->setSourceModel(this->dataPtr->linksComboBox->model());
  this->dataPtr->linksComboBox->model()->setParent(proxy);
  this->dataPtr->linksComboBox->setModel(proxy);
  this->dataPtr->linksComboBox->model()->sort(0);

  this->dataPtr->linksComboBox->blockSignals(false);

  if (this->dataPtr->linksComboBox->count() > 0)
    return true;

  gzerr << "Couldn't find links in model ' [" << _modelName << "]."
      << std::endl;

  return false;
}

/////////////////////////////////////////////////
bool ApplyWrenchDialog::SetLink(const std::string &_linkName)
{
  if (!gui::get_active_camera() || !gui::get_active_camera()->GetScene())
    return false;

  size_t modelNamePos = _linkName.find(this->dataPtr->modelName);
  std::string unscopedLinkName;

  // Leave unscopedLinkName empty if model name cannot be found
  // in the scoped link name
  if (modelNamePos != std::string::npos)
  {
     // Select on combo box
    unscopedLinkName = _linkName.substr(modelNamePos +
        this->dataPtr->modelName.size() + 2);
  }

  int index = -1;
  for (int i = 0; i < this->dataPtr->linksComboBox->count(); ++i)
  {
    if ((this->dataPtr->linksComboBox->itemText(i)).toStdString() ==
        unscopedLinkName)
    {
      index = i;
      break;
    }
  }
  if (index == -1)
  {
    gzerr << "Link [" << _linkName << "] could not be found in the combo box."
          << std::endl;
    return false;
  }
  this->dataPtr->linksComboBox->setCurrentIndex(index);

  // Visual
  this->dataPtr->linkName = _linkName;
  rendering::VisualPtr vis = gui::get_active_camera()->GetScene()->
      GetVisual(this->dataPtr->linkName);

  if (!vis)
  {
    gzerr << "A visual named [" << this->dataPtr->linkName
          << "] could not be found." << std::endl;
    return false;
  }
  this->dataPtr->linkVisual = vis;
  this->AttachVisuals();

  // Filter main window activate events
  if (this->dataPtr->mainWindow)
    this->dataPtr->mainWindow->installEventFilter(this);

  // MouseRelease filter to gain focus
  if (this->dataPtr->applyWrenchVisual)
  {
    MouseEventHandler::Instance()->AddReleaseFilter(
        "dialog_"+this->dataPtr->applyWrenchVisual->Name(),
        std::bind(&ApplyWrenchDialog::OnMouseRelease, this,
        std::placeholders::_1));
  }

  return true;
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::SetLink(const QString _linkName)
{
  // Remove previous link's filter
  if (this->dataPtr->applyWrenchVisual)
  {
    MouseEventHandler::Instance()->RemoveReleaseFilter(
        "dialog_"+this->dataPtr->applyWrenchVisual->Name());
  }

  if (!this->SetLink(this->dataPtr->modelName + "::" + _linkName.toStdString()))
    this->Fini();
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnApplyAll()
{
  // Publish wrench message
  msgs::Wrench msg;
  msgs::Set(msg.mutable_force(), this->dataPtr->forceVector);
  msgs::Set(msg.mutable_torque(), this->dataPtr->torqueVector);
  msgs::Set(msg.mutable_force_offset(), this->dataPtr->forcePosVector);

  // Register user command on server
  // The wrench will be applied from the server
  msgs::UserCmd userCmdMsg;
  userCmdMsg.set_description("Apply wrench to [" + this->dataPtr->linkName +
      "]");
  userCmdMsg.set_entity_name(this->dataPtr->linkName);
  userCmdMsg.set_type(msgs::UserCmd::WRENCH);
  userCmdMsg.mutable_wrench()->CopyFrom(msg);
  this->dataPtr->userCmdPub->Publish(userCmdMsg);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnApplyForce()
{
  // Publish wrench message
  msgs::Wrench msg;
  msgs::Set(msg.mutable_force(), this->dataPtr->forceVector);
  msgs::Set(msg.mutable_torque(), ignition::math::Vector3d::Zero);
  msgs::Set(msg.mutable_force_offset(), this->dataPtr->forcePosVector);

  // Register user command on server
  // The wrench will be applied from the server
  msgs::UserCmd userCmdMsg;
  userCmdMsg.set_description("Apply force to [" + this->dataPtr->linkName +
      "]");
  userCmdMsg.set_entity_name(this->dataPtr->linkName);
  userCmdMsg.set_type(msgs::UserCmd::WRENCH);
  userCmdMsg.mutable_wrench()->CopyFrom(msg);
  this->dataPtr->userCmdPub->Publish(userCmdMsg);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnApplyTorque()
{
  // Publish wrench message
  msgs::Wrench msg;
  msgs::Set(msg.mutable_force(), ignition::math::Vector3d::Zero);
  msgs::Set(msg.mutable_torque(), this->dataPtr->torqueVector);

  // Register user command on server
  // The wrench will be applied from the server
  msgs::UserCmd userCmdMsg;
  userCmdMsg.set_description("Apply torque to [" + this->dataPtr->linkName +
      "]");
  userCmdMsg.set_entity_name(this->dataPtr->linkName);
  userCmdMsg.set_type(msgs::UserCmd::WRENCH);
  userCmdMsg.mutable_wrench()->CopyFrom(msg);
  this->dataPtr->userCmdPub->Publish(userCmdMsg);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnCancel()
{
  this->Fini();
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnForcePosChanged(double /*_value*/)
{
  // Update forcePos vector with values from XYZ spins
  this->SetForcePos(
      ignition::math::Vector3d(this->dataPtr->forcePosXSpin->value(),
                               this->dataPtr->forcePosYSpin->value(),
                               this->dataPtr->forcePosZSpin->value()));
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnForceMagChanged(double /*_magnitude*/)
{
  // Update force vector proportionally
  // Normalize current vector
  ignition::math::Vector3d v = this->dataPtr->forceVector;
  if (v == ignition::math::Vector3d::Zero)
    v = ignition::math::Vector3d::UnitX;
  else
    v.Normalize();

  // Multiply by new magnitude
  this->SetForce(v * this->dataPtr->forceMagSpin->value());
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnForceChanged(double /*_value*/)
{
  // Update force vector with values from XYZ spins
  this->SetForce(ignition::math::Vector3d(this->dataPtr->forceXSpin->value(),
                                          this->dataPtr->forceYSpin->value(),
                                          this->dataPtr->forceZSpin->value()));
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnForceClear()
{
  this->SetForce(ignition::math::Vector3d::Zero);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnTorqueMagChanged(double /*_magnitude*/)
{
  // Update torque vector proportionally
  // Normalize current vector
  ignition::math::Vector3d v = this->dataPtr->torqueVector;
  if (v == ignition::math::Vector3d::Zero)
    v = ignition::math::Vector3d::UnitX;
  else
    v.Normalize();

  // Multiply by new magnitude
  this->SetTorque(v * this->dataPtr->torqueMagSpin->value());
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnTorqueChanged(double /*_value*/)
{
  // Update torque vector with values from XYZ spins
  this->SetTorque(ignition::math::Vector3d(this->dataPtr->torqueXSpin->value(),
      this->dataPtr->torqueYSpin->value(),
      this->dataPtr->torqueZSpin->value()));
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnTorqueClear()
{
  this->SetTorque(ignition::math::Vector3d::Zero);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::ToggleComRadio(bool _checked)
{
  if (_checked)
  {
    this->SetForcePos(this->dataPtr->comVector);
  }
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::SetSpinValue(QDoubleSpinBox *_spin, const double _value)
{
  _spin->blockSignals(true);
  _spin->setValue(_value);
  _spin->blockSignals(false);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::SetForcePos(const ignition::math::Vector3d &_forcePos)
{
  this->dataPtr->forcePosVector = _forcePos;

  // Spins
  this->SetSpinValue(this->dataPtr->forcePosXSpin, _forcePos.X());
  this->SetSpinValue(this->dataPtr->forcePosYSpin, _forcePos.Y());
  this->SetSpinValue(this->dataPtr->forcePosZSpin, _forcePos.Z());

  // Check COM box
  if (_forcePos == this->dataPtr->comVector)
  {
    this->dataPtr->comRadio->setChecked(true);
  }
  else
  {
    this->dataPtr->forcePosRadio->setChecked(true);
  }

  // Visuals
  if (!this->dataPtr->applyWrenchVisual)
  {
    gzwarn << "No wrench visual found, so it won't be updated" << std::endl;
    return;
  }

  this->dataPtr->applyWrenchVisual->SetForcePos(
      this->dataPtr->forcePosVector);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::SetForce(const ignition::math::Vector3d &_force,
    const bool _rotatedByMouse)
{
  // This can be called from the dialog or the mouse
  std::lock_guard<std::mutex> lock(this->dataPtr->mutex);

  this->dataPtr->forceVector = _force;

  // Spins
  this->SetSpinValue(this->dataPtr->forceXSpin, _force.X());
  this->SetSpinValue(this->dataPtr->forceYSpin, _force.Y());
  this->SetSpinValue(this->dataPtr->forceZSpin, _force.Z());
  this->SetSpinValue(this->dataPtr->forceMagSpin, _force.Length());

  // Mode
  if (_force == ignition::math::Vector3d::Zero)
  {
    if (this->dataPtr->torqueVector == ignition::math::Vector3d::Zero)
      this->SetMode(Mode::NONE);
    else
      this->SetMode(Mode::TORQUE);
  }
  else
  {
    this->SetMode(Mode::FORCE);
  }

  // Visuals
  if (!this->dataPtr->applyWrenchVisual)
  {
    gzwarn << "No wrench visual found, so it won't be updated" << std::endl;
    return;
  }

  this->dataPtr->applyWrenchVisual->SetForce(_force, _rotatedByMouse);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::SetTorque(const ignition::math::Vector3d &_torque,
    const bool _rotatedByMouse)
{
  // This can be called from the dialog or the mouse
  std::lock_guard<std::mutex> lock(this->dataPtr->mutex);

  this->dataPtr->torqueVector = _torque;

  // Spins
  this->SetSpinValue(this->dataPtr->torqueXSpin, _torque.X());
  this->SetSpinValue(this->dataPtr->torqueYSpin, _torque.Y());
  this->SetSpinValue(this->dataPtr->torqueZSpin, _torque.Z());
  this->SetSpinValue(this->dataPtr->torqueMagSpin, _torque.Length());

  // Mode
  if (_torque == ignition::math::Vector3d::Zero)
  {
    if (this->dataPtr->forceVector == ignition::math::Vector3d::Zero)
      this->SetMode(Mode::NONE);
    else
      this->SetMode(Mode::FORCE);
  }
  else
  {
    this->SetMode(Mode::TORQUE);
  }

  // Visuals
  if (!this->dataPtr->applyWrenchVisual)
  {
    gzwarn << "No wrench visual found, so it won't be updated" << std::endl;
    return;
  }

  this->dataPtr->applyWrenchVisual->SetTorque(_torque, _rotatedByMouse);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::SetCoM(const ignition::math::Vector3d &_com)
{
  this->dataPtr->comVector = _com;

  // Visuals
  if (!this->dataPtr->applyWrenchVisual)
  {
    gzwarn << "No wrench visual found, so it won't be updated" << std::endl;
    return;
  }

  this->dataPtr->applyWrenchVisual->SetCoM(this->dataPtr->comVector);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnPreRender()
{
  if (!gui::get_active_camera() || !gui::get_active_camera()->GetScene())
    return;

  rendering::VisualPtr vis = gui::get_active_camera()->GetScene()->
      GetVisual(this->dataPtr->linkName);

  // Close dialog in case visual has been deleted
  if (!vis)
    this->Fini();
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::AttachVisuals()
{
  if (!gui::get_active_camera() || !gui::get_active_camera()->GetScene())
  {
    gzerr << "Camera or scene missing" << std::endl;
    return;
  }
  if (!this->dataPtr->linkVisual)
  {
    gzerr << "No link visual specified." << std::endl;
    return;
  }

  // Attaching for the first time
  if (!this->dataPtr->applyWrenchVisual)
  {
    // Generate unique name
    std::string visNameBase = this->dataPtr->modelName + "__APPLY_WRENCH__";
    rendering::VisualPtr vis = gui::get_active_camera()->GetScene()->
        GetVisual(visNameBase);

    std::string visName(visNameBase);
    int count = 0;
    while (vis)
    {
      visName = visNameBase + std::to_string(count);
      vis = gui::get_active_camera()->GetScene()->GetVisual(visName);
      ++count;
    }

    this->dataPtr->applyWrenchVisual.reset(new rendering::ApplyWrenchVisual(
        visName, this->dataPtr->linkVisual));

    this->dataPtr->applyWrenchVisual->Load();
  }
  // Different link
  else if (!this->dataPtr->applyWrenchVisual->GetParent() ||
      this->dataPtr->applyWrenchVisual->GetParent() !=
      this->dataPtr->linkVisual)
  {
    if (this->dataPtr->applyWrenchVisual->GetParent())
    {
      this->dataPtr->applyWrenchVisual->GetParent()->DetachVisual(
          this->dataPtr->applyWrenchVisual);
    }
    this->dataPtr->linkVisual->AttachVisual(this->dataPtr->applyWrenchVisual);
    this->dataPtr->applyWrenchVisual->Resize();
  }

  if (!this->dataPtr->applyWrenchVisual)
  {
    gzwarn << "Failed to attach wrench visual. " <<
        "Dialog will work without it." << std::endl;
  }

  // Set COM
  this->SetCoM(this->dataPtr->linkToCOMMap[this->dataPtr->linkName]);
  // Apply force at com by default
  this->SetForcePos(this->dataPtr->comVector);
  this->SetTorque(this->dataPtr->torqueVector);
  this->SetForce(this->dataPtr->forceVector);
}

/////////////////////////////////////////////////
bool ApplyWrenchDialog::OnMousePress(const common::MouseEvent &_event)
{
  rendering::UserCameraPtr userCamera = gui::get_active_camera();
  if (!userCamera || !this->dataPtr->applyWrenchVisual)
    return false;

  this->dataPtr->draggingTool = false;

  rendering::VisualPtr vis = userCamera->Visual(_event.Pos(),
      this->dataPtr->manipState);

  if (vis)
    return false;

  // If on top of a circle handle
  if (this->dataPtr->manipState == "rot_z" ||
      this->dataPtr->manipState == "rot_y")
  {
    this->dataPtr->draggingTool = true;

    // Highlight dragged circle
    this->dataPtr->applyWrenchVisual->GetRotTool()->SetState(
        this->dataPtr->manipState);

    // Register rotTool pose at drag start
    this->dataPtr->dragStartPose =
        this->dataPtr->applyWrenchVisual->GetRotTool()->WorldPose();
  }
  return false;
}

/////////////////////////////////////////////////
bool ApplyWrenchDialog::OnMouseRelease(const common::MouseEvent &_event)
{
  rendering::UserCameraPtr userCamera = gui::get_active_camera();
  if (!userCamera || !this->dataPtr->applyWrenchVisual)
    return false;

  rendering::VisualPtr vis = userCamera->Visual(_event.Pos(),
      this->dataPtr->manipState);

  if (!vis || _event.Dragging())
    return false;

  // Force/torque clicked: activate dialog and prevent event propagation
  if (vis == this->dataPtr->applyWrenchVisual->GetForceVisual())
  {
    this->ActivateWindow();

    // Activate visual, can't attach rot tool with zero vector, UnitX by default
    if (this->dataPtr->forceVector == ignition::math::Vector3d::Zero)
      this->SetForce(ignition::math::Vector3d::UnitX);
    else
      this->SetForce(this->dataPtr->forceVector);

    return true;
  }
  else if (vis == this->dataPtr->applyWrenchVisual->GetTorqueVisual())
  {
    this->ActivateWindow();

    // Activate visual, can't attach rot tool with zero vector, UnitX by default
    if (this->dataPtr->torqueVector == ignition::math::Vector3d::Zero)
      this->SetTorque(ignition::math::Vector3d::UnitX);
    else
      this->SetTorque(this->dataPtr->torqueVector);

    return true;
  }
  return false;
}

/////////////////////////////////////////////////
bool ApplyWrenchDialog::OnMouseMove(const common::MouseEvent &_event)
{
  rendering::UserCameraPtr userCamera = gui::get_active_camera();
  if (!userCamera || !this->dataPtr->applyWrenchVisual)
    return false;

  // Must make a Qt check as well because Gazebo event is not working with test
  bool isDragging = _event.Dragging() ||
      QApplication::mouseButtons() != Qt::NoButton;
  bool isLeftButton = _event.Button() == common::MouseEvent::LEFT ||
      QApplication::mouseButtons() == Qt::LeftButton;

  // Dragging tool, adapted from ModelManipulator::RotateEntity
  if (isDragging && isLeftButton && this->dataPtr->draggingTool)
  {
    ignition::math::Vector3d normal;
    ignition::math::Vector3d axis;
    if (this->dataPtr->manipState == "rot_z")
    {
      normal = this->dataPtr->dragStartPose.Rot().ZAxis();
      axis = ignition::math::Vector3d::UnitZ;
    }
    else if (this->dataPtr->manipState == "rot_y")
    {
      normal = this->dataPtr->dragStartPose.Rot().YAxis();
      axis = ignition::math::Vector3d::UnitY;
    }
    else
    {
      gzerr << "Dragging tool on wrong manip state, this shouldn't happen" <<
          std::endl;
      return false;
    }

    double offset = this->dataPtr->dragStartPose.Pos().Dot(normal);

    ignition::math::Vector3d pressPoint;
    userCamera->WorldPointOnPlane(_event.PressPos().X(),
        _event.PressPos().Y(),
        ignition::math::Planed(normal, offset), pressPoint);

    ignition::math::Vector3d newPoint;
    userCamera->WorldPointOnPlane(_event.Pos().X(), _event.Pos().Y(),
        ignition::math::Planed(normal, offset), newPoint);

    ignition::math::Vector3d v1 = pressPoint -
      this->dataPtr->dragStartPose.Pos();
    ignition::math::Vector3d v2 = newPoint -
      this->dataPtr->dragStartPose.Pos();
    v1 = v1.Normalize();
    v2 = v2.Normalize();
    double signTest = v1.Cross(v2).Dot(normal);
    double angle = atan2((v1.Cross(v2)).Length(), v1.Dot(v2));

    if (signTest < 0)
      angle *= -1;

    if (QApplication::keyboardModifiers() & Qt::ControlModifier)
      angle = rint(angle / (M_PI * 0.25)) * (M_PI * 0.25);

    ignition::math::Quaterniond rot(axis, angle);
    rot = this->dataPtr->dragStartPose.Rot() * rot;

    // Must rotate the tool here to make sure we have proper roll,
    // once the rotation gets transformed into a vector we lose a DOF
    this->dataPtr->applyWrenchVisual->GetRotTool()->SetWorldRotation(rot);

    // Get direction from tool orientation
    ignition::math::Vector3d vec;
    ignition::math::Vector3d rotEuler;
    rotEuler = rot.Euler();
    vec.X(cos(rotEuler.Z())*cos(rotEuler.Y()));
    vec.Y(sin(rotEuler.Z())*cos(rotEuler.Y()));
    vec.Z(-sin(rotEuler.Y()));

    // To local frame
    vec = this->dataPtr->linkVisual->WorldPose().Rot().RotateVectorReverse(
        vec);

    if (this->GetMode() == Mode::FORCE)
    {
      this->NewForceDirection(vec);
    }
    else if (this->GetMode() == Mode::TORQUE)
    {
      this->NewTorqueDirection(vec);
    }
    return true;
  }
  // Highlight hovered tools
  else
  {
    userCamera->Visual(_event.Pos(), this->dataPtr->manipState);

    if (this->dataPtr->manipState == "rot_z" ||
        this->dataPtr->manipState == "rot_y")
    {
      this->dataPtr->applyWrenchVisual->GetRotTool()->SetState(
          this->dataPtr->manipState);
    }
    else
    {
      this->dataPtr->applyWrenchVisual->GetRotTool()->SetState("");
    }
  }

  return false;
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::SetMode(Mode _mode)
{
  this->dataPtr->mode = _mode;
}

/////////////////////////////////////////////////
ApplyWrenchDialog::Mode ApplyWrenchDialog::GetMode() const
{
  return this->dataPtr->mode;
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::NewForceDirection(const ignition::math::Vector3d &_dir)
{
  // Normalize direction
  ignition::math::Vector3d v = _dir;
  if (v == ignition::math::Vector3d::Zero)
    v = ignition::math::Vector3d::UnitX;
  else
    v.Normalize();

  // Multiply by magnitude
  this->SetForce(v * this->dataPtr->forceMagSpin->value(), true);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::NewTorqueDirection(const ignition::math::Vector3d &_dir)
{
  // Normalize direction
  ignition::math::Vector3d v = _dir;
  if (v == ignition::math::Vector3d::Zero)
    v = ignition::math::Vector3d::UnitX;
  else
    v.Normalize();

  // Multiply by magnitude
  this->SetTorque(v * this->dataPtr->torqueMagSpin->value(), true);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::SetActive(bool _active)
{
  if (!this->dataPtr->applyWrenchVisual)
  {
    gzerr << "No apply wrench visual." << std::endl;
    this->Fini();
    return;
  }
  if (_active)
  {
    // Set visible
    this->dataPtr->applyWrenchVisual->SetMode(
        static_cast<rendering::ApplyWrenchVisual::Mode>(this->GetMode()));

    // Set selected
    event::Events::setSelectedEntity(this->dataPtr->linkName, "normal");

    // Set arrow mode
    if (g_arrowAct)
      g_arrowAct->trigger();

    MouseEventHandler::Instance()->AddPressFilter(
        "dialog_"+this->dataPtr->applyWrenchVisual->Name(),
        std::bind(&ApplyWrenchDialog::OnMousePress, this,
        std::placeholders::_1));

    MouseEventHandler::Instance()->AddMoveFilter(
        "dialog_"+this->dataPtr->applyWrenchVisual->Name(),
        std::bind(&ApplyWrenchDialog::OnMouseMove, this,
        std::placeholders::_1));
  }
  else
  {
    this->dataPtr->applyWrenchVisual->SetMode(
        rendering::ApplyWrenchVisual::Mode::NONE);

    MouseEventHandler::Instance()->RemovePressFilter(
        "dialog_"+this->dataPtr->applyWrenchVisual->Name());
    MouseEventHandler::Instance()->RemoveMoveFilter(
        "dialog_"+this->dataPtr->applyWrenchVisual->Name());
  }
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::OnManipulation()
{
  this->SetActive(false);
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::ActivateWindow()
{
  if (!this->isActiveWindow())
  {
    // Clear focus before activating not to trigger FucusIn
    QWidget *focusedWidget = this->focusWidget();
    if (focusedWidget)
      focusedWidget->clearFocus();

    this->activateWindow();
  }
}

/////////////////////////////////////////////////
bool ApplyWrenchDialog::eventFilter(QObject *_object, QEvent *_event)
{
  // Attach rotation tool to focused mode
  if (_event->type() == QEvent::FocusIn)
  {
    if (_object == this->dataPtr->forceMagSpin ||
        _object == this->dataPtr->forceXSpin ||
        _object == this->dataPtr->forceYSpin ||
        _object == this->dataPtr->forceZSpin ||
        _object == this->dataPtr->forcePosXSpin ||
        _object == this->dataPtr->forcePosYSpin ||
        _object == this->dataPtr->forcePosZSpin ||
       (_object == this->dataPtr->linksComboBox &&
        this->dataPtr->mode != Mode::TORQUE))
    {
      this->SetForce(this->dataPtr->forceVector);
    }
    else if (_object == this->dataPtr->torqueMagSpin ||
             _object == this->dataPtr->torqueXSpin ||
             _object == this->dataPtr->torqueYSpin ||
             _object == this->dataPtr->torqueZSpin ||
            (_object == this->dataPtr->linksComboBox &&
             this->dataPtr->mode == Mode::TORQUE))
    {
      this->SetTorque(this->dataPtr->torqueVector);
    }
  }
  // Deactivate this when another dialog is focused
  else if (_event->type() == QEvent::ActivationChange)
  {
    if (!this->dataPtr->mainWindow)
      return false;

    if (_object == this->dataPtr->mainWindow)
    {
      if (!this->isActiveWindow() &&
          !this->dataPtr->mainWindow->isActiveWindow())
      {
        this->SetActive(false);
      }
    }
  }
  // Activate when changing spinboxes with mousewheel
  else if (_event->type() == QEvent::Wheel)
  {
    this->ActivateWindow();
  }

  return false;
}

/////////////////////////////////////////////////
void ApplyWrenchDialog::changeEvent(QEvent *_event)
{
  // Focus in this dialog
  if (_event->type() == QEvent::ActivationChange)
  {
    // During tests it seems not to find main window, so this is true by default
    bool mainWindowActive = true;

    if (!this->dataPtr->mainWindow ||
        !this->dataPtr->mainWindow->isActiveWindow())
    {
      mainWindowActive = false;
    }

    this->SetActive(this->isActiveWindow() || mainWindowActive);
  }
}
