/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * 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.
 */

#include "animation_controller.h"

#include <meta/api/make_callback.h>
#include <meta/ext/serialization/serializer.h>
#include <meta/interface/property/property_events.h>

META_BEGIN_NAMESPACE()

bool AnimationController::Build(const IMetadata::Ptr& data)
{
    updateCallback_ = MakeCallback<IOnChanged>(this, &AnimationController::UpdateAnimations);
    return true;
}

bool AnimationController::AttachTo(const META_NS::IAttach::Ptr& target, const META_NS::IObject::Ptr& dataContext)
{
    return true;
}

bool AnimationController::DetachFrom(const META_NS::IAttach::Ptr& target)
{
    return true;
}

void AnimationController::UpdateAnimations()
{
    uint32_t count = 0;
    uint32_t running = 0;
    {
        std::unique_lock lock(mutex_);
        auto it = animations_.begin();
        running_.clear();
        while (it != animations_.end()) {
            auto& weak = *it;
            if (auto animation = weak.lock()) {
                // Assuming here that getting the running property value cannot cause a recursive call back
                // to the animation controller
                if (GetValue(animation->Running())) {
                    running_.push_back(weak);
                }
                it++;
            } else {
                // Animation has died, clean up weak_ptr from our list
                it = animations_.erase(it);
            }
        }
        count = animations_.size();
        running = running_.size();
    }

    SetValue(META_ACCESS_PROPERTY(Count), count);
    SetValue(META_ACCESS_PROPERTY(RunningCount), running);
}

void AnimationController::UpdateRunningHandler(const IAnimation::Ptr& animation, bool addHandler)
{
    if (!animation) {
        return;
    }
    if (auto running = animation->Running()) {
        if (addHandler) {
            running->OnChanged()->AddHandler(updateCallback_, uintptr_t(this));
        } else {
            running->OnChanged()->RemoveHandler(uintptr_t(this));
        }
    }
}

bool AnimationController::AddAnimation(const META_NS::IAnimation::Ptr& animation)
{
    if (!animation) {
        return false;
    }
    {
        std::unique_lock lock(mutex_);
        for (auto& weak : animations_) {
            if (weak.lock() == animation) {
                // Already there
                return true;
            }
        }
        animations_.emplace_back(animation);
    }

    UpdateRunningHandler(animation, true);
    SetValue(animation->Controller(), GetSelf<IAnimationController>());
    UpdateAnimations();
    return true;
}

bool AnimationController::RemoveAnimation(const META_NS::IAnimation::Ptr& animation)
{
    bool removed = false;
    if (animation) {
        std::unique_lock lock(mutex_);
        for (auto it = animations_.begin(); it != animations_.end(); it++) {
            if ((*it).lock() == animation) {
                animations_.erase(it);
                removed = true;
                break;
            }
        }
    }
    if (removed) {
        UpdateRunningHandler(animation, false);
        UpdateAnimations();
    }
    return removed;
}

void AnimationController::Clear()
{
    {
        BASE_NS::vector<IAnimation::WeakPtr> all;
        {
            std::unique_lock lock(mutex_);
            all.swap(animations_);
            running_.clear();
        }
        for (auto& weak : all) {
            UpdateRunningHandler(weak.lock(), false);
        }
    }
    UpdateAnimations();
}

IAnimationController::StepInfo AnimationController::Step(const IClock::ConstPtr& clock)
{
    StepInfo info { 0, 0 };

    for (auto& anim : GetRunning()) {
        if (auto animation = anim.lock()) {
            animation->Step(clock);
            info.stepped_++;
        }
    }

    // Step may end up starting/stopping some animations
    std::shared_lock lock(mutex_);
    info.running_ = running_.size();
    return info;
}

BASE_NS::vector<IAnimation::WeakPtr> AnimationController::GetAnimations() const
{
    std::shared_lock lock(mutex_);
    return animations_;
}

BASE_NS::vector<IAnimation::WeakPtr> AnimationController::GetRunning() const
{
    std::shared_lock lock(mutex_);
    return running_;
}

ReturnError AnimationController::Export(IExportContext& c) const
{
    return Serializer(c) & NamedValue("__animations", animations_);
}
ReturnError AnimationController::Import(IImportContext& c)
{
    return Serializer(c) & NamedValue("__animations", animations_);
}
ReturnError AnimationController::Finalize(IImportFunctions&)
{
    // take the animations out and re-add by setting the animation controller
    BASE_NS::vector<IAnimation::WeakPtr> vec = BASE_NS::move(animations_);
    for (auto&& anim : vec) {
        if (auto p = anim.lock()) {
            p->Controller()->SetValue(GetSelf<IAnimationController>());
        }
    }
    return GenericError::SUCCESS;
}

META_END_NAMESPACE()