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

#include "services/device/serial/serial_device_enumerator_android.h"

#include <fcntl.h>

#include <cstdint>

#include "base/android/jni_string.h"
#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "components/device_event_log/device_event_log.h"
#include "services/device/serial/serial_io_handler_android.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "services/device/serial/jni_headers/ChromeSerialManager_jni.h"

namespace device {
using ::jni_zero::AttachCurrentThread;

SerialDeviceEnumeratorAndroid::SerialDeviceEnumeratorAndroid()
    : task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}

void SerialDeviceEnumeratorAndroid::Initialize() {
  JNIEnv* env = AttachCurrentThread();
  j_serial_manager_.Reset(
      Java_ChromeSerialManager_create(env, reinterpret_cast<int64_t>(this)));
  if (j_serial_manager_.is_null()) {
    SERIAL_LOG(ERROR) << "Could not find Android Serial Service";
    return;
  }
}

SerialDeviceEnumeratorAndroid::~SerialDeviceEnumeratorAndroid() {
  if (j_serial_manager_.is_null()) {
    return;
  }

  JNIEnv* env = AttachCurrentThread();
  Java_ChromeSerialManager_close(env, j_serial_manager_);
}

scoped_refptr<SerialIoHandler> SerialDeviceEnumeratorAndroid::CreateIoHandler(
    const base::FilePath& path,
    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
  return new SerialIoHandlerAndroid(path, std::move(ui_task_runner),
                                    weak_factory_.GetWeakPtr());
}

void SerialDeviceEnumeratorAndroid::OpenPath(const base::FilePath& path,
                                             OpenPathCallback callback,
                                             ErrorCallback error_callback) {
  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&SerialDeviceEnumeratorAndroid::OpenPathInternal,
                     base::Unretained(this), path, std::move(callback),
                     std::move(error_callback)));
}

void SerialDeviceEnumeratorAndroid::OpenPathInternal(
    const base::FilePath& path,
    OpenPathCallback callback,
    ErrorCallback error_callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (j_serial_manager_.is_null()) {
    std::move(error_callback)
        .Run("Android Serial Service is not available", path.value());
    return;
  }

  JNIEnv* env = AttachCurrentThread();

  const base::FilePath& file_path = path.BaseName();
  const std::string& name = file_path.value();
  if (callbacks_.find(name) != callbacks_.end()) {
    std::move(error_callback).Run("Already opening port", path.value());
    return;
  }
  callbacks_.emplace(name, std::make_unique<Callbacks>(
                               std::move(callback), std::move(error_callback)));
  const std::string& error =
      Java_ChromeSerialManager_openPort(env, j_serial_manager_, name);
  if (!error.empty()) {
    auto node = callbacks_.extract(name);
    if (node.empty()) {
      return;
    }
    std::move(node.mapped())->Error("Error opening port", error);
  }
}

void SerialDeviceEnumeratorAndroid::OpenPathCallbackViaJni(
    JNIEnv* env,
    const std::string& port_name,
    int32_t fd) {
  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&SerialDeviceEnumeratorAndroid::OpenPathCallbackInternal,
                     base::Unretained(this), port_name, fd));
}

void SerialDeviceEnumeratorAndroid::OpenPathCallbackInternal(
    const std::string& port_name,
    int32_t fd) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto node = callbacks_.extract(port_name);
  if (node.empty()) {
    return;
  }
  std::move(node.mapped())->Success(base::ScopedFD(fd));
}

void SerialDeviceEnumeratorAndroid::ErrorCallbackViaJni(
    JNIEnv* env,
    const std::string& port_name,
    const std::string& error_name,
    const std::string& message) {
  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&SerialDeviceEnumeratorAndroid::ErrorCallbackInternal,
                     base::Unretained(this), port_name, error_name, message));
}

void SerialDeviceEnumeratorAndroid::ErrorCallbackInternal(
    const std::string& port_name,
    const std::string& error_name,
    const std::string& message) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto node = callbacks_.extract(port_name);
  if (node.empty()) {
    return;
  }
  std::move(node.mapped())->Error(error_name, message);
}

void SerialDeviceEnumeratorAndroid::AddPortViaJni(JNIEnv* env,
                                                  const std::string& name,
                                                  int32_t vendor_id,
                                                  int32_t product_id,
                                                  bool initial_enumeration) {
  if (initial_enumeration) {
    // The class constructor invokes create() via JNI, which invokes this method
    // from Java, in this case we are already on the valid sequence.
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    AddPortName(name, vendor_id, product_id);
  } else {
    task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&SerialDeviceEnumeratorAndroid::AddPortName,
                       base::Unretained(this), name, vendor_id, product_id));
  }
}

void SerialDeviceEnumeratorAndroid::RemovePortViaJni(JNIEnv* env,
                                                     const std::string& name) {
  task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&SerialDeviceEnumeratorAndroid::RemovePortName,
                                base::Unretained(this), name));
}

void SerialDeviceEnumeratorAndroid::AddPortName(const std::string& name,
                                                int vendor_id,
                                                int product_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  mojom::SerialPortInfoPtr port = mojom::SerialPortInfo::New();
  port->path = base::FilePath("serial://").Append(name);
  port->vendor_id = vendor_id;
  port->has_vendor_id = (vendor_id >= 0);
  port->product_id = product_id;
  port->has_product_id = (product_id >= 0);
  base::UnguessableToken token = base::UnguessableToken::Create();
  tokens_.try_emplace(name, token);
  port->token = token;
  AddPort(std::move(port));
}

void SerialDeviceEnumeratorAndroid::RemovePortName(const std::string& name) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto node = tokens_.extract(name);
  if (node.empty()) {
    return;
  }
  RemovePort(node.mapped());
}

void SerialDeviceEnumeratorAndroid::SetSerialManagerForTesting(
    jni_zero::ScopedJavaLocalRef<jobject> j_serial_manager) {
  j_serial_manager_.Reset(std::move(j_serial_manager));
}

SerialDeviceEnumeratorAndroid::Callbacks::Callbacks(
    OpenPathCallback success_callback,
    ErrorCallback error_callback)
    : success_callback_(std::move(success_callback)),
      error_callback_(std::move(error_callback)) {}

SerialDeviceEnumeratorAndroid::Callbacks::~Callbacks() = default;

void SerialDeviceEnumeratorAndroid::Callbacks::Success(base::ScopedFD fd) {
  std::move(success_callback_).Run(std::move(fd));
}

void SerialDeviceEnumeratorAndroid::Callbacks::Error(
    const std::string& error_name,
    const std::string& message) {
  std::move(error_callback_).Run(error_name, message);
}

}  // namespace device

DEFINE_JNI(ChromeSerialManager)
