// Copyright 2015 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/animation_timeline.h"

#include <algorithm>

#include "cc/animation/animation_host.h"
#include "cc/animation/animation_player.h"

namespace cc {

scoped_refptr<AnimationTimeline> AnimationTimeline::Create(int id) {
  return make_scoped_refptr(new AnimationTimeline(id));
}

AnimationTimeline::AnimationTimeline(int id)
    : id_(id),
      animation_host_(),
      needs_push_properties_(false),
      is_impl_only_(false) {}

AnimationTimeline::~AnimationTimeline() {
  for (auto& kv : id_to_player_map_)
    kv.second->SetAnimationTimeline(nullptr);
}

scoped_refptr<AnimationTimeline> AnimationTimeline::CreateImplInstance() const {
  scoped_refptr<AnimationTimeline> timeline = AnimationTimeline::Create(id());
  return timeline;
}

void AnimationTimeline::SetAnimationHost(AnimationHost* animation_host) {
  if (animation_host_ == animation_host)
    return;

  animation_host_ = animation_host;
  for (auto& kv : id_to_player_map_)
    kv.second->SetAnimationHost(animation_host);

  SetNeedsPushProperties();
}

void AnimationTimeline::AttachPlayer(scoped_refptr<AnimationPlayer> player) {
  DCHECK(player->id());
  player->SetAnimationHost(animation_host_);
  player->SetAnimationTimeline(this);
  id_to_player_map_.insert(std::make_pair(player->id(), std::move(player)));

  SetNeedsPushProperties();
}

void AnimationTimeline::DetachPlayer(scoped_refptr<AnimationPlayer> player) {
  DCHECK(player->id());
  ErasePlayer(player);
  id_to_player_map_.erase(player->id());

  SetNeedsPushProperties();
}

AnimationPlayer* AnimationTimeline::GetPlayerById(int player_id) const {
  auto f = id_to_player_map_.find(player_id);
  return f == id_to_player_map_.end() ? nullptr : f->second.get();
}

void AnimationTimeline::ClearPlayers() {
  for (auto& kv : id_to_player_map_)
    ErasePlayer(kv.second);
  id_to_player_map_.clear();

  SetNeedsPushProperties();
}

void AnimationTimeline::SetNeedsPushProperties() {
  needs_push_properties_ = true;
  if (animation_host_)
    animation_host_->SetNeedsPushProperties();
}

void AnimationTimeline::PushPropertiesTo(AnimationTimeline* timeline_impl) {
  if (needs_push_properties_) {
    needs_push_properties_ = false;
    PushAttachedPlayersToImplThread(timeline_impl);
    RemoveDetachedPlayersFromImplThread(timeline_impl);
    PushPropertiesToImplThread(timeline_impl);
  }
}

void AnimationTimeline::PushAttachedPlayersToImplThread(
    AnimationTimeline* timeline_impl) const {
  for (auto& kv : id_to_player_map_) {
    auto& player = kv.second;
    AnimationPlayer* player_impl = timeline_impl->GetPlayerById(player->id());
    if (player_impl)
      continue;

    scoped_refptr<AnimationPlayer> to_add = player->CreateImplInstance();
    timeline_impl->AttachPlayer(to_add.get());
  }
}

void AnimationTimeline::RemoveDetachedPlayersFromImplThread(
    AnimationTimeline* timeline_impl) const {
  IdToPlayerMap& players_impl = timeline_impl->id_to_player_map_;

  // Erase all the impl players which |this| doesn't have.
  for (auto it = players_impl.begin(); it != players_impl.end();) {
    if (GetPlayerById(it->second->id())) {
      ++it;
    } else {
      timeline_impl->ErasePlayer(it->second);
      it = players_impl.erase(it);
    }
  }
}

void AnimationTimeline::ErasePlayer(scoped_refptr<AnimationPlayer> player) {
  if (player->element_animations())
    player->DetachElement();
  player->SetAnimationTimeline(nullptr);
  player->SetAnimationHost(nullptr);
}

void AnimationTimeline::PushPropertiesToImplThread(
    AnimationTimeline* timeline_impl) {
  for (auto& kv : id_to_player_map_) {
    AnimationPlayer* player = kv.second.get();
    if (player->needs_push_properties()) {
      AnimationPlayer* player_impl = timeline_impl->GetPlayerById(player->id());
      if (player_impl)
        player->PushPropertiesTo(player_impl);
    }
  }
}

}  // namespace cc
