// Copyright 2012 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.

#include "cc/animation/keyframe_model.h"

#include <algorithm>
#include <cmath>
#include <limits>
#include <string>
#include <utility>

#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "cc/trees/target_property.h"
#include "ui/gfx/animation/keyframe/animation_curve.h"

namespace cc {

// static
const KeyframeModel* KeyframeModel::ToCcKeyframeModel(
    const gfx::KeyframeModel* keyframe_model) {
  return static_cast<const KeyframeModel*>(keyframe_model);
}

// static
KeyframeModel* KeyframeModel::ToCcKeyframeModel(
    gfx::KeyframeModel* keyframe_model) {
  return static_cast<KeyframeModel*>(keyframe_model);
}

KeyframeModel::TargetPropertyId::TargetPropertyId(int target_property_type)
    : target_property_type_(target_property_type),
      custom_property_name_(""),
      native_property_type_(PaintWorkletInput::NativePropertyType::kInvalid) {}

KeyframeModel::TargetPropertyId::TargetPropertyId(
    int target_property_type,
    const std::string& custom_property_name)
    : target_property_type_(target_property_type),
      custom_property_name_(custom_property_name),
      native_property_type_(PaintWorkletInput::NativePropertyType::kInvalid) {}

KeyframeModel::TargetPropertyId::TargetPropertyId(
    int target_property_type,
    PaintWorkletInput::NativePropertyType native_property_type)
    : target_property_type_(target_property_type),
      custom_property_name_(""),
      native_property_type_(native_property_type) {}

KeyframeModel::TargetPropertyId::TargetPropertyId(
    const TargetPropertyId& other) = default;

KeyframeModel::TargetPropertyId::TargetPropertyId(TargetPropertyId&& other) =
    default;

KeyframeModel::TargetPropertyId::~TargetPropertyId() = default;

KeyframeModel::TargetPropertyId& KeyframeModel::TargetPropertyId::operator=(
    TargetPropertyId&& other) = default;

std::unique_ptr<KeyframeModel> KeyframeModel::Create(
    std::unique_ptr<gfx::AnimationCurve> curve,
    int keyframe_model_id,
    int group_id,
    TargetPropertyId target_property_id) {
  return base::WrapUnique(new KeyframeModel(std::move(curve), keyframe_model_id,
                                            group_id,
                                            std::move(target_property_id)));
}

std::unique_ptr<KeyframeModel> KeyframeModel::CreateImplInstance(
    RunState initial_run_state) const {
  // Should never clone a model that is the controlling instance as it ends up
  // creating multiple controlling instances.
  DCHECK(!is_controlling_instance_);
  std::unique_ptr<KeyframeModel> to_return(
      new KeyframeModel(curve()->Clone(), id(), group_, target_property_id_));
  to_return->element_id_ = element_id_;
  to_return->ForceRunState(initial_run_state);
  to_return->set_iterations(iterations());
  to_return->set_iteration_start(iteration_start());
  to_return->set_start_time(start_time());
  to_return->set_pause_time(pause_time());
  to_return->set_total_paused_duration(total_paused_duration());
  to_return->set_time_offset(time_offset());
  to_return->set_direction(direction());
  to_return->set_playback_rate(playback_rate());
  to_return->set_fill_mode(fill_mode());
  DCHECK(!to_return->is_controlling_instance_);
  to_return->is_controlling_instance_ = true;
  return to_return;
}

KeyframeModel::KeyframeModel(std::unique_ptr<gfx::AnimationCurve> curve,
                             int keyframe_model_id,
                             int group_id,
                             TargetPropertyId target_property_id)
    : gfx::KeyframeModel(std::move(curve),
                         keyframe_model_id,
                         target_property_id.target_property_type()),
      group_(group_id),
      target_property_id_(std::move(target_property_id)),
      needs_synchronized_start_time_(false),
      received_finished_event_(false),
      is_controlling_instance_(false),
      is_impl_only_(false),
      affects_active_elements_(true),
      affects_pending_elements_(true) {}

KeyframeModel::~KeyframeModel() = default;

int KeyframeModel::TargetProperty() const {
  return target_property_id_.target_property_type();
}

void KeyframeModel::SetRunState(RunState new_run_state,
                                base::TimeTicks monotonic_time) {
  char name_buffer[256];
  base::snprintf(name_buffer, sizeof(name_buffer), "%s-%d-%d",
                 curve()->TypeName(), TargetProperty(), group_);

  bool is_waiting_to_start =
      run_state() == WAITING_FOR_TARGET_AVAILABILITY || run_state() == STARTING;

  if (is_controlling_instance_ && is_waiting_to_start &&
      new_run_state == RUNNING) {
    TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("cc", "KeyframeModel",
                                      TRACE_ID_LOCAL(this), "Name",
                                      TRACE_STR_COPY(name_buffer));
  }

  bool was_finished = is_finished();

  auto old_run_state_name = gfx::KeyframeModel::ToString(run_state());
  gfx::KeyframeModel::SetRunState(new_run_state, monotonic_time);
  auto new_run_state_name = gfx::KeyframeModel::ToString(new_run_state);

  if (is_controlling_instance_ && !was_finished && is_finished()) {
    TRACE_EVENT_NESTABLE_ASYNC_END0("cc", "KeyframeModel",
                                    TRACE_ID_LOCAL(this));
  }

  char state_buffer[256];
  base::snprintf(state_buffer, sizeof(state_buffer), "%s->%s",
                 old_run_state_name.c_str(), new_run_state_name.c_str());

  TRACE_EVENT_INSTANT2(
      "cc", "ElementAnimations::SetRunState", TRACE_EVENT_SCOPE_THREAD, "Name",
      TRACE_STR_COPY(name_buffer), "State", TRACE_STR_COPY(state_buffer));
}

bool KeyframeModel::InEffect(base::TimeTicks monotonic_time) const {
  return CalculateActiveTime(monotonic_time).has_value();
}

void KeyframeModel::PushPropertiesTo(KeyframeModel* other) const {
  other->element_id_ = element_id_;
  if (run_state() == KeyframeModel::PAUSED ||
      other->run_state() == KeyframeModel::PAUSED) {
    other->ForceRunState(run_state());
    other->set_pause_time(pause_time());
    other->set_total_paused_duration(total_paused_duration());
  }
}

std::string KeyframeModel::ToString() const {
  return base::StringPrintf(
      "KeyframeModel{id=%d, group=%d, target_property_type=%d, "
      "custom_property_name=%s, native_property_type=%d, run_state=%s, "
      "element_id=%s}",
      id(), group_, TargetProperty(),
      target_property_id_.custom_property_name().c_str(),
      static_cast<int>(target_property_id_.native_property_type()),
      gfx::KeyframeModel::ToString(run_state()).c_str(),
      element_id_.ToString().c_str());
}

void KeyframeModel::SetIsImplOnly() {
  is_impl_only_ = true;
  // Impl only animations have a single instance which by definition is the
  // controlling instance.
  is_controlling_instance_ = true;
}

bool KeyframeModel::StartShouldBeDeferred() const {
  return needs_synchronized_start_time_;
}

}  // namespace cc
