/*
 * Copyright (c) 2011-2019, The DART development contributors
 * All rights reserved.
 *
 * The list of contributors can be found at:
 *   https://github.com/dartsim/dart/blob/master/LICENSE
 *
 * This file is provided under the following "BSD-style" License:
 *   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.
 *   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 HOLDER 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 <osgGA/GUIEventAdapter>

#include "dart/gui/osg/DefaultEventHandler.hpp"
#include "dart/gui/osg/MouseEventHandler.hpp"
#include "dart/gui/osg/ShapeFrameNode.hpp"
#include "dart/gui/osg/Utils.hpp"
#include "dart/gui/osg/Viewer.hpp"
#include "dart/gui/osg/render/ShapeNode.hpp"

#include "dart/dynamics/Entity.hpp"
#include "dart/dynamics/ShapeFrame.hpp"

#include <iostream>

namespace dart {
namespace gui {
namespace osg {

DefaultEventHandler::DefaultEventHandler(Viewer* _viewer)
  : mViewer(_viewer),
    mLastCursorPosition(Eigen::Vector2d::Zero()),
    mLastModKeyMask(0)
{
  mViewer->addInstructionText("Spacebar:     Turn simulation on/off\n");
  mViewer->addInstructionText("Ctrl+H:       Turn headlights on/off\n");

  for (std::size_t i = 0; i < NUM_MOUSE_BUTTONS; ++i)
    for (std::size_t j = 0; j < BUTTON_NOTHING; ++j)
      mSuppressButtonPicks[i][j] = false;
  mSuppressMovePicks = false;

  clearButtonEvents();
}

//==============================================================================
DefaultEventHandler::~DefaultEventHandler()
{
  // Do nothing
}

//==============================================================================
MouseButtonEvent DefaultEventHandler::getButtonEvent(MouseButton button) const
{
  return mLastButtonEvent[button];
}

//==============================================================================
int DefaultEventHandler::getModKeyMask() const
{
  return mLastModKeyMask;
}

//==============================================================================
double DefaultEventHandler::getWindowCursorX() const
{
  return mLastCursorPosition[0];
}

//==============================================================================
double DefaultEventHandler::getWindowCursorY() const
{
  return mLastCursorPosition[1];
}

//==============================================================================
Eigen::Vector3d DefaultEventHandler::getDeltaCursor(
    const Eigen::Vector3d& _fromPosition,
    ConstraintType _constraint,
    const Eigen::Vector3d& _constraintVector) const
{
  ::osg::Vec3d eye, center, up;
  mViewer->getCamera()->getViewMatrixAsLookAt(eye, center, up);

  Eigen::Vector3d near, far;
  getNearAndFarPointUnderCursor(near, far);
  Eigen::Vector3d v1 = far - near;

  if (LINE_CONSTRAINT == _constraint)
  {
    const Eigen::Vector3d& b1 = near;
    const Eigen::Vector3d& v2 = _constraintVector;
    const Eigen::Vector3d& b2 = _fromPosition;

    double v1_v1 = v1.dot(v1);
    double v2_v2 = v2.dot(v2);
    double v2_v1 = v2.dot(v1);

    double denominator = v1_v1 * v2_v2 - v2_v1 * v2_v1;
    double s;
    if (fabs(denominator) < 1e-10)
      s = 0;
    else
      s = (v1_v1 * (v2.dot(b1) - v2.dot(b2))
           + v2_v1 * (v1.dot(b2) - v1.dot(b1)))
          / denominator;

    return v2 * s;
  }
  else if (PLANE_CONSTRAINT == _constraint)
  {
    const Eigen::Vector3d& n = _constraintVector;
    double s = n.dot(_fromPosition - near) / n.dot(v1);
    return near - _fromPosition + s * v1;
  }
  else
  {
    Eigen::Vector3d n = osgToEigVec3(center - eye);
    double s = n.dot(_fromPosition - near) / n.dot(v1);
    return near - _fromPosition + s * v1;
  }

  return Eigen::Vector3d::Zero();
}

//==============================================================================
void DefaultEventHandler::getNearAndFarPointUnderCursor(
    Eigen::Vector3d& near, Eigen::Vector3d& far, double distance) const
{
  ::osg::Camera* C = mViewer->getCamera();
  ::osg::Matrix VPW = C->getViewMatrix() * C->getProjectionMatrix()
                      * C->getViewport()->computeWindowMatrix();
  ::osg::Matrix invVPW;
  invVPW.invert(VPW);

  double x = getWindowCursorX(), y = getWindowCursorY();
  auto osgNear = ::osg::Vec3d(x, y, 0.0) * invVPW;
  auto osgFar = ::osg::Vec3d(x, y, distance) * invVPW;

  near = osgToEigVec3(osgNear);
  far = osgToEigVec3(osgFar);
}

//==============================================================================
const std::vector<PickInfo>& DefaultEventHandler::getButtonPicks(
    MouseButton button, MouseButtonEvent event) const
{
  if (BUTTON_NOTHING == event)
    return mMovePicks;

  return mButtonPicks[button][event];
}

//==============================================================================
const std::vector<PickInfo>& DefaultEventHandler::getMovePicks() const
{
  return mMovePicks;
}

//==============================================================================
void DefaultEventHandler::suppressButtonPicks(
    MouseButton button, MouseButtonEvent event)
{
  if (BUTTON_NOTHING == event)
    mSuppressMovePicks = true;
  else
    mSuppressButtonPicks[button][event] = true;
}

//==============================================================================
void DefaultEventHandler::suppressMovePicks()
{
  mSuppressMovePicks = true;
}

//==============================================================================
void DefaultEventHandler::activateButtonPicks(
    MouseButton button, MouseButtonEvent event)
{
  if (BUTTON_NOTHING == event)
    mSuppressMovePicks = false;
  else
    mSuppressButtonPicks[button][event] = false;
}

//==============================================================================
void DefaultEventHandler::activateMovePicks()
{
  mSuppressMovePicks = false;
}

//==============================================================================
void DefaultEventHandler::pick(
    std::vector<PickInfo>& infoVector, const ::osgGA::GUIEventAdapter& ea)
{
  ::osgUtil::LineSegmentIntersector::Intersections hlist;

  infoVector.clear();
  if (mViewer->computeIntersections(ea, hlist))
  {
    infoVector.reserve(hlist.size());
    for (const ::osgUtil::LineSegmentIntersector::Intersection& intersect :
         hlist)
    {
      ::osg::Drawable* drawable = intersect.drawable;
      render::ShapeNode* shape
          = dynamic_cast<render::ShapeNode*>(drawable->getParent(0));
      if (shape)
      {
        PickInfo info;
        info.shape = shape->getShape();
        info.frame = shape->getParentShapeFrameNode()->getShapeFrame();
        info.normal
            = osgToEigVec3(intersect.getWorldIntersectNormal()).cast<double>();
        info.position = osgToEigVec3(intersect.getWorldIntersectPoint());

        infoVector.push_back(info);
      }
    }
  }
}

//==============================================================================
void DefaultEventHandler::addMouseEventHandler(MouseEventHandler* handler)
{
  mMouseEventHandlers.insert(handler);
  handler->mEventHandler = this;
  handler->addSubject(this);
}

//==============================================================================
const std::set<MouseEventHandler*>& DefaultEventHandler::getMouseEventHandlers()
    const
{
  return mMouseEventHandlers;
}

//==============================================================================
static bool wasActive(MouseButtonEvent event)
{
  return ((event == BUTTON_PUSH) || (event == BUTTON_DRAG));
}

//==============================================================================
static void assignEventToButtons(
    MouseButtonEvent (&mLastButtonEvent)[NUM_MOUSE_BUTTONS],
    const ::osgGA::GUIEventAdapter& ea)
{
  MouseButtonEvent event = BUTTON_NOTHING;
  if (ea.getEventType() == ::osgGA::GUIEventAdapter::PUSH)
    event = BUTTON_PUSH;
  else if (ea.getEventType() == ::osgGA::GUIEventAdapter::DRAG)
    event = BUTTON_DRAG;
  else if (ea.getEventType() == ::osgGA::GUIEventAdapter::RELEASE)
    event = BUTTON_RELEASE;

  if (BUTTON_RELEASE == event)
  {
    if ((ea.getButtonMask() & ::osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON) == 0
        && wasActive(mLastButtonEvent[LEFT_MOUSE]))
      mLastButtonEvent[LEFT_MOUSE] = event;

    if ((ea.getButtonMask() & ::osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON) == 0
        && wasActive(mLastButtonEvent[RIGHT_MOUSE]))
      mLastButtonEvent[RIGHT_MOUSE] = event;

    if ((ea.getButtonMask() & ::osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON)
            == 0
        && wasActive(mLastButtonEvent[MIDDLE_MOUSE]))
      mLastButtonEvent[MIDDLE_MOUSE] = event;
  }
  else
  {
    if (ea.getButtonMask() & ::osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
      mLastButtonEvent[LEFT_MOUSE] = event;

    if (ea.getButtonMask() & ::osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON)
      mLastButtonEvent[RIGHT_MOUSE] = event;

    if (ea.getButtonMask() & ::osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON)
      mLastButtonEvent[MIDDLE_MOUSE] = event;
  }
}

//==============================================================================
bool DefaultEventHandler::handle(
    const ::osgGA::GUIEventAdapter& ea, ::osgGA::GUIActionAdapter&)
{
  mLastModKeyMask = ea.getModKeyMask();

  switch (ea.getEventType())
  {
    case ::osgGA::GUIEventAdapter::PUSH:
    case ::osgGA::GUIEventAdapter::DRAG:
    case ::osgGA::GUIEventAdapter::RELEASE:
    case ::osgGA::GUIEventAdapter::MOVE:
      mLastCursorPosition[0] = ea.getX();
      mLastCursorPosition[1] = ea.getY();

      break;

    default:
      break;
  }

  switch (ea.getEventType())
  {
    case ::osgGA::GUIEventAdapter::KEYDOWN:
    {
      switch (ea.getKey())
      {
        case 8: // ctrl+h
        {
          mViewer->switchHeadlights(!mViewer->checkHeadlights());
          return true;
        }

        case ' ':
        {
          if (mViewer->isAllowingSimulation())
          {
            mViewer->simulate(!mViewer->isSimulating());
            return true;
          }
          break;
        }
      }
      break;
    }

    case ::osgGA::GUIEventAdapter::MOVE:
    {
      if (!mSuppressMovePicks)
        pick(mMovePicks, ea);

      triggerMouseEventHandlers();
      break;
    }

    case ::osgGA::GUIEventAdapter::PUSH:
    case ::osgGA::GUIEventAdapter::DRAG:
    case ::osgGA::GUIEventAdapter::RELEASE:

      assignEventToButtons(mLastButtonEvent, ea);
      eventPick(ea);

      mViewer->updateDragAndDrops();

      triggerMouseEventHandlers();
      break;

    default:
      break;
  }

  return false;
}

//==============================================================================
void DefaultEventHandler::triggerMouseEventHandlers()
{
  for (MouseEventHandler* h : mMouseEventHandlers)
  {
    h->update();
  }
}

//==============================================================================
void DefaultEventHandler::eventPick(const ::osgGA::GUIEventAdapter& ea)
{
  MouseButtonEvent mbe;
  switch (ea.getEventType())
  {
    case ::osgGA::GUIEventAdapter::PUSH:
      mbe = BUTTON_PUSH;
      break;
    case ::osgGA::GUIEventAdapter::DRAG:
      mbe = BUTTON_DRAG;
      break;
    case ::osgGA::GUIEventAdapter::RELEASE:
      mbe = BUTTON_RELEASE;
      break;
    default:
      return;
  }

  if (((ea.getButtonMask() & ::osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
       && !mSuppressButtonPicks[LEFT_MOUSE][mbe])
      || ((ea.getButtonMask() & ::osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON)
          && !mSuppressButtonPicks[RIGHT_MOUSE][mbe])
      || ((ea.getButtonMask() & ::osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON)
          && !mSuppressButtonPicks[MIDDLE_MOUSE][mbe]))
  {
    pick(mTempPicks, ea);

    if (ea.getButtonMask() & ::osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
      mButtonPicks[LEFT_MOUSE][mbe] = mTempPicks;

    if (ea.getButtonMask() & ::osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON)
      mButtonPicks[RIGHT_MOUSE][mbe] = mTempPicks;

    if (ea.getButtonMask() & ::osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON)
      mButtonPicks[MIDDLE_MOUSE][mbe] = mTempPicks;
  }
}

//==============================================================================
void DefaultEventHandler::clearButtonEvents()
{
  for (std::size_t i = 0; i < NUM_MOUSE_BUTTONS; ++i)
    mLastButtonEvent[i] = BUTTON_NOTHING;
}

//==============================================================================
void DefaultEventHandler::handleDestructionNotification(
    const dart::common::Subject* _subject)
{
  MouseEventHandler* meh = const_cast<MouseEventHandler*>(
      dynamic_cast<const MouseEventHandler*>(_subject));
  std::set<MouseEventHandler*>::iterator it = mMouseEventHandlers.find(meh);
  if (it != mMouseEventHandlers.end())
    mMouseEventHandlers.erase(it);
}

} // namespace osg
} // namespace gui
} // namespace dart
