// 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_connection.h"

#include <string>

#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "extensions/browser/api/api_resource_manager.h"
#include "extensions/common/api/serial.h"

namespace extensions {

namespace {

const int kDefaultBufferSize = 4096;

core_api::serial::SendError ConvertSendErrorFromMojo(
    device::serial::SendError input) {
  switch (input) {
    case device::serial::SEND_ERROR_NONE:
      return core_api::serial::SEND_ERROR_NONE;
    case device::serial::SEND_ERROR_DISCONNECTED:
      return core_api::serial::SEND_ERROR_DISCONNECTED;
    case device::serial::SEND_ERROR_PENDING:
      return core_api::serial::SEND_ERROR_PENDING;
    case device::serial::SEND_ERROR_TIMEOUT:
      return core_api::serial::SEND_ERROR_TIMEOUT;
    case device::serial::SEND_ERROR_SYSTEM_ERROR:
      return core_api::serial::SEND_ERROR_SYSTEM_ERROR;
  }
  return core_api::serial::SEND_ERROR_NONE;
}

core_api::serial::ReceiveError ConvertReceiveErrorFromMojo(
    device::serial::ReceiveError input) {
  switch (input) {
    case device::serial::RECEIVE_ERROR_NONE:
      return core_api::serial::RECEIVE_ERROR_NONE;
    case device::serial::RECEIVE_ERROR_DISCONNECTED:
      return core_api::serial::RECEIVE_ERROR_DISCONNECTED;
    case device::serial::RECEIVE_ERROR_TIMEOUT:
      return core_api::serial::RECEIVE_ERROR_TIMEOUT;
    case device::serial::RECEIVE_ERROR_DEVICE_LOST:
      return core_api::serial::RECEIVE_ERROR_DEVICE_LOST;
    case device::serial::RECEIVE_ERROR_SYSTEM_ERROR:
      return core_api::serial::RECEIVE_ERROR_SYSTEM_ERROR;
  }
  return core_api::serial::RECEIVE_ERROR_NONE;
}

core_api::serial::DataBits ConvertDataBitsFromMojo(
    device::serial::DataBits input) {
  switch (input) {
    case device::serial::DATA_BITS_NONE:
      return core_api::serial::DATA_BITS_NONE;
    case device::serial::DATA_BITS_SEVEN:
      return core_api::serial::DATA_BITS_SEVEN;
    case device::serial::DATA_BITS_EIGHT:
      return core_api::serial::DATA_BITS_EIGHT;
  }
  return core_api::serial::DATA_BITS_NONE;
}

device::serial::DataBits ConvertDataBitsToMojo(
    core_api::serial::DataBits input) {
  switch (input) {
    case core_api::serial::DATA_BITS_NONE:
      return device::serial::DATA_BITS_NONE;
    case core_api::serial::DATA_BITS_SEVEN:
      return device::serial::DATA_BITS_SEVEN;
    case core_api::serial::DATA_BITS_EIGHT:
      return device::serial::DATA_BITS_EIGHT;
  }
  return device::serial::DATA_BITS_NONE;
}

core_api::serial::ParityBit ConvertParityBitFromMojo(
    device::serial::ParityBit input) {
  switch (input) {
    case device::serial::PARITY_BIT_NONE:
      return core_api::serial::PARITY_BIT_NONE;
    case device::serial::PARITY_BIT_ODD:
      return core_api::serial::PARITY_BIT_ODD;
    case device::serial::PARITY_BIT_NO:
      return core_api::serial::PARITY_BIT_NO;
    case device::serial::PARITY_BIT_EVEN:
      return core_api::serial::PARITY_BIT_EVEN;
  }
  return core_api::serial::PARITY_BIT_NONE;
}

device::serial::ParityBit ConvertParityBitToMojo(
    core_api::serial::ParityBit input) {
  switch (input) {
    case core_api::serial::PARITY_BIT_NONE:
      return device::serial::PARITY_BIT_NONE;
    case core_api::serial::PARITY_BIT_NO:
      return device::serial::PARITY_BIT_NO;
    case core_api::serial::PARITY_BIT_ODD:
      return device::serial::PARITY_BIT_ODD;
    case core_api::serial::PARITY_BIT_EVEN:
      return device::serial::PARITY_BIT_EVEN;
  }
  return device::serial::PARITY_BIT_NONE;
}

core_api::serial::StopBits ConvertStopBitsFromMojo(
    device::serial::StopBits input) {
  switch (input) {
    case device::serial::STOP_BITS_NONE:
      return core_api::serial::STOP_BITS_NONE;
    case device::serial::STOP_BITS_ONE:
      return core_api::serial::STOP_BITS_ONE;
    case device::serial::STOP_BITS_TWO:
      return core_api::serial::STOP_BITS_TWO;
  }
  return core_api::serial::STOP_BITS_NONE;
}

device::serial::StopBits ConvertStopBitsToMojo(
    core_api::serial::StopBits input) {
  switch (input) {
    case core_api::serial::STOP_BITS_NONE:
      return device::serial::STOP_BITS_NONE;
    case core_api::serial::STOP_BITS_ONE:
      return device::serial::STOP_BITS_ONE;
    case core_api::serial::STOP_BITS_TWO:
      return device::serial::STOP_BITS_TWO;
  }
  return device::serial::STOP_BITS_NONE;
}

class SendBuffer : public device::ReadOnlyBuffer {
 public:
  SendBuffer(
      const std::string& data,
      const base::Callback<void(int, device::serial::SendError)>& callback)
      : data_(data), callback_(callback) {}
  virtual ~SendBuffer() {}
  virtual const char* GetData() OVERRIDE { return data_.c_str(); }
  virtual uint32_t GetSize() OVERRIDE {
    return static_cast<uint32_t>(data_.size());
  }
  virtual void Done(uint32_t bytes_read) OVERRIDE {
    callback_.Run(bytes_read, device::serial::SEND_ERROR_NONE);
  }
  virtual void DoneWithError(uint32_t bytes_read, int32_t error) OVERRIDE {
    callback_.Run(bytes_read, static_cast<device::serial::SendError>(error));
  }

 private:
  const std::string data_;
  const base::Callback<void(int, device::serial::SendError)> callback_;
};

class ReceiveBuffer : public device::WritableBuffer {
 public:
  ReceiveBuffer(
      scoped_refptr<net::IOBuffer> buffer,
      uint32_t size,
      const base::Callback<void(int, device::serial::ReceiveError)>& callback)
      : buffer_(buffer), size_(size), callback_(callback) {}
  virtual ~ReceiveBuffer() {}
  virtual char* GetData() OVERRIDE { return buffer_->data(); }
  virtual uint32_t GetSize() OVERRIDE { return size_; }
  virtual void Done(uint32_t bytes_written) OVERRIDE {
    callback_.Run(bytes_written, device::serial::RECEIVE_ERROR_NONE);
  }
  virtual void DoneWithError(uint32_t bytes_written, int32_t error) OVERRIDE {
    callback_.Run(bytes_written,
                  static_cast<device::serial::ReceiveError>(error));
  }

 private:
  scoped_refptr<net::IOBuffer> buffer_;
  const uint32_t size_;
  const base::Callback<void(int, device::serial::ReceiveError)> callback_;
};

}  // namespace

static base::LazyInstance<
    BrowserContextKeyedAPIFactory<ApiResourceManager<SerialConnection> > >
    g_factory = LAZY_INSTANCE_INITIALIZER;

// static
template <>
BrowserContextKeyedAPIFactory<ApiResourceManager<SerialConnection> >*
ApiResourceManager<SerialConnection>::GetFactoryInstance() {
  return g_factory.Pointer();
}

SerialConnection::SerialConnection(const std::string& port,
                                   const std::string& owner_extension_id)
    : ApiResource(owner_extension_id),
      port_(port),
      persistent_(false),
      buffer_size_(kDefaultBufferSize),
      receive_timeout_(0),
      send_timeout_(0),
      paused_(false),
      io_handler_(device::SerialIoHandler::Create(
          content::BrowserThread::GetMessageLoopProxyForThread(
              content::BrowserThread::FILE))) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
}

SerialConnection::~SerialConnection() {
  io_handler_->CancelRead(device::serial::RECEIVE_ERROR_DISCONNECTED);
  io_handler_->CancelWrite(device::serial::SEND_ERROR_DISCONNECTED);
}

bool SerialConnection::IsPersistent() const {
  return persistent();
}

void SerialConnection::set_buffer_size(int buffer_size) {
  buffer_size_ = buffer_size;
}

void SerialConnection::set_receive_timeout(int receive_timeout) {
  receive_timeout_ = receive_timeout;
}

void SerialConnection::set_send_timeout(int send_timeout) {
  send_timeout_ = send_timeout;
}

void SerialConnection::set_paused(bool paused) {
  paused_ = paused;
  if (paused) {
    io_handler_->CancelRead(device::serial::RECEIVE_ERROR_NONE);
  }
}

void SerialConnection::Open(const OpenCompleteCallback& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  io_handler_->Open(port_, callback);
}

bool SerialConnection::Receive(const ReceiveCompleteCallback& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (!receive_complete_.is_null())
    return false;
  receive_complete_ = callback;
  receive_buffer_ = new net::IOBuffer(buffer_size_);
  io_handler_->Read(scoped_ptr<device::WritableBuffer>(new ReceiveBuffer(
      receive_buffer_,
      buffer_size_,
      base::Bind(&SerialConnection::OnAsyncReadComplete, AsWeakPtr()))));
  receive_timeout_task_.reset();
  if (receive_timeout_ > 0) {
    receive_timeout_task_.reset(new TimeoutTask(
        base::Bind(&SerialConnection::OnReceiveTimeout, AsWeakPtr()),
        base::TimeDelta::FromMilliseconds(receive_timeout_)));
  }
  return true;
}

bool SerialConnection::Send(const std::string& data,
                            const SendCompleteCallback& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (!send_complete_.is_null())
    return false;
  send_complete_ = callback;
  io_handler_->Write(scoped_ptr<device::ReadOnlyBuffer>(new SendBuffer(
      data, base::Bind(&SerialConnection::OnAsyncWriteComplete, AsWeakPtr()))));
  send_timeout_task_.reset();
  if (send_timeout_ > 0) {
    send_timeout_task_.reset(new TimeoutTask(
        base::Bind(&SerialConnection::OnSendTimeout, AsWeakPtr()),
        base::TimeDelta::FromMilliseconds(send_timeout_)));
  }
  return true;
}

bool SerialConnection::Configure(
    const core_api::serial::ConnectionOptions& options) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (options.persistent.get())
    set_persistent(*options.persistent);
  if (options.name.get())
    set_name(*options.name);
  if (options.buffer_size.get())
    set_buffer_size(*options.buffer_size);
  if (options.receive_timeout.get())
    set_receive_timeout(*options.receive_timeout);
  if (options.send_timeout.get())
    set_send_timeout(*options.send_timeout);
  bool success = io_handler_->ConfigurePort(
      *device::serial::ConnectionOptions::From(options));
  io_handler_->CancelRead(device::serial::RECEIVE_ERROR_NONE);
  return success;
}

void SerialConnection::SetIoHandlerForTest(
    scoped_refptr<device::SerialIoHandler> handler) {
  io_handler_ = handler;
}

bool SerialConnection::GetInfo(core_api::serial::ConnectionInfo* info) const {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  info->paused = paused_;
  info->persistent = persistent_;
  info->name = name_;
  info->buffer_size = buffer_size_;
  info->receive_timeout = receive_timeout_;
  info->send_timeout = send_timeout_;
  device::serial::ConnectionInfoPtr port_info = io_handler_->GetPortInfo();
  if (!port_info)
    return false;

  info->bitrate.reset(new int(port_info->bitrate));
  info->data_bits = ConvertDataBitsFromMojo(port_info->data_bits);
  info->parity_bit = ConvertParityBitFromMojo(port_info->parity_bit);
  info->stop_bits = ConvertStopBitsFromMojo(port_info->stop_bits);
  info->cts_flow_control.reset(new bool(port_info->cts_flow_control));
  return true;
}

bool SerialConnection::Flush() const {
  return io_handler_->Flush();
}

bool SerialConnection::GetControlSignals(
    core_api::serial::DeviceControlSignals* control_signals) const {
  device::serial::DeviceControlSignalsPtr signals =
      io_handler_->GetControlSignals();
  if (!signals)
    return false;

  control_signals->dcd = signals->dcd;
  control_signals->cts = signals->cts;
  control_signals->ri = signals->ri;
  control_signals->dsr = signals->dsr;
  return true;
}

bool SerialConnection::SetControlSignals(
    const core_api::serial::HostControlSignals& control_signals) {
  return io_handler_->SetControlSignals(
      *device::serial::HostControlSignals::From(control_signals));
}

void SerialConnection::OnReceiveTimeout() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  io_handler_->CancelRead(device::serial::RECEIVE_ERROR_TIMEOUT);
}

void SerialConnection::OnSendTimeout() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  io_handler_->CancelWrite(device::serial::SEND_ERROR_TIMEOUT);
}

void SerialConnection::OnAsyncReadComplete(int bytes_read,
                                           device::serial::ReceiveError error) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(!receive_complete_.is_null());
  ReceiveCompleteCallback callback = receive_complete_;
  receive_complete_.Reset();
  receive_timeout_task_.reset();
  callback.Run(std::string(receive_buffer_->data(), bytes_read),
               ConvertReceiveErrorFromMojo(error));
  receive_buffer_ = NULL;
}

void SerialConnection::OnAsyncWriteComplete(int bytes_sent,
                                            device::serial::SendError error) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(!send_complete_.is_null());
  SendCompleteCallback callback = send_complete_;
  send_complete_.Reset();
  send_timeout_task_.reset();
  callback.Run(bytes_sent, ConvertSendErrorFromMojo(error));
}

SerialConnection::TimeoutTask::TimeoutTask(const base::Closure& closure,
                                           const base::TimeDelta& delay)
    : weak_factory_(this), closure_(closure), delay_(delay) {
  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&TimeoutTask::Run, weak_factory_.GetWeakPtr()),
      delay_);
}

SerialConnection::TimeoutTask::~TimeoutTask() {
}

void SerialConnection::TimeoutTask::Run() const {
  closure_.Run();
}

}  // namespace extensions

namespace mojo {

// static
device::serial::HostControlSignalsPtr
TypeConverter<device::serial::HostControlSignalsPtr,
              extensions::core_api::serial::HostControlSignals>::
    Convert(const extensions::core_api::serial::HostControlSignals& input) {
  device::serial::HostControlSignalsPtr output(
      device::serial::HostControlSignals::New());
  if (input.dtr.get()) {
    output->has_dtr = true;
    output->dtr = *input.dtr;
  }
  if (input.rts.get()) {
    output->has_rts = true;
    output->rts = *input.rts;
  }
  return output.Pass();
}

// static
device::serial::ConnectionOptionsPtr
TypeConverter<device::serial::ConnectionOptionsPtr,
              extensions::core_api::serial::ConnectionOptions>::
    Convert(const extensions::core_api::serial::ConnectionOptions& input) {
  device::serial::ConnectionOptionsPtr output(
      device::serial::ConnectionOptions::New());
  if (input.bitrate.get() && *input.bitrate > 0)
    output->bitrate = *input.bitrate;
  output->data_bits = extensions::ConvertDataBitsToMojo(input.data_bits);
  output->parity_bit = extensions::ConvertParityBitToMojo(input.parity_bit);
  output->stop_bits = extensions::ConvertStopBitsToMojo(input.stop_bits);
  if (input.cts_flow_control.get()) {
    output->has_cts_flow_control = true;
    output->cts_flow_control = *input.cts_flow_control;
  }
  return output.Pass();
}

}  // namespace mojo