/*
 * 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 "object_hierarchy_observer.h"

#include <algorithm>

#include <meta/api/iteration.h>
#include <meta/api/make_callback.h>
#include <meta/api/property/property_event_handler.h>
#include <meta/api/util.h>
#include <meta/base/interface_utils.h>
#include <meta/interface/intf_containable.h>
#include <meta/interface/intf_content.h>

META_BEGIN_NAMESPACE()

// ContainerChangeListener

ObjectChangeListener::ObjectChangeListener(const IObject::Ptr& object, HierarchyChangeObjectType myType,
    const IObject::WeakPtr& parent, ObjectHierarchyObserver* observer, HierarchyChangeModeValue mode)
    : object_(object), type_(myType), parent_(parent), observer_(observer)
{
    CORE_ASSERT(observer_ && object);
    Subscribe(mode);
}

ObjectChangeListener::~ObjectChangeListener()
{
    Unsubscribe();
}

void ObjectChangeListener::SubscribeContainer(const IObject::Ptr& object)
{
    if (const auto container = interface_cast<IContainer>(object)) {
        if (auto trans = interface_cast<IContainerPreTransaction>(container)) {
            handlers_.emplace_back(trans->OnAdding(), [this](const ChildChangedInfo& info, bool&) {
                NotifyContainerChangeOp(info, HierarchyChangeType::ADDING, HierarchyChangeObjectType::CHILD);
            });
            handlers_.emplace_back(trans->OnRemoving(), [this](const ChildChangedInfo& info, bool&) {
                NotifyContainerChangeOp(info, HierarchyChangeType::REMOVING, HierarchyChangeObjectType::CHILD);
            });
            containerPreTransaction_ = true;
        }

        handlers_.emplace_back(container->OnAdded(), [this](const ChildChangedInfo& info) {
            NotifyContainerChangeOp(info, HierarchyChangeType::ADDED, HierarchyChangeObjectType::CHILD);
        });
        handlers_.emplace_back(container->OnRemoved(), [this](const ChildChangedInfo& info) {
            NotifyContainerChangeOp(info, HierarchyChangeType::REMOVED, HierarchyChangeObjectType::CHILD);
        });
        handlers_.emplace_back(container->OnMoved(), [this](const ChildMovedInfo& info) {
            NotifyContainerMoveOp(info, HierarchyChangeType::MOVED, HierarchyChangeObjectType::CHILD);
        });
    }
}

void ObjectChangeListener::SubscribeAttachment(const IObject::Ptr& object)
{
    if (const auto attach = interface_cast<IAttach>(object)) {
        if (const auto container = attach->GetAttachmentContainer(true)) {
            if (const auto trans = interface_cast<IContainerPreTransaction>(container)) {
                handlers_.emplace_back(trans->OnAdding(), [this](const ChildChangedInfo& info, bool&) {
                    NotifyContainerChangeOp(info, HierarchyChangeType::ADDING, HierarchyChangeObjectType::ATTACHMENT);
                });
                handlers_.emplace_back(trans->OnRemoving(), [this](const ChildChangedInfo& info, bool&) {
                    NotifyContainerChangeOp(info, HierarchyChangeType::REMOVING, HierarchyChangeObjectType::ATTACHMENT);
                });
                attachmentPreTransaction_ = true;
            }
            handlers_.emplace_back(container->OnAdded(), [this](const ChildChangedInfo& info) {
                NotifyContainerChangeOp(info, HierarchyChangeType::ADDED, HierarchyChangeObjectType::ATTACHMENT);
            });
            handlers_.emplace_back(container->OnRemoved(), [this](const ChildChangedInfo& info) {
                NotifyContainerChangeOp(info, HierarchyChangeType::REMOVED, HierarchyChangeObjectType::ATTACHMENT);
            });
            handlers_.emplace_back(container->OnMoved(), [this](const ChildMovedInfo& info) {
                NotifyContainerMoveOp(info, HierarchyChangeType::MOVED, HierarchyChangeObjectType::ATTACHMENT);
            });
        }
    }
}

bool ObjectChangeListener::Subscribe(HierarchyChangeModeValue mode)
{
    if (const auto object = object_.lock()) {
        // Figure out which hierarchical interfaces our target object implements and add listeners based on that
        if (mode & HierarchyChangeMode::NOTIFY_CONTAINER) {
            SubscribeContainer(object);
        }
        if (mode & HierarchyChangeMode::NOTIFY_CONTENT) {
            if (const auto content = interface_cast<IContent>(object)) {
                content_ = META_NS::GetValue(content->Content());
                handlers_.emplace_back(
                    content->Content()->OnChanged(), MakeCallback<IOnChanged>([this]() { NotifyContentChangeOp(); }));
            }
        }
        if (mode & HierarchyChangeMode::NOTIFY_ATTACHMENT) {
            SubscribeAttachment(object);
        }
        if (mode & HierarchyChangeMode::NOTIFY_OBJECT) {
            if (auto i = interface_pointer_cast<INotifyOnChange>(object)) {
                handlers_.emplace_back(i->OnChanged(), MakeCallback<IOnChanged>([this]() { NotifyObjectChangedOp(); }));
            }
        }
        return true;
    }
    return false;
}

void ObjectChangeListener::Unsubscribe()
{
    handlers_.clear();
    content_.reset();
}

void ObjectChangeListener::NotifyObjectChangedOp()
{
    if (auto object = object_.lock()) {
        HierarchyChangedInfo change;
        change.object = object;
        change.change = HierarchyChangeType::CHANGED;
        change.objectType = type_;
        change.parent = parent_;
        observer_->HierarchyChanged(change, this);
    }
}

void ObjectChangeListener::NotifyContainerChangeOp(
    const ChildChangedInfo& info, HierarchyChangeType operation, HierarchyChangeObjectType objectType)
{
    HierarchyChangedInfo change;
    change.object = info.object;
    change.change = operation;
    change.objectType = objectType;
    change.index = info.index;
    change.parent = object_;
    if ((objectType == HierarchyChangeObjectType::CHILD && !containerPreTransaction_) ||
        (objectType == HierarchyChangeObjectType::ATTACHMENT && !attachmentPreTransaction_)) {
        // Our target does not support pre transaction notifications, generate ones.
        if (operation == HierarchyChangeType::ADDED) {
            change.change = HierarchyChangeType::ADDING;
            observer_->HierarchyChanged(change, this);
            change.change = operation;
        } else if (operation == HierarchyChangeType::REMOVED) {
            change.change = HierarchyChangeType::REMOVING;
            observer_->HierarchyChanged(change, this);
            change.change = operation;
        }
    }
    observer_->HierarchyChanged(change, this);
}

void ObjectChangeListener::NotifyContainerMoveOp(
    const ChildMovedInfo& info, HierarchyChangeType operation, HierarchyChangeObjectType objectType)
{
    HierarchyChangedInfo change;
    change.object = info.object;
    change.change = operation;
    change.objectType = objectType;
    change.parent = object_;
    observer_->HierarchyChanged(change, this);
}

void ObjectChangeListener::NotifyContentChangeOp()
{
    if (const auto content = interface_pointer_cast<IContent>(object_)) {
        const auto newContent = GetValue(content->Content());
        const auto oldContent = content_;

        HierarchyChangedInfo change;
        change.objectType = HierarchyChangeObjectType::CONTENT;
        change.parent = object_;

        if (oldContent != newContent) {
            if (oldContent) {
                change.object = oldContent;
                change.change = HierarchyChangeType::REMOVING;
                observer_->HierarchyChanged(change, this);
                change.change = HierarchyChangeType::REMOVED;
                observer_->HierarchyChanged(change, this);
            }
            content_ = newContent;
            if (newContent) {
                change.object = newContent;
                change.change = HierarchyChangeType::ADDING;
                observer_->HierarchyChanged(change, this);
                change.change = HierarchyChangeType::ADDED;
                observer_->HierarchyChanged(change, this);
            }
        }
    }
}

// ContainerObserver

bool ObjectHierarchyObserver::Build(const IMetadata::Ptr& p)
{
    bool ret = Super::Build(p);
    if (ret) {
        // we don't want to serialise observers
        META_NS::SetObjectFlags(GetSelf(), ObjectFlagBits::SERIALIZE, false);
    }
    return ret;
}

void ObjectHierarchyObserver::Destroy()
{
    ClearSubscriptions();
}

void ObjectHierarchyObserver::SetTarget(const IObject::Ptr& root, HierarchyChangeModeValue mode)
{
    ClearSubscriptions();
    mode_ = mode;
    root_ = root;

    if (auto i = interface_cast<IAttach>(root)) {
        i->Attach(GetSelf<IAttachment>());
    }

    Subscribe(root, HierarchyChangeObjectType::ROOT);
}

void ObjectHierarchyObserver::ClearSubscriptions()
{
    // Delay subscription removal until outside lock
    decltype(subscriptions_) subs;
    decltype(immediateChildren_) immes;
    {
        std::unique_lock lock(mutex_);
        subs = BASE_NS::move(subscriptions_);
        immes = BASE_NS::move(immediateChildren_);
    }
    subs.clear();
    immes.clear();
}

IObject::Ptr ObjectHierarchyObserver::GetTarget() const
{
    return root_.lock();
}

BASE_NS::vector<IObject::Ptr> ObjectHierarchyObserver::GetAllObserved() const
{
    std::shared_lock lock(mutex_);
    BASE_NS::vector<IObject::Ptr> observed;
    observed.reserve(subscriptions_.size());
    for (auto&& sub : subscriptions_) {
        if (auto object = sub.second.object_.lock()) {
            observed.emplace_back(BASE_NS::move(object));
        }
    }
    return observed;
}

void ObjectHierarchyObserver::AddImmediateChild(const HierarchyChangedInfo& info)
{
    std::unique_lock lock(mutex_);
    if (info.index < immediateChildren_.size()) {
        immediateChildren_.insert(
            immediateChildren_.begin() + info.index, ImmediateChild { info.object, info.objectType });
    } else {
        immediateChildren_.push_back(ImmediateChild { info.object, info.objectType });
    }
}

void ObjectHierarchyObserver::RemoveImmediateChild(const HierarchyChangedInfo& info)
{
    std::unique_lock lock(mutex_);
    auto it = immediateChildren_.begin();
    for (; it != immediateChildren_.end() && it->object.lock() != info.object; ++it) {
    }
    if (it != immediateChildren_.end()) {
        immediateChildren_.erase(it);
    }
}

void ObjectHierarchyObserver::HierarchyChanged(const HierarchyChangedInfo& info, ObjectChangeListener* listener)
{
    if (info.object != GetSelf()) {
        META_ACCESS_EVENT(OnHierarchyChanged)->Invoke(info);
        if (info.objectType == HierarchyChangeObjectType::CHILD ||
            info.objectType == HierarchyChangeObjectType::CONTENT) {
            // We do not listen to attachments (other than monitoring for changes in an object's attachment list)
            if (info.change == HierarchyChangeType::ADDING) {
                Subscribe(info.object, info.objectType, info.parent);
                if (keepTrackOfImmediate_ && listener->GetType() == HierarchyChangeObjectType::ROOT) {
                    AddImmediateChild(info);
                }
            } else if (info.change == HierarchyChangeType::REMOVED) {
                Unsubscribe(info.object);
                if (keepTrackOfImmediate_ && listener->GetType() == HierarchyChangeObjectType::ROOT) {
                    RemoveImmediateChild(info);
                }
            }
        }
    }
}

void ObjectHierarchyObserver::Subscribe(
    const IObject::Ptr& root, HierarchyChangeObjectType type, const IObject::WeakPtr& parent)
{
    if (!root) {
        return;
    }

    AddSubscription(root, type, parent);

    if (const auto container = interface_cast<IContainer>(root)) {
        for (auto&& child : container->GetAll()) {
            if (keepTrackOfImmediate_ && type == HierarchyChangeObjectType::ROOT) {
                std::unique_lock lock(mutex_);
                immediateChildren_.push_back(ImmediateChild { child, HierarchyChangeObjectType::CHILD });
            }
            Subscribe(child, HierarchyChangeObjectType::CHILD, root);
        }
    }
    if (const auto content = interface_cast<IContent>(root)) {
        if (auto object = GetValue(content->Content())) {
            if (keepTrackOfImmediate_ && type == HierarchyChangeObjectType::ROOT) {
                std::unique_lock lock(mutex_);
                immediateChildren_.push_back(ImmediateChild { object, HierarchyChangeObjectType::CONTENT });
            }
            Subscribe(object, HierarchyChangeObjectType::CONTENT, root);
        }
    }
}

void ObjectHierarchyObserver::AddSubscription(
    const IObject::Ptr& object, HierarchyChangeObjectType type, const IObject::WeakPtr& parent)
{
    auto listener = BASE_NS::make_unique<ObjectChangeListener>(object, type, parent, this, mode_);

    {
        std::unique_lock lock(mutex_);
        if (subscriptions_.find(listener.get()) != subscriptions_.end()) {
            CORE_LOG_E(
                "Duplicate event subscription for %s:%s", object->GetClassName().data(), object->GetName().data());
        }
        subscriptions_.insert({ listener.get(), Subscription(object, BASE_NS::move(listener)) });
    }
}

void ObjectHierarchyObserver::Unsubscribe(const IObject::Ptr& root)
{
    if (!root) {
        return;
    }

    RemoveSubscription(root);

    if (root->GetInterface(IIterable::UID)) {
        ForEachShared(
            root, [this](const IObject::Ptr& object) { RemoveSubscription(object); },
            TraversalType::DEPTH_FIRST_PRE_ORDER);
    } else {
        if (const auto container = interface_cast<IContainer>(root)) {
            for (auto&& child : container->GetAll()) {
                Unsubscribe(child);
            }
        }
        if (const auto content = interface_cast<IContent>(root)) {
            if (auto object = GetValue(content->Content())) {
                Unsubscribe(object);
            }
        }
    }
}

void ObjectHierarchyObserver::RemoveSubscription(const IObject::Ptr& object)
{
    // Delay subscription destruction until we're out of the lock
    BASE_NS::vector<Subscription> subs;
    {
        std::unique_lock lock(mutex_);
        auto it = subscriptions_.begin();
        while (it != subscriptions_.end()) {
            const auto o = it->second.object_.lock();
            if (!o || o == object) {
                subs.emplace_back(BASE_NS::move(it->second));
                it = subscriptions_.erase(it);
            } else {
                ++it;
            }
        }
    }
    subs.clear();
}

void ObjectHierarchyObserver::NotifyOnDetach()
{
    // If we were attached to the target and we are getting detached when the target already died, lets
    // notify about the removing the immediate children as those are not otherwise notified when destroying
    // container
    decltype(immediateChildren_) ic;
    {
        std::unique_lock lock(mutex_);
        ic = BASE_NS::move(immediateChildren_);
    }
    if (root_.expired()) {
        for (auto&& c : ic) {
            if (auto o = c.object.lock()) {
                // Generate pre transaction notifications (even if coming in wrong time).
                HierarchyChangedInfo info { o, HierarchyChangeType::REMOVING, c.type, size_t(-1), nullptr };
                META_ACCESS_EVENT(OnHierarchyChanged)->Invoke(info);
                info.change = HierarchyChangeType::REMOVED;
                META_ACCESS_EVENT(OnHierarchyChanged)->Invoke(info);
            }
        }
    }
}

bool ObjectHierarchyObserver::Attaching(const META_NS::IAttach::Ptr& target, const META_NS::IObject::Ptr& dataContext)
{
    META_ACCESS_PROPERTY(AttachedTo)->SetValue(target);
    META_ACCESS_PROPERTY(DataContext)->SetValue(dataContext);
    {
        std::unique_lock lock(mutex_);
        keepTrackOfImmediate_ = true;
    }
    return true;
}
bool ObjectHierarchyObserver::Detaching(const META_NS::IAttach::Ptr& target)
{
    NotifyOnDetach();
    {
        std::unique_lock lock(mutex_);
        keepTrackOfImmediate_ = false;
    }
    META_ACCESS_PROPERTY(AttachedTo)->SetValue({});
    META_ACCESS_PROPERTY(DataContext)->SetValue({});
    return true;
}

META_END_NAMESPACE()