/*
 * Copyright 2020-2024 Toyota Connected North America
 *
 * 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.
 */

#pragma once

#include "camera.h"
#include "touch_pair.h"

#include <camutils/Manipulator.h>
#include <core/include/resource.h>
#include <core/scene/camera/exposure.h>
#include <core/scene/camera/lens_projection.h>
#include <core/scene/camera/projection.h>
#include <core/scene/geometry/ray.h>
#include <core/scene/view_target.h>
#include <filament/Camera.h>
#include <utils/EntityManager.h>

namespace plugin_filament_view {

using CameraManipulator = ::filament::camutils::Manipulator<float>;

class Camera;
class Exposure;
class Projection;
class ViewTarget;

// CameraManager is a per 'view target' system
class CameraManager {
 public:
  explicit CameraManager(ViewTarget* poOwner);

  void setDefaultFilamentCamera();

  void lookAtDefaultPosition() const;
  void setCameraLookat(filament::math::float3 eye,
                       filament::math::float3 center,
                       filament::math::float3 up) const;

  void destroyCamera() const;

  // Camera control
  void onAction(int32_t action,
                int32_t point_count,
                size_t point_data_size,
                const double* point_data);

  [[nodiscard]] float calculateAspectRatio() const;

  void updateCameraManipulator(const Camera* cameraInfo);

  // for cameras flagged for auto-orbit, will auto orbit
  // with their properties. Auto-screenshot, render-to-texture
  // would also use this in the future.
  void updateCamerasFeatures(float fElapsedTime);

  void updateCameraOnResize(uint32_t width, uint32_t height);

  void updateCamera(const Camera* cameraInfo);

  std::string updateExposure(Exposure* exposure) const;

  bool updateProjection(const Projection* projection) const;

  std::string updateLensProjection(const LensProjection* lensProjection);

  void updateCameraProjection();

  std::string updateCameraShift(std::vector<double>* shift) const;

  std::string updateCameraScaling(std::vector<double>* scaling) const;

  void setPrimaryCamera(std::unique_ptr<Camera> camera);
  void ChangePrimaryCameraMode(const std::string& szValue) const;
  // Passes weak ptr, dont keep a copy of this.
  Camera* poGetPrimaryCamera() { return primaryCamera_.get(); }

  // Disallow copy and assign.
  CameraManager(const CameraManager&) = delete;

  CameraManager& operator=(const CameraManager&) = delete;

  [[nodiscard]] std::pair<filament::math::float3, filament::math::float3>
  aGetRayInformationFromOnTouchPosition(TouchPair touch) const;

  [[nodiscard]] Ray oGetRayInformationFromOnTouchPosition(
      TouchPair touch) const;

  void vResetInertiaCameraToDefaultValues();

 private:
  static constexpr float kNearPlane = 0.05f;   // 5 cm
  static constexpr float kFarPlane = 1000.0f;  // 1 km
  static constexpr float kAperture = 16.0f;
  static constexpr float kShutterSpeed = 1.0f / 125;
  static constexpr float kSensitivity = 100.0f;
  static constexpr float kDefaultFocalLength = 28.0f;

  enum class Gesture { NONE, ORBIT, PAN, ZOOM };

  static constexpr int kGestureConfidenceCount = 2;
  static constexpr int kPanConfidenceDistance = 4;
  static constexpr int kZoomConfidenceDistance = 10;
  static constexpr float kZoomSpeed = 1.0f / 10.0f;

  static constexpr ::filament::math::float3 kDefaultObjectPosition = {
      0.0f, 0.0f, -4.0f};
  static constexpr ::filament::math::float3 kCameraCenter = {0.0f, 0.0f, 0.0f};
  static constexpr ::filament::math::float3 kCameraUp = {0.0f, 1.0f, 0.0f};
  static constexpr float kCameraDist = 3.0f;

  static constexpr int ACTION_DOWN = 0;
  static constexpr int ACTION_UP = 1;
  static constexpr int ACTION_MOVE = 2;
  static constexpr int ACTION_CANCEL = 3;

  // ImVec2: 2D vector used to store positions, sizes etc. [Compile-time
  // configurable type] This is a frequently used type in the API. Consider
  // using IM_VEC2_CLASS_EXTRA to create implicit cast from/to our preferred
  // type.
  struct ImVec2 {
    float x, y;

    ImVec2() { x = y = 0.0f; }

    ImVec2(float _x, float _y) {
      x = _x;
      y = _y;
    }

    float operator[](size_t idx) const {
      assert(idx <= 1);
      return (&x)[idx];
    }  // We very rarely use this [] operator, assert overhead is fine.
    float& operator[](size_t idx) {
      assert(idx <= 1);
      return (&x)[idx];
    }  // We very rarely use this [] operator, assert overhead is fine.
  };

  ::filament::Camera* camera_{};
  CameraManipulator* cameraManipulator_{};

  float cameraFocalLength_{};

  utils::Entity cameraEntity_;

  // Main display size, in pixels (generally == GetMainViewport()->Size)
  ImVec2 displaySize_;
  // Amount of pixels for each unit of DisplaySize. Based on
  // displayFramebufferScale_. Generally (1,1) on normal display, (2,2) on OSX
  // with Retina display.
  ImVec2 displayFramebufferScale_;

  Gesture currentGesture_ = Gesture::NONE;
  TouchPair previousTouch_;
  std::vector<TouchPair> tentativePanEvents_;
  std::vector<TouchPair> tentativeOrbitEvents_;
  std::vector<TouchPair> tentativeZoomEvents_;

  std::shared_ptr<Camera> primaryCamera_;

  // Used with Camera Inertia / zoom
  filament::math::float3 currentVelocity_;
  filament::math::float2 initialTouchPosition_;

  ViewTarget* m_poOwner;

  void endGesture();
  [[nodiscard]] bool isOrbitGesture() const;
  [[nodiscard]] bool isPanGesture() const;
  [[nodiscard]] bool isZoomGesture();
};
}  // namespace plugin_filament_view