/*
 * Copyright (c) 2012, Willow Garage, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Willow Garage, Inc. nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <QColor>
#include <QFont>
#include <QKeyEvent>

#include <OGRE/OgreCamera.h>
#include <OGRE/OgreSceneManager.h>
#include <OGRE/OgreSceneNode.h>

#include <rviz/display_context.h>
#include <rviz/frame_manager.h>
#include <rviz/load_resource.h>
#include <rviz/properties/enum_property.h>
#include <rviz/properties/float_property.h>
#include <rviz/properties/bool_property.h>
#include <rviz/render_panel.h>
#include <rviz/selection/selection_manager.h>
#include <rviz/view_manager.h>
#include <rviz/viewport_mouse_event.h>
#include <rviz/window_manager_interface.h>
#include <rviz/ogre_helpers/render_system.h>

#include <rviz/view_controller.h>

namespace rviz
{
ViewController::ViewController()
  : context_(nullptr), camera_(nullptr), is_active_(false), type_property_(nullptr)
{
  near_clip_property_ =
      new FloatProperty("Near Clip Distance", 0.01f,
                        "Anything closer to the camera than this threshold will not get rendered.", this,
                        SLOT(updateNearClipDistance()));
  near_clip_property_->setMin(0.001);
  near_clip_property_->setMax(10000);

  stereo_enable_ = new BoolProperty("Enable Stereo Rendering", true,
                                    "Render the main view in stereo if supported."
                                    "  On Linux this requires a recent version of Ogre and"
                                    " an NVIDIA Quadro card with 3DVision glasses.",
                                    this, SLOT(updateStereoProperties()));
  stereo_eye_swap_ = new BoolProperty("Swap Stereo Eyes", false,
                                      "Swap eyes if the monitor shows the left eye on the right.",
                                      stereo_enable_, SLOT(updateStereoProperties()), this);
  stereo_eye_separation_ =
      new FloatProperty("Stereo Eye Separation", 0.06f, "Distance between eyes for stereo rendering.",
                        stereo_enable_, SLOT(updateStereoProperties()), this);
  stereo_focal_distance_ = new FloatProperty("Stereo Focal Distance", 1.0f,
                                             "Distance from eyes to screen.  For stereo rendering.",
                                             stereo_enable_, SLOT(updateStereoProperties()), this);
  invert_z_ =
      new BoolProperty("Invert Z Axis", false, "Invert camera's Z axis for Z-down environments/models.",
                       this, SLOT(updateStereoProperties()));
}

void ViewController::initialize(DisplayContext* context)
{
  context_ = context;

  std::stringstream ss;
  static int count = 0;
  ss << "ViewControllerCamera" << count++;
  camera_ = context_->getSceneManager()->createCamera(ss.str());
  context_->getSceneManager()->getRootSceneNode()->attachObject(camera_);

  setValue(formatClassId(getClassId()));
  setReadOnly(true);

  // Do subclass initialization.
  onInitialize();

  cursor_ = getDefaultCursor();

  standard_cursors_[Default] = getDefaultCursor();
  standard_cursors_[Rotate2D] = makeIconCursor("package://rviz/icons/rotate.svg");
  standard_cursors_[Rotate3D] = makeIconCursor("package://rviz/icons/rotate_cam.svg");
  standard_cursors_[MoveXY] = makeIconCursor("package://rviz/icons/move2d.svg");
  standard_cursors_[MoveZ] = makeIconCursor("package://rviz/icons/move_z.svg");
  standard_cursors_[Zoom] = makeIconCursor("package://rviz/icons/zoom.svg");
  standard_cursors_[Crosshair] = makeIconCursor("package://rviz/icons/crosshair.svg");

  updateNearClipDistance();
  updateStereoProperties();

  if (!RenderSystem::get()->isStereoSupported())
  {
    stereo_enable_->setBool(false);
    stereo_enable_->hide();
  }
}

ViewController::~ViewController()
{
  context_->getSceneManager()->destroyCamera(camera_);
}

QString ViewController::formatClassId(const QString& class_id)
{
  QStringList id_parts = class_id.split("/");
  if (id_parts.size() != 2)
  {
    // Should never happen with pluginlib class ids, which are
    // formatted like "package_name/class_name".  Not worth crashing
    // over though.
    return class_id;
  }
  else
  {
    return id_parts[1] + " (" + id_parts[0] + ")";
  }
}

QVariant ViewController::getViewData(int column, int role) const
{
  if (role == Qt::TextColorRole)
    return QVariant();

  if (is_active_)
  {
    switch (role)
    {
    case Qt::FontRole:
    {
      QFont font;
      font.setBold(true);
      return font;
    }
    }
  }
  return Property::getViewData(column, role);
}

Qt::ItemFlags ViewController::getViewFlags(int column) const
{
  if (is_active_)
  {
    return Property::getViewFlags(column);
  }
  else
  {
    return Property::getViewFlags(column) | Qt::ItemIsDragEnabled;
  }
}

void ViewController::activate()
{
  is_active_ = true;
  onActivate();
}

void ViewController::emitConfigChanged()
{
  Q_EMIT configChanged();
}

void ViewController::load(const Config& config)
{
  // Load the name by hand.
  QString name;
  if (config.mapGetString("Name", &name))
  {
    setName(name);
  }
  // Load all sub-properties the same way the base class does.
  Property::load(config);
}

void ViewController::save(Config config) const
{
  config.mapSetValue("Class", getClassId());
  config.mapSetValue("Name", getName());

  Property::save(config);
}

void ViewController::handleKeyEvent(QKeyEvent* event, RenderPanel* panel)
{
  if (event->key() == Qt::Key_F && panel->getViewport() && context_->getSelectionManager())
  {
    QPoint mouse_rel_panel = panel->mapFromGlobal(QCursor::pos());
    Ogre::Vector3 point_rel_world; // output of get3DPoint().
    if (context_->getSelectionManager()->get3DPoint(panel->getViewport(), mouse_rel_panel.x(),
                                                    mouse_rel_panel.y(), point_rel_world))
    {
      lookAt(point_rel_world);
    }
  }

  if (event->key() == Qt::Key_Z)
  {
    reset();
  }
}

void ViewController::setCursor(CursorType cursor_type)
{
  cursor_ = standard_cursors_[cursor_type];
}

void ViewController::lookAt(float x, float y, float z)
{
  Ogre::Vector3 point(x, y, z);
  lookAt(point);
}

void ViewController::setStatus(const QString& message)
{
  if (context_)
  {
    context_->setStatus(message);
  }
}

void ViewController::updateNearClipDistance()
{
  float n = near_clip_property_->getFloat();
  camera_->setNearClipDistance(n);
}

void ViewController::updateStereoProperties()
{
  if (stereo_enable_->getBool())
  {
    float focal_dist = stereo_focal_distance_->getFloat();
    float eye_sep = stereo_eye_swap_->getBool() ? -stereo_eye_separation_->getFloat() :
                                                  stereo_eye_separation_->getFloat();
    camera_->setFrustumOffset(0.5f * eye_sep, 0.0f);
    camera_->setFocalLength(focal_dist);
    stereo_eye_swap_->show();
    stereo_eye_separation_->show();
    stereo_focal_distance_->show();
  }
  else
  {
    camera_->setFrustumOffset(0.0f, 0.0f);
    camera_->setFocalLength(1.0f);
    stereo_eye_swap_->hide();
    stereo_eye_separation_->hide();
    stereo_focal_distance_->hide();
  }
}

void ViewController::updateInvertZAxis()
{
  // We don't seem to need to do anything here.
}

} // end namespace rviz
