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

#include <scene/ext/scene_utils.h>
#include <scene/ext/util.h>

#include <render/intf_render_context.h>

#include "component_factory.h"
#include "core/internal_scene.h"
#include "render_configuration.h"

SCENE_BEGIN_NAMESPACE()

SceneObject::~SceneObject()
{
    if (internal_) {
        if (updateTask_) {
            internal_->GetContext()->GetRenderQueue()->CancelTask(updateTask_);
        }
        // we attach asset loader to the scene, so destroy the attachments in engine thread
        BASE_NS::vector<META_NS::IObject::Ptr> attachments = GetAttachments({}, false);
        for (auto&& v : attachments) {
            Detach(v);
        }
        internal_->RunDirectlyOrInTask([&, atts = BASE_NS::move(attachments)]() mutable {
            atts.clear();
            internal_->Uninitialize();
            internal_.reset();
        });
    }
}

bool SceneObject::Build(const META_NS::IMetadata::Ptr& d)
{
    bool res = Super::Build(d);
    if (res) {
        auto context = GetInterfaceBuildArg<IRenderContext>(d, "RenderContext");
        if (!context) {
            CORE_LOG_E("Invalid parameters to construct Scene");
            return false;
        }
        auto opts = GetBuildArg<SceneOptions>(d, "Options");
        auto in = CreateShared<InternalScene>(GetSelf<IScene>(), BASE_NS::move(context), BASE_NS::move(opts));

        internal_ = in;
        in->SetSelf(internal_);
        AddBuiltinComponentFactories(internal_);
        res = internal_->Initialize();
    }
    return res;
}

bool SceneObject::InitDynamicProperty(const META_NS::IProperty::Ptr& p, BASE_NS::string_view path)
{
    if (p->GetName() == "RenderConfiguration") {
        // ETS API requires RenderConfigurationComponent to be available as an attachment in the root node
        // of the Scene. When RenderConfiguration property is initialized, attach the component wrapper into the
        // Scene's root node for it to be available from JS.
        return internal_->RunDirectlyOrInTask([&]() -> bool {
            if (META_NS::Property<IRenderConfiguration::Ptr> t{p}) {
                if (auto root = internal_->GetRootNode()) {
                    if (auto component = internal_->CreateEcsComponent(root, "RenderConfigurationComponent")) {
                        return t->SetValue(interface_pointer_cast<IRenderConfiguration>(component));
                    }
                }
            }
            return false;
        });
    }
    return false;
}

bool SceneObject::AttachEngineProperty(const META_NS::IProperty::Ptr& p, BASE_NS::string_view path)
{
    return false;
}

void SceneObject::StartAutoUpdate(META_NS::TimeSpan interval)
{
    if (updateTask_) {
        internal_->GetContext()->GetRenderQueue()->CancelTask(updateTask_);
    }
    updateTask_ =
        internal_->GetContext()->GetRenderQueue()->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>([&] {
            internal_->Update();
            return true;
        }),
            interval);
}

Future<INode::Ptr> SceneObject::GetRootNode() const
{
    return internal_->AddTaskOrRunDirectly([=] { return internal_->GetRootNode(); });
}

Future<INode::Ptr> SceneObject::CreateNode(const BASE_NS::string_view uri, META_NS::ObjectId id)
{
    return internal_->AddTaskOrRunDirectly(
        [=, path = BASE_NS::string(uri)] { return internal_->CreateNode(path, id); });
}
Future<INode::Ptr> SceneObject::CreateNode(
    const INode::ConstPtr& parent, BASE_NS::string_view name, META_NS::ObjectId id)
{
    return internal_->AddTaskOrRunDirectly(
        [=, n = BASE_NS::string(name)] { return internal_->CreateNode(parent, n, id); });
}

Future<INode::Ptr> SceneObject::FindNode(BASE_NS::string_view uri, META_NS::ObjectId id) const
{
    return internal_->AddTaskOrRunDirectly([=, path = BASE_NS::string(uri)] { return internal_->FindNode(path, id); });
}

Future<BASE_NS::vector<INode::Ptr>> SceneObject::FindNamedNodes(const IScene::FindNamedNodeParams& params) const
{
    return internal_->AddTaskOrRunDirectly([=, name = BASE_NS::string(params.name)] {
        return internal_->FindNamedNodes(params.name, params.maxCount, params.root, params.id, params.traversalType);
    });
}

Future<META_NS::IObject::Ptr> SceneObject::CreateObject(META_NS::ObjectId id)
{
    return internal_->AddTaskOrRunDirectly([=] { return internal_->CreateObject(id); });
}

Future<uint32_t> SceneObject::ReleaseNode(INode::Ptr&& node, bool recursive)
{
    return internal_->AddTaskOrRunDirectly(
        [=, n = BASE_NS::move(node)]() mutable { return internal_->ReleaseNode(BASE_NS::move(n), recursive); });
}

Future<bool> SceneObject::RemoveNode(INode::Ptr&& node)
{
    return internal_->AddTaskOrRunDirectly(
        [=, n = BASE_NS::move(node)]() mutable { return internal_->RemoveNode(BASE_NS::move(n), true); });
}

Future<bool> SceneObject::RemoveObject(META_NS::IObject::Ptr&& object, const RemoveObjectOptions& options)
{
    return internal_->AddTaskOrRunDirectly([=, o = BASE_NS::move(object), opt = options]() mutable {
        return internal_->RemoveObject(BASE_NS::move(o), opt.removeFromResourceIndex);
    });
}

Future<BASE_NS::vector<ICamera::Ptr>> SceneObject::GetCameras() const
{
    return internal_->AddTaskOrRunDirectly([=] { return internal_->GetCameras(); });
}

Future<BASE_NS::vector<META_NS::IAnimation::Ptr>> SceneObject::GetAnimations() const
{
    return internal_->AddTaskOrRunDirectly([=] { return internal_->GetAnimations(); });
}

IInternalScene::Ptr SceneObject::GetInternalScene() const
{
    return internal_;
}

Future<bool> SceneObject::SetRenderMode(RenderMode mode)
{
    return internal_->AddTaskOrRunDirectly([=] { return internal_->SetRenderMode(mode); });
}
Future<RenderMode> SceneObject::GetRenderMode() const
{
    return internal_->AddTaskOrRunDirectly([=] { return internal_->GetRenderMode(); });
}
IComponent::Ptr SceneObject::GetComponent(const INode::Ptr& node, BASE_NS::string_view componentName) const
{
    if (const auto attach = interface_cast<META_NS::IAttach>(node)) {
        if (auto container = attach->GetAttachmentContainer(false)) {
            if (auto component = container->FindAny<IComponent>(componentName, META_NS::TraversalType::NO_HIERARCHY)) {
                return component;
            }
        }
    }
    return {};
}
Future<IComponent::Ptr> SceneObject::CreateComponent(const INode::Ptr& node, BASE_NS::string_view componentName)
{
    return internal_->AddTaskOrRunDirectly(
        [=, name = BASE_NS::string(componentName)] { return internal_->CreateEcsComponent(node, name); });
}
Future<NodeHits> SceneObject::CastRay(
    const BASE_NS::Math::Vec3& pos, const BASE_NS::Math::Vec3& dir, const RayCastOptions& options) const
{
    return internal_->AddTaskOrRunDirectly([=] {
        RayCastOptions ops = options;
        if (ops.layerMask == NONE_LAYER_MASK) {
            ops.layerMask = ALL_LAYER_MASK;
        }
        NodeHits result;
        if (auto ir = interface_cast<IInternalRayCast>(internal_)) {
            result = ir->CastRay(pos, dir, ops);
        }
        return result;
    });
}
ResourceGroupBundle SceneObject::GetResourceGroups() const
{
    return internal_->GetResourceGroups();
}
CORE_NS::IResource::Ptr SceneObject::GetResource(const CORE_NS::ResourceId& res)
{
    auto resources = GetResourceManager(internal_);
    return resources ? resources->GetResource({res, internal_->GetScene()}) : nullptr;
}

SCENE_END_NAMESPACE()