// 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.

#define RAPIDJSON_HAS_STDSTRING 1

#include "flutter/runtime/service_protocol.h"

#include <string.h>

#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "flutter/fml/synchronization/waitable_event.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"

namespace flutter {

const std::string_view ServiceProtocol::kScreenshotExtensionName =
    "_flutter.screenshot";
const std::string_view ServiceProtocol::kScreenshotSkpExtensionName =
    "_flutter.screenshotSkp";
const std::string_view ServiceProtocol::kRunInViewExtensionName =
    "_flutter.runInView";
const std::string_view ServiceProtocol::kFlushUIThreadTasksExtensionName =
    "_flutter.flushUIThreadTasks";
const std::string_view ServiceProtocol::kSetAssetBundlePathExtensionName =
    "_flutter.setAssetBundlePath";
const std::string_view ServiceProtocol::kGetDisplayRefreshRateExtensionName =
    "_flutter.getDisplayRefreshRate";

static constexpr std::string_view kViewIdPrefx = "_flutterView/";
static constexpr std::string_view kListViewsExtensionName =
    "_flutter.listViews";

ServiceProtocol::ServiceProtocol()
    : endpoints_({
          // Private
          kListViewsExtensionName,

          // Public
          kScreenshotExtensionName,
          kScreenshotSkpExtensionName,
          kRunInViewExtensionName,
          kFlushUIThreadTasksExtensionName,
          kSetAssetBundlePathExtensionName,
          kGetDisplayRefreshRateExtensionName,
      }),
      handlers_mutex_(fml::SharedMutex::Create()) {}

ServiceProtocol::~ServiceProtocol() {
  ToggleHooks(false);
}

void ServiceProtocol::AddHandler(Handler* handler,
                                 Handler::Description description) {
  fml::UniqueLock lock(*handlers_mutex_);
  handlers_.emplace(handler, description);
}

void ServiceProtocol::RemoveHandler(Handler* handler) {
  fml::UniqueLock lock(*handlers_mutex_);
  handlers_.erase(handler);
}

void ServiceProtocol::SetHandlerDescription(Handler* handler,
                                            Handler::Description description) {
  fml::SharedLock lock(*handlers_mutex_);
  auto it = handlers_.find(handler);
  if (it != handlers_.end())
    it->second.Store(description);
}

void ServiceProtocol::ToggleHooks(bool set) {
  for (const auto& endpoint : endpoints_) {
    Dart_RegisterIsolateServiceRequestCallback(
        endpoint.data(),                  // method
        &ServiceProtocol::HandleMessage,  // callback
        set ? this : nullptr              // user data
    );
  }
}

static void WriteServerErrorResponse(rapidjson::Document& document,
                                     const char* message) {
  document.SetObject();
  document.AddMember("code", -32000, document.GetAllocator());
  rapidjson::Value message_value;
  message_value.SetString(message, document.GetAllocator());
  document.AddMember("message", message_value, document.GetAllocator());
}

bool ServiceProtocol::HandleMessage(const char* method,
                                    const char** param_keys,
                                    const char** param_values,
                                    intptr_t num_params,
                                    void* user_data,
                                    const char** json_object) {
  Handler::ServiceProtocolMap params;
  for (intptr_t i = 0; i < num_params; i++) {
    params[std::string_view{param_keys[i]}] = std::string_view{param_values[i]};
  }

#ifndef NDEBUG
  FML_DLOG(INFO) << "Service protcol method: " << method;
  FML_DLOG(INFO) << "Arguments: " << params.size();
  for (intptr_t i = 0; i < num_params; i++) {
    FML_DLOG(INFO) << "  " << i + 1 << ": " << param_keys[i] << " = "
                   << param_values[i];
  }
#endif  // NDEBUG

  rapidjson::Document document;
  bool result = HandleMessage(std::string_view{method},                  //
                              params,                                    //
                              static_cast<ServiceProtocol*>(user_data),  //
                              document                                   //
  );
  rapidjson::StringBuffer buffer;
  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
  document.Accept(writer);
  *json_object = strdup(buffer.GetString());

#ifndef NDEBUG
  FML_DLOG(INFO) << "Response: " << *json_object;
  FML_DLOG(INFO) << "RPC Result: " << result;
#endif  // NDEBUG

  return result;
}

bool ServiceProtocol::HandleMessage(std::string_view method,
                                    const Handler::ServiceProtocolMap& params,
                                    ServiceProtocol* service_protocol,
                                    rapidjson::Document& response) {
  if (service_protocol == nullptr) {
    WriteServerErrorResponse(response, "Service protocol unavailable.");
    return false;
  }

  return service_protocol->HandleMessage(method, params, response);
}

FML_WARN_UNUSED_RESULT
static bool HandleMessageOnHandler(
    ServiceProtocol::Handler* handler,
    std::string_view method,
    const ServiceProtocol::Handler::ServiceProtocolMap& params,
    rapidjson::Document& document) {
  FML_DCHECK(handler);
  fml::AutoResetWaitableEvent latch;
  bool result = false;
  fml::TaskRunner::RunNowOrPostTask(
      handler->GetServiceProtocolHandlerTaskRunner(method),
      [&latch,    //
       &result,   //
       &handler,  //
       &method,   //
       &params,   //
       &document  //
  ]() {
        result =
            handler->HandleServiceProtocolMessage(method, params, document);
        latch.Signal();
      });
  latch.Wait();
  return result;
}

bool ServiceProtocol::HandleMessage(std::string_view method,
                                    const Handler::ServiceProtocolMap& params,
                                    rapidjson::Document& response) const {
  if (method == kListViewsExtensionName) {
    // So far, this is the only built-in method that does not forward to the
    // dynamic set of handlers.
    return HandleListViewsMethod(response);
  }

  fml::SharedLock lock(*handlers_mutex_);

  if (handlers_.size() == 0) {
    WriteServerErrorResponse(response,
                             "There are no running service protocol handlers.");
    return false;
  }

  // Find the handler by its "viewId" in the params.
  auto view_id_param_found = params.find(std::string_view{"viewId"});
  if (view_id_param_found != params.end()) {
    auto* handler = reinterpret_cast<Handler*>(std::stoull(
        view_id_param_found->second.data() + kViewIdPrefx.size(), nullptr, 16));
    auto handler_found = handlers_.find(handler);
    if (handler_found != handlers_.end()) {
      return HandleMessageOnHandler(handler, method, params, response);
    }
  }

  // Handle legacy calls that do not specify a handler in their args.
  // TODO(chinmaygarde): Deprecate these calls in the tools and remove these
  // fallbacks.
  if (method == kScreenshotExtensionName ||
      method == kScreenshotSkpExtensionName ||
      method == kFlushUIThreadTasksExtensionName) {
    return HandleMessageOnHandler(handlers_.begin()->first, method, params,
                                  response);
  }

  WriteServerErrorResponse(
      response,
      "Service protocol could not handle or find a handler for the "
      "requested method.");
  return false;
}

static std::string CreateFlutterViewID(intptr_t handler) {
  std::stringstream stream;
  stream << kViewIdPrefx << "0x" << std::hex << handler;
  return stream.str();
}

static std::string CreateIsolateID(int64_t isolate) {
  std::stringstream stream;
  stream << "isolates/" << isolate;
  return stream.str();
}

void ServiceProtocol::Handler::Description::Write(
    Handler* handler,
    rapidjson::Value& view,
    rapidjson::MemoryPoolAllocator<>& allocator) const {
  view.SetObject();
  view.AddMember("type", "FlutterView", allocator);
  view.AddMember("id", CreateFlutterViewID(reinterpret_cast<intptr_t>(handler)),
                 allocator);
  if (isolate_port != 0) {
    rapidjson::Value isolate(rapidjson::Type::kObjectType);
    {
      isolate.AddMember("type", "@Isolate", allocator);
      isolate.AddMember("fixedId", true, allocator);
      isolate.AddMember("id", CreateIsolateID(isolate_port), allocator);
      isolate.AddMember("name", isolate_name, allocator);
      isolate.AddMember("number", isolate_port, allocator);
    }
    view.AddMember("isolate", isolate, allocator);
  }
}

bool ServiceProtocol::HandleListViewsMethod(
    rapidjson::Document& response) const {
  fml::SharedLock lock(*handlers_mutex_);
  std::vector<std::pair<intptr_t, Handler::Description>> descriptions;
  for (const auto& handler : handlers_) {
    descriptions.emplace_back(reinterpret_cast<intptr_t>(handler.first),
                              handler.second.Load());
  }

  auto& allocator = response.GetAllocator();

  // Construct the response objects.
  response.SetObject();
  response.AddMember("type", "FlutterViewList", allocator);

  rapidjson::Value viewsList(rapidjson::Type::kArrayType);
  for (const auto& description : descriptions) {
    rapidjson::Value view(rapidjson::Type::kObjectType);
    description.second.Write(reinterpret_cast<Handler*>(description.first),
                             view, allocator);
    viewsList.PushBack(view, allocator);
  }

  response.AddMember("views", viewsList, allocator);

  return true;
}

}  // namespace flutter