// Copyright 2013 The Flutter 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 "flutter/flow/view_holder.h"

#include "flutter/fml/thread_local.h"

namespace {

using ViewHolderBindings =
    std::unordered_map<zx_koid_t, std::unique_ptr<flutter::ViewHolder>>;

FML_THREAD_LOCAL fml::ThreadLocalUniquePtr<ViewHolderBindings>
    tls_view_holder_bindings;

fuchsia::ui::gfx::ViewProperties ToViewProperties(float width,
                                                  float height,
                                                  float insetTop,
                                                  float insetRight,
                                                  float insetBottom,
                                                  float insetLeft,
                                                  bool focusable) {
  return fuchsia::ui::gfx::ViewProperties({
      .bounding_box = fuchsia::ui::gfx::BoundingBox({
          .min = fuchsia::ui::gfx::vec3({
              .x = 0.f,
              .y = 0.f,
              .z = -1000.f,
          }),
          .max = fuchsia::ui::gfx::vec3({.x = width, .y = height, .z = 0.f}),
      }),
      .inset_from_min = fuchsia::ui::gfx::vec3({
          .x = insetLeft,
          .y = insetTop,
          .z = 0.f,
      }),
      .inset_from_max = fuchsia::ui::gfx::vec3({
          .x = insetRight,
          .y = insetBottom,
          .z = 0.f,
      }),
      .focus_change = focusable,
  });
}

}  // namespace

namespace flutter {

void ViewHolder::Create(zx_koid_t id,
                        fml::RefPtr<fml::TaskRunner> ui_task_runner,
                        fuchsia::ui::views::ViewHolderToken view_holder_token,
                        BindCallback on_bind_callback) {
  // This GPU thread contains at least 1 ViewHolder.  Initialize the per-thread
  // bindings.
  if (tls_view_holder_bindings.get() == nullptr) {
    tls_view_holder_bindings.reset(new ViewHolderBindings());
  }

  auto* bindings = tls_view_holder_bindings.get();
  FML_DCHECK(bindings);
  FML_DCHECK(bindings->find(id) == bindings->end());

  auto view_holder = std::make_unique<ViewHolder>(std::move(ui_task_runner),
                                                  std::move(view_holder_token),
                                                  std::move(on_bind_callback));
  bindings->emplace(id, std::move(view_holder));
}

void ViewHolder::Destroy(zx_koid_t id) {
  auto* bindings = tls_view_holder_bindings.get();
  FML_DCHECK(bindings);

  bindings->erase(id);
}

ViewHolder* ViewHolder::FromId(zx_koid_t id) {
  auto* bindings = tls_view_holder_bindings.get();
  if (!bindings) {
    return nullptr;
  }

  auto binding = bindings->find(id);
  if (binding == bindings->end()) {
    return nullptr;
  }

  return binding->second.get();
}

ViewHolder::ViewHolder(fml::RefPtr<fml::TaskRunner> ui_task_runner,
                       fuchsia::ui::views::ViewHolderToken view_holder_token,
                       BindCallback on_bind_callback)
    : ui_task_runner_(std::move(ui_task_runner)),
      pending_view_holder_token_(std::move(view_holder_token)),
      pending_bind_callback_(std::move(on_bind_callback)) {
  FML_DCHECK(ui_task_runner_);
  FML_DCHECK(pending_view_holder_token_.value);
}

void ViewHolder::UpdateScene(SceneUpdateContext& context,
                             const SkPoint& offset,
                             const SkSize& size,
                             bool hit_testable) {
  if (pending_view_holder_token_.value) {
    opacity_node_ =
        std::make_unique<scenic::OpacityNodeHACK>(context.session());
    entity_node_ = std::make_unique<scenic::EntityNode>(context.session());
    view_holder_ = std::make_unique<scenic::ViewHolder>(
        context.session(), std::move(pending_view_holder_token_),
        "Flutter SceneHost");

    opacity_node_->AddChild(*entity_node_);
    entity_node_->Attach(*view_holder_);
    ui_task_runner_->PostTask(
        [bind_callback = std::move(pending_bind_callback_),
         view_holder_id = view_holder_->id()]() {
          bind_callback(view_holder_id);
        });
  }
  FML_DCHECK(opacity_node_);
  FML_DCHECK(view_holder_);

  context.top_entity()->entity_node().AddChild(*opacity_node_);
  entity_node_->SetTranslation(offset.x(), offset.y(), -0.1f);
  entity_node_->SetHitTestBehavior(
      hit_testable ? fuchsia::ui::gfx::HitTestBehavior::kDefault
                   : fuchsia::ui::gfx::HitTestBehavior::kSuppress);
  if (has_pending_opacity_) {
    opacity_node_->SetOpacity(pending_opacity_);

    has_pending_opacity_ = false;
  }
  if (has_pending_properties_) {
    view_holder_->SetViewProperties(std::move(pending_properties_));

    has_pending_properties_ = false;
  }
}

void ViewHolder::SetProperties(double width,
                               double height,
                               double insetTop,
                               double insetRight,
                               double insetBottom,
                               double insetLeft,
                               bool focusable) {
  pending_properties_ = ToViewProperties(width, height, insetTop, insetRight,
                                         insetBottom, insetLeft, focusable);
  has_pending_properties_ = true;
}

void ViewHolder::SetOpacity(double opacity) {
  pending_opacity_ = std::clamp(opacity, 0.0, 1.0);
  has_pending_opacity_ = true;
}

}  // namespace flutter