// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/services/orca/orca_library.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/scoped_native_library.h"
#include "base/types/expected.h"
#include "chromeos/ash/services/orca/public/cpp/orca_entry.h"
#include "mojo/public/c/system/thunks.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"

namespace ash::orca {
namespace {

base::FilePath GetDefaultOrcaLibraryPath() {
  // A relative path is fine because base::ScopedNativeLibrary will look for the
  // library in an architecture-dependent system path.
  return base::FilePath("libimedecoder.so");
}

logging::LogSeverity ConvertLogSeverity(OrcaLogSeverity severity) {
  switch (severity) {
    case OrcaLogSeverity::ORCA_LOG_SEVERITY_WARNING:
      return logging::LOGGING_WARNING;
    case OrcaLogSeverity::ORCA_LOG_SEVERITY_ERROR:
      return logging::LOGGING_ERROR;
  }
  NOTREACHED();
}

void DefaultLogSink(logging::LogSeverity severity, std::string_view message) {
  switch (severity) {
    case logging::LOGGING_WARNING:
      LOG(WARNING) << message;
      break;
    case logging::LOGGING_ERROR:
      LOG(ERROR) << message;
      break;
    default:
      NOTREACHED();
  }
}

std::unique_ptr<OrcaLogger> CreateOrcaLogger(OrcaLibrary::LogSink* log_sink) {
  auto logger = std::make_unique<OrcaLogger>();
  logger->user_data = reinterpret_cast<void*>(log_sink),
  logger->log = [](OrcaLogger* self, OrcaLogSeverity severity,
                   const char* message) {
    reinterpret_cast<OrcaLibrary::LogSink*>(self->user_data)
        ->Run(ConvertLogSeverity(severity), message);
  };
  return logger;
}

}  // namespace

OrcaLibrary::OrcaLibrary() : OrcaLibrary(GetDefaultOrcaLibraryPath()) {}

OrcaLibrary::OrcaLibrary(const base::FilePath& library_path)
    : OrcaLibrary(library_path, base::BindRepeating(DefaultLogSink)) {}

OrcaLibrary::OrcaLibrary(const base::FilePath& library_path, LogSink log_sink)
    : library_path_(library_path),
      log_sink_(std::move(log_sink)),
      orca_logger_(CreateOrcaLogger(&log_sink_)) {}

OrcaLibrary::~OrcaLibrary() {
  if (!library_.is_valid()) {
    return;
  }

  if (auto* reset_function = reinterpret_cast<decltype(OrcaResetService)*>(
          library_.GetFunctionPointer("OrcaResetService"))) {
    reset_function();
  }
}

base::expected<void, OrcaLibrary::BindError> OrcaLibrary::BindReceiver(
    mojo::PendingReceiver<mojom::OrcaService> receiver) {
  library_ = base::ScopedNativeLibrary(library_path_);
  if (!library_.is_valid()) {
    return base::unexpected(BindError{
        .code = BindErrorCode::kLoadFailed,
        .message = library_.GetError()->ToString(),
    });
  }

  auto* bind_function = reinterpret_cast<decltype(OrcaBindServiceV2)*>(
      library_.GetFunctionPointer("OrcaBindServiceV2"));
  if (!bind_function) {
    return base::unexpected(
        BindError{.code = BindErrorCode::kGetFunctionPointerFailed});
  }

  const MojoSystemThunks2* mojo_thunks = MojoEmbedderGetSystemThunks2();
  const MojoHandle receiver_handle = receiver.PassPipe().release().value();

  if (OrcaBindServiceStatus code =
          bind_function(mojo_thunks, receiver_handle, orca_logger_.get());
      code != OrcaBindServiceStatus::ORCA_BIND_SERVICE_STATUS_OK) {
    return base::unexpected(BindError{.code = BindErrorCode::kBindFailed});
  }

  return base::ok();
}

}  // namespace ash::orca
