// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CC_INPUT_BROWSER_CONTROLS_OFFSET_MANAGER_H_
#define CC_INPUT_BROWSER_CONTROLS_OFFSET_MANAGER_H_

#include <memory>

#include "base/time/time.h"
#include "cc/input/browser_controls_state.h"
#include "cc/layers/layer_impl.h"
#include "cc/trees/browser_controls_params.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d_f.h"

namespace cc {

class BrowserControlsOffsetManagerClient;

// Manages the position of the browser controls.
class CC_EXPORT BrowserControlsOffsetManager {
 public:
  enum class AnimationDirection {
    NO_ANIMATION,
    SHOWING_CONTROLS,
    HIDING_CONTROLS
  };

  static std::unique_ptr<BrowserControlsOffsetManager> Create(
      BrowserControlsOffsetManagerClient* client,
      float controls_show_threshold,
      float controls_hide_threshold);
  BrowserControlsOffsetManager(const BrowserControlsOffsetManager&) = delete;
  virtual ~BrowserControlsOffsetManager();

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

  // The offset from the window top to the top edge of the controls. Runs from 0
  // (controls fully shown) to negative values (down is positive).
  float ControlsTopOffset() const;
  // The amount of offset of the web content area. Same as the current shown
  // height of the browser controls.
  float ContentTopOffset() const;
  float TopControlsShownRatio() const;
  float TopControlsHeight() const;
  float TopControlsMinHeight() const;
  // The minimum shown ratio top controls can have.
  float TopControlsMinShownRatio() const;
  // The current top controls min-height. If the min-height is changing with an
  // animation, this will return a value between the old min-height and the new
  // min-height, which is equal to the current visible min-height. Otherwise,
  // this will return the same value as |TopControlsMinHeight()|.
  float TopControlsMinHeightOffset() const;

  // The amount of offset of the web content area, calculating from the bottom.
  // Same as the current shown height of the bottom controls.
  float ContentBottomOffset() const;
  // Similar to TopControlsHeight(), this method should return a static value.
  // The current animated height should be acquired from ContentBottomOffset().
  float BottomControlsHeight() const;
  float BottomControlsMinHeight() const;
  float BottomControlsShownRatio() const;
  // The minimum shown ratio bottom controls can have.
  float BottomControlsMinShownRatio() const;
  // The current bottom controls min-height. If the min-height is changing with
  // an animation, this will return a value between the old min-height and the
  // new min-height, which is equal to the current visible min-height.
  // Otherwise, this will return the same value as |BottomControlsMinHeight()|.
  float BottomControlsMinHeightOffset() const;

  // Valid shown ratio range for the top controls. The values will be (0, 1) if
  // there is no animation running.
  std::pair<float, float> TopControlsShownRatioRange();
  // Valid shown ratio range for the bottom controls. The values will be (0, 1)
  // if there is no animation running.
  std::pair<float, float> BottomControlsShownRatioRange();

  bool HasAnimation();

  void UpdateBrowserControlsState(BrowserControlsState constraints,
                                  BrowserControlsState current,
                                  bool animate);

  BrowserControlsState PullConstraintForMainThread(
      bool* out_changed_since_commit);

  void OnBrowserControlsParamsChanged(bool animate_changes);

  void ScrollBegin();
  gfx::Vector2dF ScrollBy(const gfx::Vector2dF& pending_delta);
  void ScrollEnd();

  // The caller should ensure that |Pinch{Begin,End}| are called within
  // the scope of |Scroll{Begin,End}|.
  void PinchBegin();
  void PinchEnd();

  gfx::Vector2dF Animate(base::TimeTicks monotonic_time);

 protected:
  BrowserControlsOffsetManager(BrowserControlsOffsetManagerClient* client,
                               float controls_show_threshold,
                               float controls_hide_threshold);

 private:
  class Animation;

  void ResetAnimations();
  void SetupAnimation(AnimationDirection direction);
  void StartAnimationIfNecessary();
  void ResetBaseline();
  float OldTopControlsMinShownRatio();
  float OldBottomControlsMinShownRatio();
  void UpdateOldBrowserControlsParams();
  void InitAnimationForHeightChange(Animation* animation,
                                    float start_ratio,
                                    float stop_ratio);

  // The client manages the lifecycle of this.
  BrowserControlsOffsetManagerClient* client_;

  BrowserControlsState permitted_state_;

  // Accumulated scroll delta since last baseline reset
  float accumulated_scroll_delta_;

  // Content offset when last baseline reset occurred.
  float baseline_top_content_offset_;
  float baseline_bottom_content_offset_;

  // The percent height of the visible control such that it must be shown
  // when the user stops the scroll.
  float controls_show_threshold_;

  // The percent height of the visible control such that it must be hidden
  // when the user stops the scroll.
  float controls_hide_threshold_;

  bool pinch_gesture_active_;

  // Used to track whether the constraint has changed and we need up reflect
  // the changes to Blink.
  bool constraint_changed_since_commit_;

  // The old browser controls params that are used to figure out how to animate
  // the height and min-height changes.
  BrowserControlsParams old_browser_controls_params_;

  // Whether a min-height change animation is in progress.
  bool top_min_height_change_in_progress_;
  bool bottom_min_height_change_in_progress_;

  // Current top/bottom controls min-height.
  float top_controls_min_height_offset_;
  float bottom_controls_min_height_offset_;

  // Class that holds and manages the state of the controls animations.
  class Animation {
   public:
    Animation();

    // Whether the animation is initialized with a direction and start and stop
    // values.
    bool IsInitialized() { return initialized_; }
    AnimationDirection Direction() { return direction_; }
    void Initialize(AnimationDirection direction,
                    float start_value,
                    float stop_value,
                    int64_t duration,
                    bool jump_to_end_on_reset);
    // Returns the animated value for the given monotonic time tick if the
    // animation is initialized. Otherwise, returns |base::nullopt|.
    base::Optional<float> Tick(base::TimeTicks monotonic_time);
    // Set the minimum and maximum values the animation can have.
    void SetBounds(float min, float max);
    // Reset the properties. If |skip_to_end_on_reset_| is false, this function
    // will return |base::nullopt|. Otherwise, it will return the end value
    // (clamped to min-max).
    base::Optional<float> Reset();

    // Returns the value the animation will end on. This will be the stop_value
    // passed to the constructor clamped by the currently configured bounds.
    float FinalValue();

    // Return the bounds.
    float min_value() { return min_value_; }
    float max_value() { return max_value_; }

   private:
    bool IsComplete(float value);

    // Whether the animation is running.
    bool started_ = false;
    // Whether the animation is initialized by setting start and stop time and
    // values.
    bool initialized_ = false;
    AnimationDirection direction_ = AnimationDirection::NO_ANIMATION;
    // Monotonic start and stop times.
    base::TimeTicks start_time_;
    base::TimeTicks stop_time_;
    // Animation duration.
    base::TimeDelta duration_;
    // Start and stop values.
    float start_value_ = 0.f;
    float stop_value_ = 0.f;
    // Minimum and maximum values the animation can have, used to decide if the
    // animation is complete.
    float min_value_ = 0.f;
    float max_value_ = 1.f;
    // Whether to fast-forward to end when reset. It is still BCOM's
    // responsibility to actually set the shown ratios using the value returned
    // by ::Reset().
    bool jump_to_end_on_reset_ = false;
  };

  Animation top_controls_animation_;
  Animation bottom_controls_animation_;
};

}  // namespace cc

#endif  // CC_INPUT_BROWSER_CONTROLS_OFFSET_MANAGER_H_
