// 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/shell/platform/embedder/tests/embedder_test_context.h"

#include <utility>

#include "flutter/fml/make_copyable.h"
#include "flutter/fml/paths.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/shell/platform/embedder/tests/embedder_assertions.h"
#include "flutter/testing/testing.h"
#include "third_party/dart/runtime/bin/elf_loader.h"
#include "third_party/skia/include/core/SkSurface.h"

namespace flutter {
namespace testing {

EmbedderTestContext::EmbedderTestContext(std::string assets_path)
    : assets_path_(std::move(assets_path)),
      aot_symbols_(
          LoadELFSymbolFromFixturesIfNeccessary(kDefaultAOTAppELFFileName)),
      native_resolver_(std::make_shared<TestDartNativeResolver>()) {
  SetupAOTMappingsIfNecessary();
  SetupAOTDataIfNecessary();
  isolate_create_callbacks_.push_back(
      [weak_resolver =
           std::weak_ptr<TestDartNativeResolver>{native_resolver_}]() {
        if (auto resolver = weak_resolver.lock()) {
          resolver->SetNativeResolverForIsolate();
        }
      });
}

EmbedderTestContext::~EmbedderTestContext() = default;

void EmbedderTestContext::SetupAOTMappingsIfNecessary() {
  if (!DartVM::IsRunningPrecompiledCode()) {
    return;
  }
  vm_snapshot_data_ =
      std::make_unique<fml::NonOwnedMapping>(aot_symbols_.vm_snapshot_data, 0u);
  vm_snapshot_instructions_ = std::make_unique<fml::NonOwnedMapping>(
      aot_symbols_.vm_snapshot_instrs, 0u);
  isolate_snapshot_data_ =
      std::make_unique<fml::NonOwnedMapping>(aot_symbols_.vm_isolate_data, 0u);
  isolate_snapshot_instructions_ = std::make_unique<fml::NonOwnedMapping>(
      aot_symbols_.vm_isolate_instrs, 0u);
}

void EmbedderTestContext::SetupAOTDataIfNecessary() {
  if (!DartVM::IsRunningPrecompiledCode()) {
    return;
  }
  FlutterEngineAOTDataSource data_in = {};
  FlutterEngineAOTData data_out = nullptr;

  const auto elf_path = fml::paths::JoinPaths(
      {GetFixturesPath(), testing::kDefaultAOTAppELFFileName});

  data_in.type = kFlutterEngineAOTDataSourceTypeElfPath;
  data_in.elf_path = elf_path.c_str();

  ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kSuccess);

  aot_data_.reset(data_out);
}

const std::string& EmbedderTestContext::GetAssetsPath() const {
  return assets_path_;
}

const fml::Mapping* EmbedderTestContext::GetVMSnapshotData() const {
  return vm_snapshot_data_.get();
}

const fml::Mapping* EmbedderTestContext::GetVMSnapshotInstructions() const {
  return vm_snapshot_instructions_.get();
}

const fml::Mapping* EmbedderTestContext::GetIsolateSnapshotData() const {
  return isolate_snapshot_data_.get();
}

const fml::Mapping* EmbedderTestContext::GetIsolateSnapshotInstructions()
    const {
  return isolate_snapshot_instructions_.get();
}

FlutterEngineAOTData EmbedderTestContext::GetAOTData() const {
  return aot_data_.get();
}

void EmbedderTestContext::SetRootSurfaceTransformation(SkMatrix matrix) {
  root_surface_transformation_ = matrix;
}

void EmbedderTestContext::AddIsolateCreateCallback(
    const fml::closure& closure) {
  if (closure) {
    isolate_create_callbacks_.push_back(closure);
  }
}

VoidCallback EmbedderTestContext::GetIsolateCreateCallbackHook() {
  return [](void* user_data) {
    reinterpret_cast<EmbedderTestContext*>(user_data)
        ->FireIsolateCreateCallbacks();
  };
}

void EmbedderTestContext::FireIsolateCreateCallbacks() {
  for (auto closure : isolate_create_callbacks_) {
    closure();
  }
}

void EmbedderTestContext::AddNativeCallback(const char* name,
                                            Dart_NativeFunction function) {
  native_resolver_->AddNativeCallback({name}, function);
}

void EmbedderTestContext::SetSemanticsUpdateCallback2(
    SemanticsUpdateCallback2 update_semantics_callback) {
  update_semantics_callback2_ = std::move(update_semantics_callback);
}

void EmbedderTestContext::SetSemanticsUpdateCallback(
    SemanticsUpdateCallback update_semantics_callback) {
  update_semantics_callback_ = std::move(update_semantics_callback);
}

void EmbedderTestContext::SetSemanticsNodeCallback(
    SemanticsNodeCallback update_semantics_node_callback) {
  update_semantics_node_callback_ = std::move(update_semantics_node_callback);
}

void EmbedderTestContext::SetSemanticsCustomActionCallback(
    SemanticsActionCallback update_semantics_custom_action_callback) {
  update_semantics_custom_action_callback_ =
      std::move(update_semantics_custom_action_callback);
}

void EmbedderTestContext::SetPlatformMessageCallback(
    const std::function<void(const FlutterPlatformMessage*)>& callback) {
  platform_message_callback_ = callback;
}

void EmbedderTestContext::PlatformMessageCallback(
    const FlutterPlatformMessage* message) {
  if (platform_message_callback_) {
    platform_message_callback_(message);
  }
}

void EmbedderTestContext::SetLogMessageCallback(
    const LogMessageCallback& callback) {
  log_message_callback_ = callback;
}

FlutterUpdateSemanticsCallback2
EmbedderTestContext::GetUpdateSemanticsCallback2Hook() {
  if (update_semantics_callback2_ == nullptr) {
    return nullptr;
  }

  return [](const FlutterSemanticsUpdate2* update, void* user_data) {
    auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
    if (context->update_semantics_callback2_) {
      context->update_semantics_callback2_(update);
    }
  };
}

FlutterUpdateSemanticsCallback
EmbedderTestContext::GetUpdateSemanticsCallbackHook() {
  if (update_semantics_callback_ == nullptr) {
    return nullptr;
  }

  return [](const FlutterSemanticsUpdate* update, void* user_data) {
    auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
    if (context->update_semantics_callback_) {
      context->update_semantics_callback_(update);
    }
  };
}

FlutterUpdateSemanticsNodeCallback
EmbedderTestContext::GetUpdateSemanticsNodeCallbackHook() {
  if (update_semantics_node_callback_ == nullptr) {
    return nullptr;
  }

  return [](const FlutterSemanticsNode* semantics_node, void* user_data) {
    auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
    if (context->update_semantics_node_callback_) {
      context->update_semantics_node_callback_(semantics_node);
    }
  };
}

FlutterUpdateSemanticsCustomActionCallback
EmbedderTestContext::GetUpdateSemanticsCustomActionCallbackHook() {
  if (update_semantics_custom_action_callback_ == nullptr) {
    return nullptr;
  }

  return [](const FlutterSemanticsCustomAction* action, void* user_data) {
    auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
    if (context->update_semantics_custom_action_callback_) {
      context->update_semantics_custom_action_callback_(action);
    }
  };
}

FlutterLogMessageCallback EmbedderTestContext::GetLogMessageCallbackHook() {
  return [](const char* tag, const char* message, void* user_data) {
    auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
    if (context->log_message_callback_) {
      context->log_message_callback_(tag, message);
    }
  };
}

FlutterComputePlatformResolvedLocaleCallback
EmbedderTestContext::GetComputePlatformResolvedLocaleCallbackHook() {
  return [](const FlutterLocale** supported_locales,
            size_t length) -> const FlutterLocale* {
    return supported_locales[0];
  };
}

FlutterTransformation EmbedderTestContext::GetRootSurfaceTransformation() {
  return FlutterTransformationMake(root_surface_transformation_);
}

EmbedderTestCompositor& EmbedderTestContext::GetCompositor() {
  FML_CHECK(compositor_)
      << "Accessed the compositor on a context where one was not set up. Use "
         "the config builder to set up a context with a custom compositor.";
  return *compositor_;
}

void EmbedderTestContext::SetNextSceneCallback(
    const NextSceneCallback& next_scene_callback) {
  if (compositor_) {
    compositor_->SetNextSceneCallback(next_scene_callback);
    return;
  }
  next_scene_callback_ = next_scene_callback;
}

std::future<sk_sp<SkImage>> EmbedderTestContext::GetNextSceneImage() {
  std::promise<sk_sp<SkImage>> promise;
  auto future = promise.get_future();
  SetNextSceneCallback(
      fml::MakeCopyable([promise = std::move(promise)](auto image) mutable {
        promise.set_value(image);
      }));
  return future;
}

/// @note Procedure doesn't copy all closures.
void EmbedderTestContext::FireRootSurfacePresentCallbackIfPresent(
    const std::function<sk_sp<SkImage>(void)>& image_callback) {
  if (!next_scene_callback_) {
    return;
  }
  auto callback = next_scene_callback_;
  next_scene_callback_ = nullptr;
  callback(image_callback());
}

void EmbedderTestContext::SetVsyncCallback(
    std::function<void(intptr_t)> callback) {
  vsync_callback_ = std::move(callback);
}

void EmbedderTestContext::RunVsyncCallback(intptr_t baton) {
  vsync_callback_(baton);
}

}  // namespace testing
}  // namespace flutter
