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

#include "ui/gfx/animation/tween.h"

#include <math.h>
#include <stdint.h>

#include <algorithm>

#include "base/check_op.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "ui/gfx/geometry/cubic_bezier.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/transform_operations.h"

#if BUILDFLAG(IS_WIN)
#include <float.h>
#endif

namespace gfx {

// static
double Tween::CalculateValue(Tween::Type type, double state) {
  DCHECK_GE(state, 0);
  DCHECK_LE(state, 1);

  switch (type) {
    case EASE_IN:
      return pow(state, 2);

    case EASE_IN_2:
      return pow(state, 4);

    case EASE_IN_OUT:
      if (state < 0.5)
        return pow(state * 2, 2) / 2.0;
      return 1.0 - (pow((state - 1.0) * 2, 2) / 2.0);

    case EASE_IN_OUT_2:
      return gfx::CubicBezier(0.33, 0, 0.67, 1).Solve(state);

    case EASE_OUT_3:
      return gfx::CubicBezier(0.6, 0, 0, 1).Solve(state);

    case EASE_OUT_4:
      return gfx::CubicBezier(1, 0, 0.8, 1).Solve(state);

    case LINEAR:
      return state;

    case EASE_OUT:
      return 1.0 - pow(1.0 - state, 2);

    case EASE_OUT_2:
      return gfx::CubicBezier(0.4, 0, 0, 1).Solve(state);

    case SMOOTH_IN_OUT:
      return sin(state);

    case FAST_OUT_SLOW_IN:
      return gfx::CubicBezier(0.4, 0, 0.2, 1).Solve(state);

    case FAST_OUT_SLOW_IN_2:
      return gfx::CubicBezier(0.2, 0, 0.2, 1).Solve(state);

    case FAST_OUT_SLOW_IN_3:
      return gfx::CubicBezier(0.2, 0, 0, 1).Solve(state);

    case LINEAR_OUT_SLOW_IN:
      return gfx::CubicBezier(0, 0, .2, 1).Solve(state);

    case SLOW_OUT_LINEAR_IN:
      return gfx::CubicBezier(0, 0, 1, .2).Solve(state);

    case FAST_OUT_LINEAR_IN:
      return gfx::CubicBezier(0.4, 0, 1, 1).Solve(state);

    case ZERO:
      return 0;

    case ACCEL_LIN_DECEL_60:
      return gfx::CubicBezier(0, 0, 0.4, 1).Solve(state);

    case ACCEL_LIN_DECEL_100:
      return gfx::CubicBezier(0, 0, 0, 1).Solve(state);

    case ACCEL_LIN_DECEL_100_3:
      return gfx::CubicBezier(0, 0, 0, 0.97).Solve(state);

    case ACCEL_20_DECEL_60:
      return gfx::CubicBezier(0.2, 0, 0.4, 1).Solve(state);

    case ACCEL_20_DECEL_100:
      return gfx::CubicBezier(0.2, 0, 0, 1).Solve(state);

    case ACCEL_30_DECEL_20_85:
      return gfx::CubicBezier(0.3, 0, 0.8, 0.15).Solve(state);

    case ACCEL_40_DECEL_20:
      return gfx::CubicBezier(0.4, 0, 0.8, 1).Solve(state);

    case ACCEL_80_DECEL_20:
      return gfx::CubicBezier(0.8, 0, 0.8, 1).Solve(state);

    case ACCEL_0_40_DECEL_100:
      return gfx::CubicBezier(0, 0.4, 0, 1).Solve(state);

    case ACCEL_40_DECEL_100_3:
      return gfx::CubicBezier(0.40, 0, 0, 0.97).Solve(state);

    case ACCEL_0_80_DECEL_80:
      return gfx::CubicBezier(0, 0.8, 0.2, 1).Solve(state);

    case ACCEL_0_100_DECEL_80:
      return gfx::CubicBezier(0, 1, 0.2, 1).Solve(state);

    case ACCEL_5_70_DECEL_90:
      return gfx::CubicBezier(0.05, 0.7, 0.1, 1).Solve(state);
  }

  NOTREACHED();
  return state;
}

namespace {

uint8_t FloatToColorByte(float f) {
  return base::ClampRound<uint8_t>(f * 255.0f);
}

uint8_t BlendColorComponents(uint8_t start,
                             uint8_t target,
                             float start_alpha,
                             float target_alpha,
                             float blended_alpha,
                             double progress) {
  // Since progress can be outside [0, 1], blending can produce a value outside
  // [0, 255].
  float blended_premultiplied = Tween::FloatValueBetween(
      progress, start / 255.f * start_alpha, target / 255.f * target_alpha);
  return FloatToColorByte(blended_premultiplied / blended_alpha);
}

float BlendColorComponentsFloat(float start,
                                float target,
                                float start_alpha,
                                float target_alpha,
                                float blended_alpha,
                                double progress) {
  // Since progress can be outside [0, 1], blending can produce a value outside
  // [0, 1].
  float blended_premultiplied = Tween::FloatValueBetween(
      progress, start * start_alpha, target * target_alpha);
  return blended_premultiplied / blended_alpha;
}

}  // namespace

// static
SkColor4f Tween::ColorValueBetween(double value,
                                   SkColor4f start,
                                   SkColor4f target) {
  float start_a = start.fA;
  float target_a = target.fA;
  float blended_a = FloatValueBetween(value, start_a, target_a);
  if (blended_a <= 0.f)
    return SkColors::kTransparent;
  blended_a = std::min(blended_a, 1.f);

  auto blended_r = BlendColorComponentsFloat(start.fR, target.fR, start_a,
                                             target_a, blended_a, value);
  auto blended_g = BlendColorComponentsFloat(start.fG, target.fG, start_a,
                                             target_a, blended_a, value);
  auto blended_b = BlendColorComponentsFloat(start.fB, target.fB, start_a,
                                             target_a, blended_a, value);

  return SkColor4f{blended_r, blended_g, blended_b, blended_a};
}
SkColor Tween::ColorValueBetween(double value, SkColor start, SkColor target) {
  float start_a = SkColorGetA(start) / 255.f;
  float target_a = SkColorGetA(target) / 255.f;
  float blended_a = FloatValueBetween(value, start_a, target_a);
  if (blended_a <= 0.f)
    return SK_ColorTRANSPARENT;
  blended_a = std::min(blended_a, 1.f);

  uint8_t blended_r =
      BlendColorComponents(SkColorGetR(start), SkColorGetR(target), start_a,
                           target_a, blended_a, value);
  uint8_t blended_g =
      BlendColorComponents(SkColorGetG(start), SkColorGetG(target), start_a,
                           target_a, blended_a, value);
  uint8_t blended_b =
      BlendColorComponents(SkColorGetB(start), SkColorGetB(target), start_a,
                           target_a, blended_a, value);

  return SkColorSetARGB(FloatToColorByte(blended_a), blended_r, blended_g,
                        blended_b);
}

// static
double Tween::DoubleValueBetween(double value, double start, double target) {
  return start + (target - start) * value;
}

// static
float Tween::FloatValueBetween(double value, float start, float target) {
  return static_cast<float>(start + (target - start) * value);
}

// static
float Tween::ClampedFloatValueBetween(const base::TimeTicks& time,
                                      const base::TimeTicks& start_time,
                                      float start,
                                      const base::TimeTicks& target_time,
                                      float target) {
  if (time <= start_time)
    return start;
  if (time >= target_time)
    return target;

  const double progress = (time - start_time) / (target_time - start_time);
  return FloatValueBetween(progress, start, target);
}

// static
int Tween::IntValueBetween(double value, int start, int target) {
  if (start == target)
    return start;
  double delta = static_cast<double>(target - start);
  if (delta < 0)
    delta--;
  else
    delta++;
#if BUILDFLAG(IS_WIN)
  return start + static_cast<int>(value * _nextafter(delta, 0));
#else
  return start + static_cast<int>(value * nextafter(delta, 0));
#endif
}

// static
int Tween::LinearIntValueBetween(double value, int start, int target) {
  // NOTE: Do not use base::ClampRound()!  See comments on function declaration.
  return base::ClampFloor(0.5 + DoubleValueBetween(value, start, target));
}

// static
gfx::Rect Tween::RectValueBetween(double value,
                                  const gfx::Rect& start,
                                  const gfx::Rect& target) {
  const int x = LinearIntValueBetween(value, start.x(), target.x());
  const int y = LinearIntValueBetween(value, start.y(), target.y());
  const int right = LinearIntValueBetween(value, start.right(), target.right());
  const int bottom =
      LinearIntValueBetween(value, start.bottom(), target.bottom());
  return gfx::Rect(x, y, right - x, bottom - y);
}

// static
gfx::RectF Tween::RectFValueBetween(double value,
                                    const gfx::RectF& start,
                                    const gfx::RectF& target) {
  const float x = FloatValueBetween(value, start.x(), target.x());
  const float y = FloatValueBetween(value, start.y(), target.y());
  const float right = FloatValueBetween(value, start.right(), target.right());
  const float bottom =
      FloatValueBetween(value, start.bottom(), target.bottom());
  return gfx::RectF(x, y, right - x, bottom - y);
}

// static
gfx::Transform Tween::TransformValueBetween(double value,
                                            const gfx::Transform& start,
                                            const gfx::Transform& target) {
  if (value >= 1.0)
    return target;
  if (value <= 0.0)
    return start;

  gfx::Transform to_return = target;
  to_return.Blend(start, value);
  return to_return;
}

// static
gfx::TransformOperations Tween::TransformOperationsValueBetween(
    double value,
    const gfx::TransformOperations& start,
    const gfx::TransformOperations& target) {
  return target.Blend(start, value);
}

gfx::Size Tween::SizeValueBetween(double value,
                                  const gfx::Size& start,
                                  const gfx::Size& target) {
  return gfx::Size(
      Tween::LinearIntValueBetween(value, start.width(), target.width()),
      Tween::LinearIntValueBetween(value, start.height(), target.height()));
}

gfx::SizeF Tween::SizeFValueBetween(double value,
                                    const gfx::SizeF& start,
                                    const gfx::SizeF& target) {
  return gfx::SizeF(
      Tween::FloatValueBetween(value, start.width(), target.width()),
      Tween::FloatValueBetween(value, start.height(), target.height()));
}

}  // namespace gfx