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

#include "base/files/file_proxy.h"

#include <memory>
#include <optional>
#include <utility>

#include "base/containers/heap_array.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/task_runner.h"

namespace {

void FileDeleter(base::File file) {}

}  // namespace

namespace base {

class FileHelper {
 public:
  FileHelper(base::WeakPtr<FileProxy> proxy, File file)
      : file_(std::move(file)),
        task_runner_(proxy->task_runner()),
        proxy_(proxy) {}
  FileHelper(const FileHelper&) = delete;
  FileHelper& operator=(const FileHelper&) = delete;

  void PassFile() {
    if (proxy_) {
      proxy_->SetFile(std::move(file_));
    } else if (file_.IsValid()) {
      task_runner_->PostTask(FROM_HERE,
                             BindOnce(&FileDeleter, std::move(file_)));
    }
  }

 protected:
  File file_;
  File::Error error_ = File::FILE_ERROR_FAILED;

 private:
  scoped_refptr<TaskRunner> task_runner_;
  WeakPtr<FileProxy> proxy_;
};

namespace {

class GenericFileHelper : public FileHelper {
 public:
  GenericFileHelper(base::WeakPtr<FileProxy> proxy, File file)
      : FileHelper(std::move(proxy), std::move(file)) {}
  GenericFileHelper(const GenericFileHelper&) = delete;
  GenericFileHelper& operator=(const GenericFileHelper&) = delete;

  void Close() {
    file_.Close();
    error_ = File::FILE_OK;
  }

  void SetTimes(Time last_access_time, Time last_modified_time) {
    bool rv = file_.SetTimes(last_access_time, last_modified_time);
    error_ = rv ? File::FILE_OK : File::FILE_ERROR_FAILED;
  }

  void SetLength(int64_t length) {
    if (file_.SetLength(length)) {
      error_ = File::FILE_OK;
    }
  }

  void Flush() {
    if (file_.Flush()) {
      error_ = File::FILE_OK;
    }
  }

  void Reply(FileProxy::StatusCallback callback) {
    PassFile();
    if (!callback.is_null()) {
      std::move(callback).Run(error_);
    }
  }
};

class CreateOrOpenHelper : public FileHelper {
 public:
  CreateOrOpenHelper(base::WeakPtr<FileProxy> proxy, File file)
      : FileHelper(std::move(proxy), std::move(file)) {}
  CreateOrOpenHelper(const CreateOrOpenHelper&) = delete;
  CreateOrOpenHelper& operator=(const CreateOrOpenHelper&) = delete;

  void RunWork(const FilePath& file_path, uint32_t file_flags) {
    file_.Initialize(file_path, file_flags);
    error_ = file_.IsValid() ? File::FILE_OK : file_.error_details();
  }

  void Reply(FileProxy::StatusCallback callback) {
    DCHECK(!callback.is_null());
    PassFile();
    std::move(callback).Run(error_);
  }
};

class CreateTemporaryHelper : public FileHelper {
 public:
  CreateTemporaryHelper(base::WeakPtr<FileProxy> proxy, File file)
      : FileHelper(std::move(proxy), std::move(file)) {}
  CreateTemporaryHelper(const CreateTemporaryHelper&) = delete;
  CreateTemporaryHelper& operator=(const CreateTemporaryHelper&) = delete;

  void RunWork(uint32_t additional_file_flags) {
    // TODO(darin): file_util should have a variant of CreateTemporaryFile
    // that returns a FilePath and a File.
    if (!CreateTemporaryFile(&file_path_)) {
      // TODO(davidben): base::CreateTemporaryFile should preserve the error
      // code.
      error_ = File::FILE_ERROR_FAILED;
      return;
    }

    uint32_t file_flags = File::FLAG_WRITE | File::FLAG_WIN_TEMPORARY |
                          File::FLAG_CREATE_ALWAYS | additional_file_flags;

    file_.Initialize(file_path_, file_flags);
    if (file_.IsValid()) {
      error_ = File::FILE_OK;
    } else {
      error_ = file_.error_details();
      DeleteFile(file_path_);
      file_path_.clear();
    }
  }

  void Reply(FileProxy::CreateTemporaryCallback callback) {
    DCHECK(!callback.is_null());
    PassFile();
    std::move(callback).Run(error_, file_path_);
  }

 private:
  FilePath file_path_;
};

class GetInfoHelper : public FileHelper {
 public:
  GetInfoHelper(base::WeakPtr<FileProxy> proxy, File file)
      : FileHelper(std::move(proxy), std::move(file)) {}
  GetInfoHelper(const GetInfoHelper&) = delete;
  GetInfoHelper& operator=(const GetInfoHelper&) = delete;

  void RunWork() {
    if (file_.GetInfo(&file_info_)) {
      error_ = File::FILE_OK;
    }
  }

  void Reply(FileProxy::GetFileInfoCallback callback) {
    PassFile();
    DCHECK(!callback.is_null());
    std::move(callback).Run(error_, file_info_);
  }

 private:
  File::Info file_info_;
};

class ReadHelper : public FileHelper {
 public:
  ReadHelper(base::WeakPtr<FileProxy> proxy, File file, int bytes_to_read)
      : FileHelper(std::move(proxy), std::move(file)),
        // SAFETY - References to `buffer_` are provided as a span only after
        // successfully reading some bytes.
        buffer_(base::HeapArray<uint8_t>::Uninit(
            static_cast<size_t>(bytes_to_read))) {}

  ReadHelper(const ReadHelper&) = delete;
  ReadHelper& operator=(const ReadHelper&) = delete;

  void RunWork(int64_t offset) {
    std::optional<size_t> result = file_.Read(offset, buffer_);
    if (!result.has_value()) {
      bytes_read_ = -1;
      error_ = File::FILE_ERROR_FAILED;
      return;
    }
    bytes_read_ = checked_cast<int>(result.value());
    error_ = File::FILE_OK;
  }

  void Reply(FileProxy::ReadCallback callback) {
    PassFile();
    DCHECK(!callback.is_null());
    base::span<uint8_t> read_span;
    if (error_ == File::FILE_OK) {
      read_span = buffer_.first(checked_cast<size_t>(bytes_read_));
    }
    std::move(callback).Run(error_, base::as_chars(read_span));
  }

 private:
  base::HeapArray<uint8_t> buffer_;
  int bytes_read_ = 0;
};

class WriteHelper : public FileHelper {
 public:
  WriteHelper(base::WeakPtr<FileProxy> proxy,
              File file,
              base::span<const uint8_t> data)
      : FileHelper(std::move(proxy), std::move(file)),
        buffer_(base::HeapArray<uint8_t>::CopiedFrom(data)) {}

  WriteHelper(const WriteHelper&) = delete;
  WriteHelper& operator=(const WriteHelper&) = delete;

  void RunWork(int64_t offset) {
    std::optional<size_t> result = file_.Write(offset, buffer_);
    if (!result.has_value()) {
      bytes_written_ = -1;
      error_ = File::FILE_ERROR_FAILED;
      return;
    }
    bytes_written_ = checked_cast<int>(result.value());
    error_ = File::FILE_OK;
  }

  void Reply(FileProxy::WriteCallback callback) {
    PassFile();
    if (!callback.is_null()) {
      std::move(callback).Run(error_, bytes_written_);
    }
  }

 private:
  base::HeapArray<uint8_t> buffer_;
  int bytes_written_ = 0;
};

}  // namespace

FileProxy::FileProxy(TaskRunner* task_runner) : task_runner_(task_runner) {}

FileProxy::~FileProxy() {
  if (file_.IsValid()) {
    task_runner_->PostTask(FROM_HERE, BindOnce(&FileDeleter, std::move(file_)));
  }
}

bool FileProxy::CreateOrOpen(const FilePath& file_path,
                             uint32_t file_flags,
                             StatusCallback callback) {
  DCHECK(!file_.IsValid());
  CreateOrOpenHelper* helper =
      new CreateOrOpenHelper(weak_ptr_factory_.GetWeakPtr(), File());
  return task_runner_->PostTaskAndReply(
      FROM_HERE,
      BindOnce(&CreateOrOpenHelper::RunWork, Unretained(helper), file_path,
               file_flags),
      BindOnce(&CreateOrOpenHelper::Reply, Owned(helper), std::move(callback)));
}

bool FileProxy::CreateTemporary(uint32_t additional_file_flags,
                                CreateTemporaryCallback callback) {
  DCHECK(!file_.IsValid());
  CreateTemporaryHelper* helper =
      new CreateTemporaryHelper(weak_ptr_factory_.GetWeakPtr(), File());
  return task_runner_->PostTaskAndReply(
      FROM_HERE,
      BindOnce(&CreateTemporaryHelper::RunWork, Unretained(helper),
               additional_file_flags),
      BindOnce(&CreateTemporaryHelper::Reply, Owned(helper),
               std::move(callback)));
}

bool FileProxy::IsValid() const {
  return file_.IsValid();
}

void FileProxy::SetFile(File file) {
  DCHECK(!file_.IsValid());
  file_ = std::move(file);
}

File FileProxy::TakeFile() {
  return std::move(file_);
}

File FileProxy::DuplicateFile() {
  return file_.Duplicate();
}

PlatformFile FileProxy::GetPlatformFile() const {
  return file_.GetPlatformFile();
}

bool FileProxy::Close(StatusCallback callback) {
  DCHECK(file_.IsValid());
  GenericFileHelper* helper =
      new GenericFileHelper(weak_ptr_factory_.GetWeakPtr(), std::move(file_));
  return task_runner_->PostTaskAndReply(
      FROM_HERE, BindOnce(&GenericFileHelper::Close, Unretained(helper)),
      BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
}

bool FileProxy::GetInfo(GetFileInfoCallback callback) {
  DCHECK(file_.IsValid());
  GetInfoHelper* helper =
      new GetInfoHelper(weak_ptr_factory_.GetWeakPtr(), std::move(file_));
  return task_runner_->PostTaskAndReply(
      FROM_HERE, BindOnce(&GetInfoHelper::RunWork, Unretained(helper)),
      BindOnce(&GetInfoHelper::Reply, Owned(helper), std::move(callback)));
}

bool FileProxy::Read(int64_t offset, int bytes_to_read, ReadCallback callback) {
  DCHECK(file_.IsValid());
  if (bytes_to_read < 0) {
    return false;
  }

  ReadHelper* helper = new ReadHelper(weak_ptr_factory_.GetWeakPtr(),
                                      std::move(file_), bytes_to_read);
  return task_runner_->PostTaskAndReply(
      FROM_HERE, BindOnce(&ReadHelper::RunWork, Unretained(helper), offset),
      BindOnce(&ReadHelper::Reply, Owned(helper), std::move(callback)));
}

bool FileProxy::Write(int64_t offset,
                      base::span<const uint8_t> data,
                      WriteCallback callback) {
  DCHECK(file_.IsValid());
  if (data.empty()) {
    return false;
  }
  WriteHelper* helper =
      new WriteHelper(weak_ptr_factory_.GetWeakPtr(), std::move(file_), data);

  return task_runner_->PostTaskAndReply(
      FROM_HERE, BindOnce(&WriteHelper::RunWork, Unretained(helper), offset),
      BindOnce(&WriteHelper::Reply, Owned(helper), std::move(callback)));
}

bool FileProxy::SetTimes(Time last_access_time,
                         Time last_modified_time,
                         StatusCallback callback) {
  DCHECK(file_.IsValid());
  GenericFileHelper* helper =
      new GenericFileHelper(weak_ptr_factory_.GetWeakPtr(), std::move(file_));
  return task_runner_->PostTaskAndReply(
      FROM_HERE,
      BindOnce(&GenericFileHelper::SetTimes, Unretained(helper),
               last_access_time, last_modified_time),
      BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
}

bool FileProxy::SetLength(int64_t length, StatusCallback callback) {
  DCHECK(file_.IsValid());
  GenericFileHelper* helper =
      new GenericFileHelper(weak_ptr_factory_.GetWeakPtr(), std::move(file_));
  return task_runner_->PostTaskAndReply(
      FROM_HERE,
      BindOnce(&GenericFileHelper::SetLength, Unretained(helper), length),
      BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
}

bool FileProxy::Flush(StatusCallback callback) {
  DCHECK(file_.IsValid());
  GenericFileHelper* helper =
      new GenericFileHelper(weak_ptr_factory_.GetWeakPtr(), std::move(file_));
  return task_runner_->PostTaskAndReply(
      FROM_HERE, BindOnce(&GenericFileHelper::Flush, Unretained(helper)),
      BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
}

}  // namespace base
