// Copyright 2017 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 "services/device/serial/serial_port_impl.h"

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/single_thread_task_runner.h"
#include "services/device/serial/buffer.h"
#include "services/device/serial/serial_io_handler.h"

namespace device {

// static
void SerialPortImpl::Create(
    const base::FilePath& path,
    mojo::PendingReceiver<mojom::SerialPort> receiver,
    mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher,
    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
  // This SerialPortImpl is owned by |receiver| and |watcher|.
  new SerialPortImpl(
      device::SerialIoHandler::Create(path, std::move(ui_task_runner)),
      std::move(receiver), std::move(watcher));
}

// static
void SerialPortImpl::CreateForTesting(
    scoped_refptr<SerialIoHandler> io_handler,
    mojo::PendingReceiver<mojom::SerialPort> receiver,
    mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher) {
  // This SerialPortImpl is owned by |receiver| and |watcher|.
  new SerialPortImpl(std::move(io_handler), std::move(receiver),
                     std::move(watcher));
}

SerialPortImpl::SerialPortImpl(
    scoped_refptr<SerialIoHandler> io_handler,
    mojo::PendingReceiver<mojom::SerialPort> receiver,
    mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher)
    : receiver_(this, std::move(receiver)),
      io_handler_(std::move(io_handler)),
      watcher_(std::move(watcher)),
      in_stream_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
      out_stream_watcher_(FROM_HERE,
                          mojo::SimpleWatcher::ArmingPolicy::MANUAL) {
  receiver_.set_disconnect_handler(base::BindOnce(
      [](SerialPortImpl* self) { delete self; }, base::Unretained(this)));
  if (watcher_.is_bound()) {
    watcher_.set_disconnect_handler(base::BindOnce(
        [](SerialPortImpl* self) { delete self; }, base::Unretained(this)));
  }
}

SerialPortImpl::~SerialPortImpl() {
  // Cancel I/O operations so that |io_handler_| drops its self-reference.
  io_handler_->Close(base::DoNothing());
}

void SerialPortImpl::Open(mojom::SerialConnectionOptionsPtr options,
                          mojo::PendingRemote<mojom::SerialPortClient> client,
                          OpenCallback callback) {
  if (client)
    client_.Bind(std::move(client));

  io_handler_->Open(*options, std::move(callback));
}

void SerialPortImpl::StartWriting(mojo::ScopedDataPipeConsumerHandle consumer) {
  if (in_stream_) {
    mojo::ReportBadMessage("Data pipe consumer still open.");
    return;
  }

  in_stream_watcher_.Cancel();
  in_stream_ = std::move(consumer);
  in_stream_watcher_.Watch(
      in_stream_.get(),
      MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
      MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
      base::BindRepeating(&SerialPortImpl::WriteToPort,
                          weak_factory_.GetWeakPtr()));
  in_stream_watcher_.ArmOrNotify();
}

void SerialPortImpl::StartReading(mojo::ScopedDataPipeProducerHandle producer) {
  if (out_stream_) {
    mojo::ReportBadMessage("Data pipe producer still open.");
    return;
  }

  out_stream_watcher_.Cancel();
  out_stream_ = std::move(producer);
  out_stream_watcher_.Watch(
      out_stream_.get(),
      MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
      MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
      base::BindRepeating(&SerialPortImpl::ReadFromPortAndWriteOut,
                          weak_factory_.GetWeakPtr()));
  out_stream_watcher_.ArmOrNotify();
}

void SerialPortImpl::Flush(mojom::SerialPortFlushMode mode,
                           FlushCallback callback) {
  switch (mode) {
    case mojom::SerialPortFlushMode::kReceiveAndTransmit:
      // Do nothing. This case exists to support the chrome.serial.flush()
      // method.
      break;
    case mojom::SerialPortFlushMode::kReceive:
      io_handler_->CancelRead(mojom::SerialReceiveError::NONE);
      break;
    case mojom::SerialPortFlushMode::kTransmit:
      io_handler_->CancelWrite(mojom::SerialSendError::NONE);
      break;
  }

  io_handler_->Flush(mode);

  switch (mode) {
    case mojom::SerialPortFlushMode::kReceiveAndTransmit:
      // Do nothing. This case exists to support the chrome.serial.flush()
      // method.
      break;
    case mojom::SerialPortFlushMode::kReceive:
      if (io_handler_->IsReadPending()) {
        // Delay closing |out_stream_| because |io_handler_| still holds a
        // pointer into the shared memory owned by the pipe.
        read_flush_callback_ = std::move(callback);
        return;
      }

      out_stream_watcher_.Cancel();
      out_stream_.reset();
      break;
    case mojom::SerialPortFlushMode::kTransmit:
      if (io_handler_->IsWritePending()) {
        // Delay closing |in_stream_| because |io_handler_| still holds a
        // pointer into the shared memory owned by the pipe.
        write_flush_callback_ = std::move(callback);
        return;
      }

      in_stream_watcher_.Cancel();
      in_stream_.reset();
      break;
  }

  std::move(callback).Run();
}

void SerialPortImpl::Drain(DrainCallback callback) {
  if (!in_stream_) {
    std::move(callback).Run();
    return;
  }

  drain_callback_ = std::move(callback);
}

void SerialPortImpl::GetControlSignals(GetControlSignalsCallback callback) {
  std::move(callback).Run(io_handler_->GetControlSignals());
}

void SerialPortImpl::SetControlSignals(
    mojom::SerialHostControlSignalsPtr signals,
    SetControlSignalsCallback callback) {
  std::move(callback).Run(io_handler_->SetControlSignals(*signals));
}

void SerialPortImpl::ConfigurePort(mojom::SerialConnectionOptionsPtr options,
                                   ConfigurePortCallback callback) {
  std::move(callback).Run(io_handler_->ConfigurePort(*options));
  // Cancel pending reading as the new configure options are applied.
  io_handler_->CancelRead(mojom::SerialReceiveError::NONE);
}

void SerialPortImpl::GetPortInfo(GetPortInfoCallback callback) {
  std::move(callback).Run(io_handler_->GetPortInfo());
}

void SerialPortImpl::Close(CloseCallback callback) {
  io_handler_->Close(std::move(callback));
}

void SerialPortImpl::WriteToPort(MojoResult result,
                                 const mojo::HandleSignalsState& state) {
  const void* buffer;
  uint32_t num_bytes;

  if (result == MOJO_RESULT_OK) {
    DCHECK(in_stream_);
    result = in_stream_->BeginReadData(&buffer, &num_bytes,
                                       MOJO_WRITE_DATA_FLAG_NONE);
  }
  if (result == MOJO_RESULT_OK) {
    io_handler_->Write(std::make_unique<SendBuffer>(
        static_cast<const uint8_t*>(buffer), num_bytes,
        base::BindOnce(&SerialPortImpl::OnWriteToPortCompleted,
                       weak_factory_.GetWeakPtr(), num_bytes)));
    return;
  }
  if (result == MOJO_RESULT_SHOULD_WAIT) {
    // If there is no space to write, wait for more space.
    in_stream_watcher_.ArmOrNotify();
    return;
  }
  if (result == MOJO_RESULT_FAILED_PRECONDITION ||
      result == MOJO_RESULT_CANCELLED) {
    // The |in_stream_| has been closed.
    in_stream_watcher_.Cancel();
    in_stream_.reset();

    if (drain_callback_) {
      io_handler_->Drain();
      std::move(drain_callback_).Run();
    }
    return;
  }
  // The code should not reach other cases.
  NOTREACHED();
}

void SerialPortImpl::OnWriteToPortCompleted(uint32_t bytes_expected,
                                            uint32_t bytes_sent,
                                            mojom::SerialSendError error) {
  DCHECK(in_stream_);
  in_stream_->EndReadData(bytes_sent);

  if (error != mojom::SerialSendError::NONE) {
    in_stream_watcher_.Cancel();
    in_stream_.reset();
    if (client_) {
      client_->OnSendError(error);
    }
    return;
  }

  in_stream_watcher_.ArmOrNotify();
}

void SerialPortImpl::ReadFromPortAndWriteOut(
    MojoResult result,
    const mojo::HandleSignalsState& state) {
  void* buffer;
  uint32_t num_bytes;
  if (result == MOJO_RESULT_OK) {
    DCHECK(out_stream_);
    result = out_stream_->BeginWriteData(&buffer, &num_bytes,
                                         MOJO_WRITE_DATA_FLAG_NONE);
  }
  if (result == MOJO_RESULT_OK) {
    io_handler_->Read(std::make_unique<ReceiveBuffer>(
        static_cast<char*>(buffer), num_bytes,
        base::BindOnce(&SerialPortImpl::WriteToOutStream,
                       weak_factory_.GetWeakPtr())));
    return;
  }
  if (result == MOJO_RESULT_SHOULD_WAIT) {
    // If there is no space to write, wait for more space.
    out_stream_watcher_.ArmOrNotify();
    return;
  }
  if (result == MOJO_RESULT_FAILED_PRECONDITION ||
      result == MOJO_RESULT_CANCELLED) {
    // The |out_stream_| has been closed.
    out_stream_watcher_.Cancel();
    out_stream_.reset();
    return;
  }
  // The code should not reach other cases.
  NOTREACHED() << "Unexpected Mojo result: " << result;
}

void SerialPortImpl::WriteToOutStream(uint32_t bytes_read,
                                      mojom::SerialReceiveError error) {
  DCHECK(out_stream_);
  out_stream_->EndWriteData(bytes_read);

  if (error != mojom::SerialReceiveError::NONE) {
    out_stream_watcher_.Cancel();
    out_stream_.reset();
    if (client_)
      client_->OnReadError(error);
    if (read_flush_callback_)
      std::move(read_flush_callback_).Run();
    return;
  }

  if (read_flush_callback_) {
    std::move(read_flush_callback_).Run();
    out_stream_watcher_.Cancel();
    out_stream_.reset();
    return;
  }

  out_stream_watcher_.ArmOrNotify();
}

}  // namespace device
