// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_TEST_INSPECTOR_TASKS_H_
#define V8_TEST_INSPECTOR_TASKS_H_

#include <vector>

#include "include/v8-context.h"
#include "include/v8-function.h"
#include "include/v8-inspector.h"
#include "include/v8-microtask-queue.h"
#include "include/v8-primitive.h"
#include "src/base/platform/semaphore.h"
#include "test/inspector/isolate-data.h"
#include "test/inspector/task-runner.h"
#include "test/inspector/utils.h"

namespace v8 {
namespace internal {

template <typename T>
void RunSyncTask(TaskRunner* task_runner, T callback) {
  class SyncTask : public TaskRunner::Task {
   public:
    SyncTask(v8::base::Semaphore* ready_semaphore, T callback)
        : ready_semaphore_(ready_semaphore), callback_(callback) {}
    ~SyncTask() override = default;
    bool is_priority_task() final { return true; }

   private:
    void Run(InspectorIsolateData* data) override {
      callback_(data);
      if (ready_semaphore_) ready_semaphore_->Signal();
    }

    v8::base::Semaphore* ready_semaphore_;
    T callback_;
  };

  v8::base::Semaphore ready_semaphore(0);
  task_runner->Append(std::make_unique<SyncTask>(&ready_semaphore, callback));
  ready_semaphore.Wait();
}

class SendMessageToBackendTask : public TaskRunner::Task {
 public:
  SendMessageToBackendTask(int session_id, const std::vector<uint16_t>& message)
      : session_id_(session_id), message_(message) {}
  bool is_priority_task() final { return true; }

 private:
  void Run(InspectorIsolateData* data) override {
    v8_inspector::StringView message_view(message_.data(), message_.size());
    data->SendMessage(session_id_, message_view);
  }

  int session_id_;
  std::vector<uint16_t> message_;
};

inline void RunAsyncTask(TaskRunner* task_runner,
                         const v8_inspector::StringView& task_name,
                         std::unique_ptr<TaskRunner::Task> task) {
  class AsyncTask : public TaskRunner::Task {
   public:
    explicit AsyncTask(std::unique_ptr<TaskRunner::Task> inner)
        : inner_(std::move(inner)) {}
    ~AsyncTask() override = default;
    AsyncTask(const AsyncTask&) = delete;
    AsyncTask& operator=(const AsyncTask&) = delete;
    bool is_priority_task() override { return inner_->is_priority_task(); }
    void Run(InspectorIsolateData* data) override {
      data->AsyncTaskStarted(inner_.get());
      inner_->Run(data);
      data->AsyncTaskFinished(inner_.get());
    }

   private:
    std::unique_ptr<TaskRunner::Task> inner_;
  };

  task_runner->data()->AsyncTaskScheduled(task_name, task.get(), false);
  task_runner->Append(std::make_unique<AsyncTask>(std::move(task)));
}

class ExecuteStringTask : public TaskRunner::Task {
 public:
  ExecuteStringTask(v8::Isolate* isolate, int context_group_id,
                    const std::vector<uint16_t>& expression,
                    v8::Local<v8::String> name,
                    v8::Local<v8::Integer> line_offset,
                    v8::Local<v8::Integer> column_offset,
                    v8::Local<v8::Boolean> is_module)
      : expression_(expression),
        name_(ToVector(isolate, name)),
        line_offset_(line_offset.As<v8::Int32>()->Value()),
        column_offset_(column_offset.As<v8::Int32>()->Value()),
        is_module_(is_module->Value()),
        context_group_id_(context_group_id) {}

  ExecuteStringTask(const std::string& expression, int context_group_id)
      : expression_utf8_(expression), context_group_id_(context_group_id) {}

  ~ExecuteStringTask() override = default;
  ExecuteStringTask(const ExecuteStringTask&) = delete;
  ExecuteStringTask& operator=(const ExecuteStringTask&) = delete;
  bool is_priority_task() override { return false; }
  void Run(InspectorIsolateData* data) override;

 private:
  std::vector<uint16_t> expression_;
  std::string expression_utf8_;
  std::vector<uint16_t> name_;
  int32_t line_offset_ = 0;
  int32_t column_offset_ = 0;
  bool is_module_ = false;
  int context_group_id_;
};

class SetTimeoutTask : public TaskRunner::Task {
 public:
  SetTimeoutTask(int context_group_id, v8::Isolate* isolate,
                 v8::Local<v8::Function> function)
      : function_(isolate, function), context_group_id_(context_group_id) {}
  ~SetTimeoutTask() override = default;
  bool is_priority_task() final { return false; }

 private:
  void Run(InspectorIsolateData* data) override {
    v8::MicrotasksScope microtasks_scope(data->isolate(),
                                         v8::MicrotasksScope::kRunMicrotasks);
    v8::HandleScope handle_scope(data->isolate());
    v8::Local<v8::Context> context = data->GetDefaultContext(context_group_id_);
    v8::Context::Scope context_scope(context);

    v8::Local<v8::Function> function = function_.Get(data->isolate());
    v8::MaybeLocal<v8::Value> result;
    result = function->Call(context, context->Global(), 0, nullptr);
  }

  v8::Global<v8::Function> function_;
  int context_group_id_;
};

class SetTimeoutExtension : public InspectorIsolateData::SetupGlobalTask {
 public:
  void Run(v8::Isolate* isolate,
           v8::Local<v8::ObjectTemplate> global) override {
    global->Set(
        ToV8String(isolate, "setTimeout"),
        v8::FunctionTemplate::New(isolate, &SetTimeoutExtension::SetTimeout));
  }

 private:
  static void SetTimeout(const v8::FunctionCallbackInfo<v8::Value>& args) {
    if (args.Length() != 2 || !args[1]->IsNumber() ||
        (!args[0]->IsFunction() && !args[0]->IsString()) ||
        args[1].As<v8::Number>()->Value() != 0.0) {
      return;
    }
    v8::Isolate* isolate = args.GetIsolate();
    v8::Local<v8::Context> context = isolate->GetCurrentContext();
    InspectorIsolateData* data = InspectorIsolateData::FromContext(context);
    int context_group_id = data->GetContextGroupId(context);
    const char* task_name = "setTimeout";
    v8_inspector::StringView task_name_view(
        reinterpret_cast<const uint8_t*>(task_name), strlen(task_name));
    if (args[0]->IsFunction()) {
      RunAsyncTask(data->task_runner(), task_name_view,
                   std::make_unique<SetTimeoutTask>(
                       context_group_id, isolate,
                       v8::Local<v8::Function>::Cast(args[0])));
    } else {
      RunAsyncTask(
          data->task_runner(), task_name_view,
          std::make_unique<ExecuteStringTask>(
              isolate, context_group_id,
              ToVector(isolate, args[0].As<v8::String>()),
              v8::String::Empty(isolate), v8::Integer::New(isolate, 0),
              v8::Integer::New(isolate, 0), v8::Boolean::New(isolate, false)));
    }
  }
};

}  // namespace internal
}  // namespace v8

#endif  //  V8_TEST_INSPECTOR_TASKS_H_