/*
 * Copyright 2019 University of Toronto
 *
 * 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.
 *
 * Authors: Mario Badr, Sameh Attia and Tanner Young-Schultz
 */

#ifndef EZGL_CAMERA_HPP
#define EZGL_CAMERA_HPP

#include "ezgl/point.hpp"
#include "ezgl/rectangle.hpp"

namespace ezgl {

/**
 * Manages the transformations between coordinate systems.
 * Application code doesn't (and can't) call these functions; they are for ezgl internal use.
 *
 * The camera class manages transformations between a GTK widget, world, and "screen" coordinate system. A GTK widget
 * has dimensions that change based on the user, and its aspect ratio may not match the world coordinate system. The
 * camera maintains a "screen" within the widget that keeps the same aspect ratio as the world coordinate system,
 * regardless of the dimensions of the widget.
 *
 * A camera object can only be created by an ezgl::canvas object, who has the responsibility of updating the camera with
 * changes to the widget's dimensions. The only state that can be mutated outside the library is the camera's world
 * coordinate system.
 */
class camera {
public:
  /**
   * Convert a point in world coordinates to screen coordinates.
   */
  point2d world_to_screen(point2d world_coordinates) const;

  /**
   * Convert a point in widget coordinates to screen coordinates.
   */
  point2d widget_to_screen(point2d widget_coordinates) const;

  /**
   * Convert a point in widget coordinates to world coordinates.
   */
  point2d widget_to_world(point2d widget_coordinates) const;

  /**
   * Get the currently visible bounds of the world.
   */
  rectangle get_world() const
  {
    return m_world;
  }

  /**
   * Get the dimensions of the screen.
   */
  rectangle get_screen() const
  {
    return m_screen;
  }

  /**
   * Get the dimensions of the widget.
   */
  rectangle get_widget() const
  {
    return m_widget;
  }

  /**
   * Get the initial bounds of the world. Needed for zoom_fit
   */
  rectangle get_initial_world() const
  {
    return m_initial_world;
  }

  /**
   * Update the visible bounds of the world.
   *
   * Used in panning and zooming.
   */
  void set_world(rectangle new_world);

  /**
   * Reset the world coordinates
   *
   * Used by change_canvas_world_coordinates().
   */
  void reset_world(rectangle new_world);

  /**
   * Get the screen to world scaling factor.
   */
  point2d get_world_scale_factor() const
  {
    return m_screen_to_world;
  }

protected:
  // Only an ezgl::canvas can create a camera.
  friend class canvas;

  /**
   * Create a camera.
   *
   * @param bounds The initial bounds of the coordinate system.
   */
  explicit camera(rectangle bounds);

  /**
   * Update the dimensions of the widget.
   *
   * This will change the screen where the world is projected. The screen will maintain the aspect ratio of the world's
   * coordinate system while being centered within the screen.
   *
   * @see canvas::configure_event
   */
  void update_widget(int width, int height);

  /**
   * Update the scaling factors.
   */
  void update_scale_factors();

private:
  // The dimensions of the parent widget.
  rectangle m_widget = {{0, 0}, 1.0, 1.0};

  // The dimensions of the world (user-defined bounding box).
  rectangle m_world;

  // The dimensions of the screen, which may not match the widget.
  rectangle m_screen;

  // The dimensions of the initial world (user-defined bounding box). Needed for zoom_fit
  rectangle m_initial_world;

  // The x and y scaling factors.
  point2d m_world_to_widget = {1.0, 1.0};
  point2d m_widget_to_screen = {1.0, 1.0};
  point2d m_screen_to_world = {1.0, 1.0};
};
}

#endif //EZGL_CAMERA_HPP