#ifndef _GAME_CAM_CONTROLLER_H_
#define _GAME_CAM_CONTROLLER_H_

#include "Camera/InteractiveCameraController.h"
#include "Utils/CasualCoreCandidates.h"

namespace bne
{
  BEGIN_CUSTOMOBJECT(GameCameraController, ::InteractiveCameraController)
public:

  struct Parameters
  {
    float minimumZoom = 20.f;
    float maximumZoom = 35.f;
    float zoomExtension = 10.f;

    float panDeceleration = 0.3f;
    float panMaxInertialSpeed = 0.8f;
    float panExtension = 5.f;

    bool enableCameraExtents = true;
    bool bOverrideViewportCentre = false;
    RKVector viewportCentreOverride = RKVector::Zero;
    float viewportExtentsPositiveX = 17.f;
    float viewportExtentsPositiveZ = 11.f;
    float viewportExtentsNegativeX = 9.f;
    float viewportExtentsNegativeZ = 17.f;
    
    bool adjustCameraAngleWithZoom = false;
    float cameraYawAtMinZoom = 75.f;
    float cameraYawAtMaxZoom = 85.f;
    
    bool adjustCameraFOVWithZoom = false;
    float cameraFOVAtMinZoom = 25.f;
    float cameraFOVAtMaxZoom = 15.f;
    
    float rubberBandRestoreTime = 1.f;

    bool disableInput = false;
  };

public:

  GameCameraController();
  ~GameCameraController();

  inline const Parameters& GetParameters() const { return m_pars; }
  inline void SetParameters(const Parameters& pars) { m_pars = pars; }

  void MoveCameraTowardDesiredTarget(const RKVector& target);

  /// \brief Get the bounded position of a camera.
  /// \param a_camera The camera to bound.
  /// \param a_retPos The bounded position.
  /// \returns true if the camera was bounded; otherwise, false.
  bool GetBoundedPosition(const RKCamera& a_camera, RKVector& a_retPos);

  /// \brief Calculate the field of view for the current device
  /// \param a_srcFOV The source vertical FOV (degrees)
  /// \param a_srcAspectRatio The source aspect ratio
  /// \return The new vertical FOV for the current device is returned (degrees)
  static float CalculateDeviceFOV(float a_srcFOV, float a_srcAspectRatio = (16.0f /9.0f));

  //+ overrides
  virtual bool PostInit() override;
  virtual void Reset() override;
  virtual void SetCameraControl(RKCamera* camera) override;
  virtual void UpdateFromCamera(RKCamera* camera) override;

  virtual void LockCameraInput(bool locked) override;
  virtual void EnableCameraExtents(bool enabled) override;

  virtual void CopySettingFromCameraCtrl(CasualCore::CameraController* ctrl) override;

  virtual void PinchStarted(float x, float y) override;
  virtual void PinchChanged(float scale, float velocity) override;
  virtual void PinchEnded(float x, float y) override;
  virtual void Pinch(float x, float y) override;

  virtual void PanStarted(float x, float y) override;
  virtual void PanChanged(float x, float y) override;
  virtual void PanEnded(float x, float y) override;

  virtual void OnTap(float x, float y) override;
  virtual void OnPinchGesture(int gestureId, float x, float y) override;
  virtual void OnPanGesture(int gestureId, float x, float y) override;

  virtual void Update(float time) override;
  //- overrides

private:
  //+ serialized data
  Parameters m_pars;
  //- serialized data

  float m_defaultZoom = 25.f;
  float m_ZoomMultiplier = 1.f;

  RKVector m_vBoundaryCenter = RKVector::Zero;
  RKVector m_vBoundaryXAxis = RKVector::Zero;
  RKVector m_vBoundaryZAxis = RKVector::Zero;

  RKVector m_CameraDesiredTarget{ 0, 0, 0, 1 };
  
  float     m_fPanSpeed = 0;
  RKVector2 m_panCurrentScreenPoint{ 0, 0 };
  RKVector2 m_panPreviousScreenPoint{ 0, 0 };
  RKVector2 m_panDirection{ 0, 0 };

  float m_panRestoreTime = 0.f;
  RKVector m_panRestoreStartPoint = RKVector::Zero;
  RKVector m_panRestoreEndPoint = RKVector::Zero;

  float m_zoomRestoreTime = 0;
  float m_zoomRestoreStart = 0;
  float m_zoomRestoreDelta = 0;

  RKVector m_RefPos = RKVector::Zero;
  RKVector m_StartPos = RKVector::Zero;

  RKQuaternion m_RefQuat = RKQuaternion::Identity;

  bool m_bRestoreAfterZoomin = false;
  bool m_bRestoreAfterPan = false;

  bool m_bUserInput = false;
  bool m_bPanApplyInertia = false;
  bool m_bInPan = false;
  bool m_bInPinch = false;

  /// \brief Initialise the restoration from a pan extension.
  void InitPanRestore();

  void RestoreFromZoomExtension(float a_fTime);
  void RestoreFromPanExtension(float a_fTime);

  struct ViewportExtentData
  {
    float topExcess;
    float rightExcess;
    float bottomExcess;
    float leftExcess;
  };
  void GetViewportWorldExtents(const RKCamera& a_camera, ViewportExtentData& a_extentData);

  /// \brief Apply the extents to the camera.
  /// \returns true if the camera was moved into bounds; otherwise, false.
  bool ApplyExtents();

  void ZoomBy(float zoom_delta);
  float ApplyZoomLimits(float zoom) const;
  void SetZoomCam();

  void StopInertialMovement();
  void UpdateCameraInertia(float time);

  RKVector ScreenToWorld(const RKVector2& position);
  RKVector GetLookAtPoint(RKTransformObject* camera);

  inline void SetUserInput(bool a_userInput) { m_bUserInput = a_userInput; }

  END_CUSTOMOBJECT(GameCameraController)
} // namespace bne

#endif //_GAME_CAM_CONTROLLER_H_
