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

#include "chrome/browser/ash/arc/fileapi/file_stream_forwarder.h"

#include <algorithm>
#include <string_view>
#include <utility>

#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/net_errors.h"

using content::BrowserThread;

namespace arc {

namespace {

constexpr int kBufSize = 32 * 1024;

}  // namespace

FileStreamForwarder::FileStreamForwarder(
    scoped_refptr<storage::FileSystemContext> context,
    const storage::FileSystemURL& url,
    int64_t offset,
    int64_t size,
    base::ScopedFD fd_dest,
    ResultCallback callback)
    : context_(context),
      url_(url),
      offset_(offset),
      remaining_size_(size),
      fd_dest_(std::move(fd_dest)),
      callback_(std::move(callback)),
      task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          // It's safe to shutdown without waiting for the
          // completion of tasks running with this task runner.
          {base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN,
           base::MayBlock()})),
      buf_(base::MakeRefCounted<net::IOBufferWithSize>(kBufSize)) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&FileStreamForwarder::Start, base::Unretained(this)));
}

void FileStreamForwarder::Destroy() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&FileStreamForwarder::DestroyOnIOThread,
                                base::Unretained(this)));
}

FileStreamForwarder::~FileStreamForwarder() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (!callback_.is_null())  // Aborted before completion.
    NotifyCompleted(false);
  // Use the task runner to close the FD.
  task_runner_->PostTask(FROM_HERE,
                         base::DoNothingWithBoundArgs(std::move(fd_dest_)));
}

void FileStreamForwarder::DestroyOnIOThread() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  delete this;
}

void FileStreamForwarder::Start() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  stream_reader_ = context_->CreateFileStreamReader(
      url_, offset_, remaining_size_, base::Time());
  if (!stream_reader_) {
    LOG(ERROR) << "CreateFileStreamReader failed.";
    NotifyCompleted(false);
    return;
  }
  DoRead();
}

void FileStreamForwarder::DoRead() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (remaining_size_ == 0) {
    NotifyCompleted(true);
    return;
  }
  const int result = stream_reader_->Read(
      buf_.get(), std::min<int64_t>(buf_->size(), remaining_size_),
      base::BindOnce(&FileStreamForwarder::OnReadCompleted,
                     weak_ptr_factory_.GetWeakPtr()));
  if (result != net::ERR_IO_PENDING) {
    // Read result is returned synchronously.
    OnReadCompleted(result);
  }
}

void FileStreamForwarder::OnReadCompleted(int result) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (result <= 0) {
    if (result == 0) {
      LOG(ERROR) << remaining_size_ << " more bytes to read, but reached EOF.";
    } else {
      LOG(ERROR) << "Read failed " << net::ErrorToString(result);
    }
    NotifyCompleted(false);
    return;
  }
  remaining_size_ -= result;
  DCHECK_GE(remaining_size_, 0);

  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](int fd, scoped_refptr<net::IOBuffer> buf, int size) {
            const bool result = base::WriteFileDescriptor(
                fd, std::string_view(buf->data(), size));
            PLOG_IF(ERROR, !result) << "Write failed.";
            return result;
          },
          fd_dest_.get(), buf_, result),
      base::BindOnce(&FileStreamForwarder::OnWriteCompleted,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FileStreamForwarder::OnWriteCompleted(bool result) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (!result) {
    NotifyCompleted(false);
    return;
  }
  // Continue reading.
  DoRead();
}

void FileStreamForwarder::NotifyCompleted(bool result) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(!callback_.is_null());
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback_), result));
}

}  // namespace arc
