// 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 "chrome/utility/image_writer/image_writer_handler.h"

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

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "chrome/services/removable_storage_writer/public/mojom/removable_storage_writer.mojom.h"
#include "chrome/utility/image_writer/error_message_strings.h"

namespace {

bool IsTestDevice(const base::FilePath& device) {
  return device.AsUTF8Unsafe() ==
         chrome::mojom::RemovableStorageWriter::kTestDevice;
}

base::FilePath MakeTestDevicePath(const base::FilePath& image) {
  return image.ReplaceExtension(FILE_PATH_LITERAL("out"));
}

}  // namespace

namespace image_writer {

ImageWriterHandler::ImageWriterHandler() = default;

ImageWriterHandler::~ImageWriterHandler() = default;

void ImageWriterHandler::Write(
    const base::FilePath& image,
    const base::FilePath& device,
    mojo::PendingRemote<chrome::mojom::RemovableStorageWriterClient> client) {
  client_.Bind(std::move(client));
  client_.set_disconnect_handler(
      base::BindOnce(&ImageWriterHandler::Cancel, base::Unretained(this)));

  base::FilePath target_device = device;
  const bool test_mode = IsTestDevice(device);
  if (test_mode)
    target_device = MakeTestDevicePath(image);

  if (ShouldResetImageWriter(image, target_device))
    image_writer_ = std::make_unique<ImageWriter>(this, image, target_device);

  if (image_writer_->IsRunning()) {
    SendFailed(error::kOperationAlreadyInProgress);
    return;
  }

  if (test_mode) {
    image_writer_->Write();
    return;
  }

  if (!image_writer_->IsValidDevice()) {
    SendFailed(error::kInvalidDevice);
    return;
  }

  image_writer_->UnmountVolumes(
      base::BindOnce(&ImageWriter::Write, image_writer_->AsWeakPtr()));
}

void ImageWriterHandler::Verify(
    const base::FilePath& image,
    const base::FilePath& device,
    mojo::PendingRemote<chrome::mojom::RemovableStorageWriterClient> client) {
  client_.Bind(std::move(client));
  client_.set_disconnect_handler(
      base::BindOnce(&ImageWriterHandler::Cancel, base::Unretained(this)));

  base::FilePath target_device = device;
  const bool test_mode = IsTestDevice(device);
  if (test_mode)
    target_device = MakeTestDevicePath(image);

  if (ShouldResetImageWriter(image, target_device))
    image_writer_ = std::make_unique<ImageWriter>(this, image, target_device);

  if (image_writer_->IsRunning()) {
    SendFailed(error::kOperationAlreadyInProgress);
    return;
  }

  if (test_mode) {
    image_writer_->Verify();
    return;
  }

  if (!image_writer_->IsValidDevice()) {
    SendFailed(error::kInvalidDevice);
    return;
  }

  image_writer_->Verify();
}

void ImageWriterHandler::SendProgress(int64_t progress) {
  client_->Progress(progress);
}

void ImageWriterHandler::SendSucceeded() {
  client_->Complete(std::nullopt);
  client_.reset();
}

void ImageWriterHandler::SendFailed(const std::string& error) {
  if (client_) {
    // client_ may be null as the ImageWriter implementation may have reported
    // an error already.
    client_->Complete(error);
    client_.reset();
  }
}

void ImageWriterHandler::Cancel() {
  if (image_writer_)
    image_writer_->Cancel();
  client_.reset();
}

bool ImageWriterHandler::ShouldResetImageWriter(const base::FilePath& image,
                                                const base::FilePath& device) {
  if (!image_writer_)
    return true;
  if (image != image_writer_->GetImagePath())
    return true;
  if (device != image_writer_->GetDevicePath())
    return true;

  // When writing and verifying the same file on the same device, keep
  // the file handles open; do not reset them since that can cause the
  // operation to fail in unexpected ways: crbug.com/40355836#comment8
  return false;
}

}  // namespace image_writer
