/*
 * Copyright (c) 2022 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.
 */

#ifndef FOUNDATION_ACE_FRAMEWORKS_BASE_GEOMETRY_ANIMATABLE_FLOAT_H
#define FOUNDATION_ACE_FRAMEWORKS_BASE_GEOMETRY_ANIMATABLE_FLOAT_H

#include "core/animation/animator.h"
#include "core/animation/curve_animation.h"
#include "core/components/common/properties/animation_option.h"
#include "core/pipeline/pipeline_base.h"

namespace OHOS::Ace {

/*
 * AnimatableFloat is a float with AnimationOption and Animator.
 */
class AnimatableFloat final {
public:
    AnimatableFloat() = default;
    explicit AnimatableFloat(float value, const AnimationOption& option = AnimationOption())
        : value_(value), animationOption_(option) {}
    ~AnimatableFloat() = default;
    using RenderNodeAnimationCallback = std::function<void()>;

    void SetContextAndCallback(const WeakPtr<PipelineBase>& context, RenderNodeAnimationCallback&& callback)
    {
        context_ = context;
        animationCallback_ = std::move(callback);
    }

    float GetValue() const
    {
        return value_;
    }

    void SetValue(float value)
    {
        value_ = value;
    }

    AnimationOption GetAnimationOption() const
    {
        return animationOption_;
    }

    void SetAnimationOption(const AnimationOption& option)
    {
        animationOption_ = option;
    }

    bool operator==(const AnimatableFloat& animFloat) const
    {
        return NearEqual(value_, animFloat.GetValue());
    }

    bool operator!=(const AnimatableFloat& animFloat) const
    {
        return !operator==(animFloat);
    }

    AnimatableFloat& operator=(const AnimatableFloat& newValue)
    {
        SetAnimationOption(newValue.GetAnimationOption());
        auto pipelineContext = context_.Upgrade();
        if (NearEqual(value_, std::numeric_limits<float>::max()) || NearEqual(value_, newValue.GetValue())
            || !pipelineContext || !animationCallback_) {
            SetValue(newValue.GetValue());
            return *this;
        }

        AnimationOption explicitAnim;
        if (pipelineContext) {
            explicitAnim = pipelineContext->GetExplicitAnimationOption();
        }

        // Animaiton has started already in previous update call.
        if (NearEqual(animateToEndValue_, newValue.GetValue()) && explicitAnim.IsValid()) {
            LOGW("Previous animateTo end value is same as new value.");
            return *this;
        }

        if (explicitAnim.IsValid()) {
            SetAnimationOption(explicitAnim);
            AnimateTo(newValue.GetValue());
        } else if (animationOption_.IsValid()) {
            AnimateTo(newValue.GetValue());
        } else {
            SetValue(newValue.GetValue());
        }
        return *this;
    }

private:
    void AnimateTo(float endValue)
    {
        animateToEndValue_ = endValue;
        ResetController();
        if (!animationController_) {
            animationController_ = CREATE_ANIMATOR(context_);
        }
        RefPtr<CurveAnimation<float>> animation = AceType::MakeRefPtr<CurveAnimation<float>>(
            GetValue(), endValue, animationOption_.GetCurve());
        animation->AddListener(std::bind(&AnimatableFloat::OnAnimationCallback, this, std::placeholders::_1));

        animationController_->AddInterpolator(animation);
        animationController_->SetDuration(animationOption_.GetDuration());
        animationController_->SetStartDelay(animationOption_.GetDelay());
        animationController_->Play();
    }

    void ResetController()
    {
        if (animationController_) {
            if (!animationController_->IsStopped()) {
                animationController_->Stop();
            }
            animationController_->ClearInterpolators();
            animationController_.Reset();
        }
    }

    void OnAnimationCallback(float value)
    {
        SetValue(value);
        if (animationCallback_) {
            animationCallback_();
        }
    }

private:
    float value_ = std::numeric_limits<float>::max();
    AnimationOption animationOption_;
    RefPtr<Animator> animationController_;
    WeakPtr<PipelineBase> context_;
    RenderNodeAnimationCallback animationCallback_;
    float animateToEndValue_ = -1.0f;
};

} // namespace OHOS::Ace

#endif // FOUNDATION_ACE_FRAMEWORKS_BASE_GEOMETRY_ANIMATABLE_FLOAT_H