#include "DebugCameraController.h"
#include "Utils/CasualCoreCandidates.h"
#include "Utils/Plane.h"
#include <RKCamera.h>

using namespace bne;

namespace details
{
  static Plane s_basePlane(RKVector(0, 0, 0, 0), RKVector(0, 1.f, 0, 0)); // RKVector::Zero, RKVector::Up - order of static initialisation issue
  static const float c_rorationSpeed(1.0f);
  static const float c_heightSpeed(10.0f);
  static const float c_zoomSpeed(1.0f);
  static const int c_panKey(17); // CTRL
  static const int c_heightKey(16); // SHIFT
}

SERIALIZE_BEGIN(DebugCameraController)
SERIALIZE_END()

DebugCameraController::DebugCameraController()
: Parent()
, m_pinchRefScreenPoint(0, 0)
, m_vBoundaryCenter(0, 0, 0, 0)
, m_CameraDesiredTarget(0, 0, 0, 1)
, m_currentScreenPoint(0, 0)
, m_previousScreenPoint(0, 0)
, m_mode(Mode_Rotate)
, m_yaw(0.0f)
, m_pitch(0.0f)
{
}

DebugCameraController::~DebugCameraController()
{
  RKCamera_SetDefault();
}

bool DebugCameraController::Init()
{
  bool success = Parent::Init();
  if (success)
  {
    SetUpdatable(true);
  }
  return success;
}

void DebugCameraController::Reset()
{
  m_pinchRefScreenPoint.Set(0, 0);
}

void DebugCameraController::SetCameraControl(RKCamera* camera)
{
  Parent::SetCameraControl(camera);
  if (camera)
  {
    m_vBoundaryCenter = ScreenToWorld(RKVector2(0, 0));
    m_CameraDesiredTarget = m_vBoundaryCenter;
    camera->SetZoom(2.5f);
  }
}

void DebugCameraController::UpdateFromCamera(RKCamera* camera)
{
  if ((camera != nullptr) && (camera->IsOrtho()))
  {
    RKVector cameraLookAt = GetLookAtPoint(camera);
    float currentZoom = (cameraLookAt - camera->GetWorldPosition()).Length();
    RKVector controllerLookAt = GetLookAtPoint(this);
    RKVector controllerDirection = GetWorldTransform().GetZAxis();
    controllerDirection.w = 0;
    controllerDirection.Normalize();
    RKVector newControllerPos = controllerLookAt - (controllerDirection * currentZoom);
    SetWorldPosition(newControllerPos);
  }
}

void DebugCameraController::CopySettingFromCameraCtrl(CasualCore::CameraController* ctrl)
{
  Parent::CopySettingFromCameraCtrl(ctrl);
  if (ctrl)
  {
    SetWorldPosition(ctrl->GetWorldPosition());
    SetWorldRotation(ctrl->GetWorldRotation());

    RKCamera* camera = GetCamera();
    if (camera != nullptr)
    {
      UpdateCamera(camera);
    }
  }
}

void DebugCameraController::Update(float time)
{
}

bool DebugCameraController::OnKeyDown(int keycode)
{
  switch (keycode)
  {
  case details::c_panKey:
    m_mode = Mode_Pan;
    break;

  case details::c_heightKey:
    m_mode = Mode_Height;
    break;

  default:
    m_mode = Mode_Rotate;
    break;
  }
  return true;
}

bool DebugCameraController::OnKeyUp(int keycode)
{
  m_mode = Mode_Rotate;
  return true;
}

void DebugCameraController::PanStarted(float x, float y)
{
  m_currentScreenPoint = RKVector2(x, y);
  m_previousScreenPoint = RKVector2(x, y);

  switch (m_mode)
  {
  case Mode_Rotate:
    RotateStarted(x, y);
    break;

  case Mode_Pan:
    m_CameraDesiredTarget = ScreenToWorld(RKVector2(0, 0));
    break;

  case Mode_Height:
    HeightStarted(x, y);
    break;

  default:
    RKASSERT(false, "DebugCameraController: Mode not implemented.");
    break;
  }
}

void DebugCameraController::PanChanged(float x, float y)
{
  m_previousScreenPoint = m_currentScreenPoint;
  m_currentScreenPoint = RKVector2(x, y);

  switch (m_mode)
  {
  case Mode_Rotate:
    RotateChanged(x, y);
    break;

  case Mode_Pan:
    if (GetCamera())
    {
      m_CameraDesiredTarget -= ScreenToWorld(RKVector2(x, y)) - ScreenToWorld(m_currentScreenPoint);
      GetCamera()->MoveToLookAt(m_CameraDesiredTarget);
    }
    break;

  case Mode_Height:
    HeightChanged(x, y);
    break;

  default:
    RKASSERT(false, "DebugCameraController: Mode not implemented.");
    break;
  }
}

void DebugCameraController::PanEnded(float x, float y)
{
  switch (m_mode)
  {
  case Mode_Rotate:
    RotateEnded(x, y);
    break;

  case Mode_Pan:
    // nothing to do
    break;

  case Mode_Height:
    break;

  default:
    RKASSERT(false, "DebugCameraController: Mode not implemented.");
    break;
  }
}

void DebugCameraController::RotateStarted(float x, float y)
{
}

void DebugCameraController::RotateChanged(float x, float y)
{
  float deltaYaw   = (m_previousScreenPoint.x - m_currentScreenPoint.x) * details::c_rorationSpeed;
  float deltaPitch = (m_currentScreenPoint.y - m_previousScreenPoint.y) * details::c_rorationSpeed;

  m_yaw += deltaYaw;
  m_pitch += deltaPitch;

  m_yaw = m_yaw < RKPI ? m_yaw : m_yaw - (2.0f * RKPI);
  m_yaw = m_yaw > -RKPI ? m_yaw : m_yaw + (2.0f * RKPI);
  m_pitch = RKCLAMP(-RKPI / 2.0f, RKPI / 2.0f, m_pitch);

  RKLOG("yaw = %f", m_yaw);
  RKLOG("pitch = %f", m_pitch);

  RKQuaternion rotation;
  rotation.FromEuler(m_pitch, m_yaw, 0.0f);

  RKCamera* camera = GetCamera();
  if (camera != nullptr)
  {
    camera->SetRotation(rotation);
  }
}

void DebugCameraController::HeightStarted(float x, float y)
{
  RKCamera* camera = GetCamera();
  if (camera != nullptr)
  {
    m_initialPosition = camera->GetWorldPosition();
  }
}

void DebugCameraController::HeightChanged(float x, float y)
{
  float deltaHeight = (m_previousScreenPoint.y - m_currentScreenPoint.y) * details::c_heightSpeed;
  RKCamera* camera = GetCamera();
  if (camera != nullptr)
  {
    camera->SetWorldPosition(m_initialPosition + RKVector(0.0f, deltaHeight, 0.0f, 0.0f));
  }
}

void DebugCameraController::Pinch(float x, float y)
{
  if (x > 1.0f)
  {
    ZoomBy(-1.0f);
  }
  else if (x < 1.0f)
  {
    ZoomBy(1.0f);
  }
  PinchEnded(x, y);
}

void DebugCameraController::UpdateZoomPoint(float x, float y)
{
  m_pinchRefScreenPoint.Set(x, y);
}

void DebugCameraController::ZoomBy(float zoomFactor)
{
  RKCamera* camera = GetCamera();
  if (camera != nullptr)
  {
    if (!camera->IsOrtho())
    {
      RKVector direction = camera->GetDirection();
      RKVector position = camera->GetWorldPosition();
      position += direction * details::c_zoomSpeed * zoomFactor;
      camera->SetWorldPosition(position);
    }
  }
}

RKVector DebugCameraController::ScreenToWorld(const RKVector2 & position)
{
  RKVector position_w(RKVector::Zero);
  RKCamera* camera = GetCamera();
  if (camera != nullptr)
  {
    RKVector origin, direction;
    GetCamera()->GetWorldRay(position, origin, direction);
    details::s_basePlane.Intersection(origin, direction, &position_w);
  }
  return position_w;
}

RKVector DebugCameraController::GetLookAtPoint(RKTransformObject* camera)
{
  RKVector lookAtPoint(RKVector::Zero);
  if (camera != nullptr)
  {
    RKVector direction = camera->GetWorldTransform().GetZAxis();
    direction.w = 0;
    direction.Normalize();
    details::s_basePlane.Intersection(camera->GetWorldPosition(), direction, &lookAtPoint);
  }
  return lookAtPoint;
}
