// Copyright 2014 The Chromium 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 "extensions/browser/api/serial/serial_api.h"

#include <algorithm>
#include <vector>

#include "base/task_scheduler/post_task.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/service_manager_connection.h"
#include "extensions/browser/api/serial/serial_connection.h"
#include "extensions/browser/api/serial/serial_event_dispatcher.h"
#include "extensions/common/api/serial.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/device/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"

using content::BrowserThread;

namespace extensions {

namespace api {

namespace {

// It's a fool's errand to come up with a default bitrate, because we don't get
// to control both sides of the communication. Unless the other side has
// implemented auto-bitrate detection (rare), if we pick the wrong rate, then
// you're gonna have a bad time. Close doesn't count.
//
// But we'd like to pick something that has a chance of working, and 9600 is a
// good balance between popularity and speed. So 9600 it is.
const int kDefaultBufferSize = 4096;
const int kDefaultBitrate = 9600;
const serial::DataBits kDefaultDataBits = serial::DATA_BITS_EIGHT;
const serial::ParityBit kDefaultParityBit = serial::PARITY_BIT_NO;
const serial::StopBits kDefaultStopBits = serial::STOP_BITS_ONE;
const int kDefaultReceiveTimeout = 0;
const int kDefaultSendTimeout = 0;

const char kErrorConnectFailed[] = "Failed to connect to the port.";
const char kErrorSerialConnectionNotFound[] = "Serial connection not found.";
const char kErrorGetControlSignalsFailed[] = "Failed to get control signals.";

template <class T>
void SetDefaultScopedPtrValue(std::unique_ptr<T>& ptr, const T& value) {
  if (!ptr.get())
    ptr.reset(new T(value));
}

}  // namespace

SerialAsyncApiFunction::SerialAsyncApiFunction() : manager_(nullptr) {}

SerialAsyncApiFunction::~SerialAsyncApiFunction() {}

bool SerialAsyncApiFunction::PrePrepare() {
  manager_ = ApiResourceManager<SerialConnection>::Get(browser_context());
  DCHECK(manager_);
  return true;
}

bool SerialAsyncApiFunction::Respond() {
  return error_.empty();
}

SerialConnection* SerialAsyncApiFunction::GetSerialConnection(
    int api_resource_id) {
  return manager_->Get(extension_->id(), api_resource_id);
}

void SerialAsyncApiFunction::RemoveSerialConnection(int api_resource_id) {
  manager_->Remove(extension_->id(), api_resource_id);
}

SerialGetDevicesFunction::SerialGetDevicesFunction() {}

SerialGetDevicesFunction::~SerialGetDevicesFunction() {}

ExtensionFunction::ResponseAction SerialGetDevicesFunction::Run() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(content::ServiceManagerConnection::GetForProcess());
  content::ServiceManagerConnection::GetForProcess()
      ->GetConnector()
      ->BindInterface(device::mojom::kServiceName,
                      mojo::MakeRequest(&enumerator_));
  enumerator_.set_connection_error_handler(
      base::BindOnce(&SerialGetDevicesFunction::OnGotDevices, this,
                     std::vector<device::mojom::SerialDeviceInfoPtr>()));
  enumerator_->GetDevices(
      base::BindOnce(&SerialGetDevicesFunction::OnGotDevices, this));
  return RespondLater();
}

void SerialGetDevicesFunction::OnGotDevices(
    std::vector<device::mojom::SerialDeviceInfoPtr> devices) {
  std::unique_ptr<base::ListValue> results =
      serial::GetDevices::Results::Create(
          mojo::ConvertTo<std::vector<serial::DeviceInfo>>(devices));
  Respond(ArgumentList(std::move(results)));
}

SerialConnectFunction::SerialConnectFunction() {}

SerialConnectFunction::~SerialConnectFunction() {}

bool SerialConnectFunction::Prepare() {
  params_ = serial::Connect::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  // Fill in any omitted options to ensure a known initial configuration.
  if (!params_->options.get())
    params_->options.reset(new serial::ConnectionOptions());
  serial::ConnectionOptions* options = params_->options.get();

  SetDefaultScopedPtrValue(options->persistent, false);
  SetDefaultScopedPtrValue(options->buffer_size, kDefaultBufferSize);
  SetDefaultScopedPtrValue(options->bitrate, kDefaultBitrate);
  SetDefaultScopedPtrValue(options->cts_flow_control, false);
  SetDefaultScopedPtrValue(options->receive_timeout, kDefaultReceiveTimeout);
  SetDefaultScopedPtrValue(options->send_timeout, kDefaultSendTimeout);

  if (options->data_bits == serial::DATA_BITS_NONE)
    options->data_bits = kDefaultDataBits;
  if (options->parity_bit == serial::PARITY_BIT_NONE)
    options->parity_bit = kDefaultParityBit;
  if (options->stop_bits == serial::STOP_BITS_NONE)
    options->stop_bits = kDefaultStopBits;

  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(content::ServiceManagerConnection::GetForProcess());
  content::ServiceManagerConnection::GetForProcess()
      ->GetConnector()
      ->BindInterface(device::mojom::kServiceName,
                      mojo::MakeRequest(&io_handler_info_));

  serial_event_dispatcher_ = SerialEventDispatcher::Get(browser_context());
  DCHECK(serial_event_dispatcher_);

  return true;
}

void SerialConnectFunction::AsyncWorkStart() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  connection_ = std::make_unique<SerialConnection>(
      params_->path, extension_->id(), std::move(io_handler_info_));
  connection_->Open(*params_->options,
                    base::BindOnce(&SerialConnectFunction::OnConnected, this));
}

void SerialConnectFunction::OnConnected(bool success) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(connection_);

  if (!success) {
    FinishConnect(false, false, nullptr);
    return;
  }

  connection_->GetInfo(
      base::BindOnce(&SerialConnectFunction::FinishConnect, this, true));
}

void SerialConnectFunction::FinishConnect(
    bool connected,
    bool got_complete_info,
    std::unique_ptr<serial::ConnectionInfo> info) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(connection_);
  if (!connected || !got_complete_info) {
    error_ = kErrorConnectFailed;
    connection_.reset();
  } else {
    DCHECK(info);
    int id = manager_->Add(connection_.release());
    // If a SerialConnection encountered a mojo connection error, it just
    // becomes useless, we won't try to re-connect it but just remove it
    // completely.
    GetSerialConnection(id)->set_connection_error_handler(base::BindOnce(
        [](scoped_refptr<ApiResourceManager<SerialConnection>::ApiResourceData>
               connections,
           std::string extension_id, int api_resource_id) {
          connections->Remove(extension_id, api_resource_id);
        },
        manager_->data_, extension_->id(), id));

    info->connection_id = id;
    serial_event_dispatcher_->PollConnection(extension_->id(), id);
    results_ = serial::Connect::Results::Create(*info);
  }
  AsyncWorkCompleted();
}

SerialUpdateFunction::SerialUpdateFunction() {}

SerialUpdateFunction::~SerialUpdateFunction() {}

bool SerialUpdateFunction::Prepare() {
  params_ = serial::Update::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  return true;
}

void SerialUpdateFunction::AsyncWorkStart() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    AsyncWorkCompleted();
    return;
  }
  connection->Configure(params_->options,
                        base::BindOnce(&SerialUpdateFunction::OnUpdated, this));
}

void SerialUpdateFunction::OnUpdated(bool success) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  results_ = serial::Update::Results::Create(success);
  AsyncWorkCompleted();
}

SerialDisconnectFunction::SerialDisconnectFunction() {}

SerialDisconnectFunction::~SerialDisconnectFunction() {}

bool SerialDisconnectFunction::Prepare() {
  params_ = serial::Disconnect::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  return true;
}

void SerialDisconnectFunction::Work() {
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    return;
  }
  RemoveSerialConnection(params_->connection_id);
  results_ = serial::Disconnect::Results::Create(true);
}

SerialSendFunction::SerialSendFunction() {}

SerialSendFunction::~SerialSendFunction() {}

bool SerialSendFunction::Prepare() {
  params_ = serial::Send::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  return true;
}

void SerialSendFunction::AsyncWorkStart() {
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    AsyncWorkCompleted();
    return;
  }

  if (!connection->Send(
          params_->data,
          base::BindOnce(&SerialSendFunction::OnSendComplete, this))) {
    OnSendComplete(0, serial::SEND_ERROR_PENDING);
  }
}

void SerialSendFunction::OnSendComplete(uint32_t bytes_sent,
                                        serial::SendError error) {
  serial::SendInfo send_info;
  send_info.bytes_sent = bytes_sent;
  send_info.error = error;
  results_ = serial::Send::Results::Create(send_info);
  AsyncWorkCompleted();
}

SerialFlushFunction::SerialFlushFunction() {}

SerialFlushFunction::~SerialFlushFunction() {}

bool SerialFlushFunction::Prepare() {
  params_ = serial::Flush::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());
  return true;
}

void SerialFlushFunction::AsyncWorkStart() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    AsyncWorkCompleted();
    return;
  }
  connection->Flush(base::BindOnce(&SerialFlushFunction::OnFlushed, this));
}

void SerialFlushFunction::OnFlushed(bool success) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  results_ = serial::Flush::Results::Create(success);
  AsyncWorkCompleted();
}

SerialSetPausedFunction::SerialSetPausedFunction() {}

SerialSetPausedFunction::~SerialSetPausedFunction() {}

bool SerialSetPausedFunction::Prepare() {
  params_ = serial::SetPaused::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  serial_event_dispatcher_ = SerialEventDispatcher::Get(browser_context());
  DCHECK(serial_event_dispatcher_);
  return true;
}

void SerialSetPausedFunction::Work() {
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    return;
  }

  if (params_->paused != connection->paused()) {
    connection->set_paused(params_->paused);
    if (!params_->paused) {
      serial_event_dispatcher_->PollConnection(extension_->id(),
                                               params_->connection_id);
    }
  }

  results_ = serial::SetPaused::Results::Create();
}

SerialGetInfoFunction::SerialGetInfoFunction() {}

SerialGetInfoFunction::~SerialGetInfoFunction() {}

bool SerialGetInfoFunction::Prepare() {
  params_ = serial::GetInfo::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  return true;
}

void SerialGetInfoFunction::AsyncWorkStart() {
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    AsyncWorkCompleted();
    return;
  }

  connection->GetInfo(base::BindOnce(&SerialGetInfoFunction::OnGotInfo, this));
}

void SerialGetInfoFunction::OnGotInfo(
    bool got_complete_info,
    std::unique_ptr<serial::ConnectionInfo> info) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(info);
  info->connection_id = params_->connection_id;
  results_ = serial::GetInfo::Results::Create(*info);

  AsyncWorkCompleted();
}

SerialGetConnectionsFunction::SerialGetConnectionsFunction() {}

SerialGetConnectionsFunction::~SerialGetConnectionsFunction() {}

bool SerialGetConnectionsFunction::Prepare() {
  return true;
}

void SerialGetConnectionsFunction::AsyncWorkStart() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  const base::hash_set<int>* connection_ids =
      manager_->GetResourceIds(extension_->id());
  if (connection_ids) {
    for (base::hash_set<int>::const_iterator it = connection_ids->begin();
         it != connection_ids->end(); ++it) {
      int connection_id = *it;
      SerialConnection* connection = GetSerialConnection(connection_id);
      if (connection) {
        count_++;
        connection->GetInfo(base::BindOnce(
            &SerialGetConnectionsFunction::OnGotOne, this, connection_id));
      }
    }
  }
  if (count_ > 0)
    return;

  OnGotAll();
}

void SerialGetConnectionsFunction::OnGotOne(
    int connection_id,
    bool got_complete_info,
    std::unique_ptr<serial::ConnectionInfo> info) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(info);
  info->connection_id = connection_id;
  infos_.push_back(std::move(*info));

  if (infos_.size() == count_) {
    OnGotAll();
  }
}

void SerialGetConnectionsFunction::OnGotAll() {
  results_ = serial::GetConnections::Results::Create(infos_);
  AsyncWorkCompleted();
}

SerialGetControlSignalsFunction::SerialGetControlSignalsFunction() {}

SerialGetControlSignalsFunction::~SerialGetControlSignalsFunction() {}

bool SerialGetControlSignalsFunction::Prepare() {
  params_ = serial::GetControlSignals::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  return true;
}

void SerialGetControlSignalsFunction::AsyncWorkStart() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    AsyncWorkCompleted();
    return;
  }

  connection->GetControlSignals(base::BindOnce(
      &SerialGetControlSignalsFunction::OnGotControlSignals, this));
}

void SerialGetControlSignalsFunction::OnGotControlSignals(
    std::unique_ptr<serial::DeviceControlSignals> signals) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (!signals) {
    error_ = kErrorGetControlSignalsFailed;
  } else {
    results_ = serial::GetControlSignals::Results::Create(*signals);
  }

  AsyncWorkCompleted();
}

SerialSetControlSignalsFunction::SerialSetControlSignalsFunction() {}

SerialSetControlSignalsFunction::~SerialSetControlSignalsFunction() {}

bool SerialSetControlSignalsFunction::Prepare() {
  params_ = serial::SetControlSignals::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  return true;
}

void SerialSetControlSignalsFunction::AsyncWorkStart() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    AsyncWorkCompleted();
    return;
  }

  connection->SetControlSignals(
      params_->signals,
      base::BindOnce(&SerialSetControlSignalsFunction::OnSetControlSignals,
                     this));
}

void SerialSetControlSignalsFunction::OnSetControlSignals(bool success) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  results_ = serial::SetControlSignals::Results::Create(success);
  AsyncWorkCompleted();
}

SerialSetBreakFunction::SerialSetBreakFunction() {}

SerialSetBreakFunction::~SerialSetBreakFunction() {}

bool SerialSetBreakFunction::Prepare() {
  params_ = serial::SetBreak::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  return true;
}

void SerialSetBreakFunction::AsyncWorkStart() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    AsyncWorkCompleted();
    return;
  }
  connection->SetBreak(
      base::BindOnce(&SerialSetBreakFunction::OnSetBreak, this));
}

void SerialSetBreakFunction::OnSetBreak(bool success) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  results_ = serial::SetBreak::Results::Create(success);
  AsyncWorkCompleted();
}

SerialClearBreakFunction::SerialClearBreakFunction() {}

SerialClearBreakFunction::~SerialClearBreakFunction() {}

bool SerialClearBreakFunction::Prepare() {
  params_ = serial::ClearBreak::Params::Create(*args_);
  EXTENSION_FUNCTION_VALIDATE(params_.get());

  return true;
}

void SerialClearBreakFunction::AsyncWorkStart() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  SerialConnection* connection = GetSerialConnection(params_->connection_id);
  if (!connection) {
    error_ = kErrorSerialConnectionNotFound;
    AsyncWorkCompleted();
    return;
  }
  connection->ClearBreak(
      base::BindOnce(&SerialClearBreakFunction::OnClearBreak, this));
}

void SerialClearBreakFunction::OnClearBreak(bool success) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  results_ = serial::ClearBreak::Results::Create(success);
  AsyncWorkCompleted();
}

}  // namespace api

}  // namespace extensions

namespace mojo {

// static
extensions::api::serial::DeviceInfo
TypeConverter<extensions::api::serial::DeviceInfo,
              device::mojom::SerialDeviceInfoPtr>::
    Convert(const device::mojom::SerialDeviceInfoPtr& device) {
  extensions::api::serial::DeviceInfo info;
  info.path = device->path;
  if (device->has_vendor_id)
    info.vendor_id.reset(new int(static_cast<int>(device->vendor_id)));
  if (device->has_product_id)
    info.product_id.reset(new int(static_cast<int>(device->product_id)));
  if (device->display_name)
    info.display_name.reset(new std::string(device->display_name.value()));
  return info;
}

}  // namespace mojo
