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

#include "components/paint_preview/browser/paint_preview_file_mixin.h"

#include <string_view>
#include <utility>

#include "base/files/file_util.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/common/trace_event_common.h"
#include "base/trace_event/trace_event.h"
#include "ui/accessibility/mojom/ax_tree_update.mojom.h"

namespace paint_preview {

namespace {

const char kPaintPreviewDir[] = "paint_preview";
const char kAxTreeFilename[] = "ax_tree.message";

// Threadpool task to read the proto from disk.
std::pair<PaintPreviewFileMixin::ProtoReadStatus,
          std::unique_ptr<PaintPreviewProto>>
GetProto(scoped_refptr<FileManager> file_manager,
         const DirectoryKey& key,
         std::optional<base::TimeDelta> expiry_horizon) {
  TRACE_EVENT0("paint_preview", "PaintPreviewFileMixin::GetProto");
  if (expiry_horizon.has_value()) {
    auto file_info = file_manager->GetInfo(key);
    if (!file_info.has_value())
      return std::make_pair(PaintPreviewFileMixin::ProtoReadStatus::kNoProto,
                            nullptr);

    if (file_info->last_modified + expiry_horizon.value() <
        base::Time::NowFromSystemTime()) {
      return std::make_pair(PaintPreviewFileMixin::ProtoReadStatus::kExpired,
                            nullptr);
    }
  }
  auto result = file_manager->DeserializePaintPreviewProto(key);
  PaintPreviewFileMixin::ProtoReadStatus status =
      PaintPreviewFileMixin::ProtoReadStatus::kNoProto;
  switch (result.first) {
    case FileManager::ProtoReadStatus::kOk:
      status = PaintPreviewFileMixin::ProtoReadStatus::kOk;
      break;
    case FileManager::ProtoReadStatus::kNoProto:
      status = PaintPreviewFileMixin::ProtoReadStatus::kNoProto;
      break;
    case FileManager::ProtoReadStatus::kDeserializationError:
      status = PaintPreviewFileMixin::ProtoReadStatus::kDeserializationError;
      break;
    default:
      NOTREACHED();
  }
  return std::make_pair(status, std::move(result.second));
}

// Callback executed on threadpool read complete.
void OnReadProto(PaintPreviewFileMixin::OnReadProtoCallback callback,
                 std::pair<PaintPreviewFileMixin::ProtoReadStatus,
                           std::unique_ptr<PaintPreviewProto>> result) {
  TRACE_EVENT0("paint_preview", "PaintPreviewFileMixin::OnReadProto");
  std::move(callback).Run(result.first, std::move(result.second));
}

}  // namespace

PaintPreviewFileMixin::PaintPreviewFileMixin(
    const base::FilePath& path,
    std::string_view ascii_feature_name)
    : task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
           base::TaskShutdownBehavior::BLOCK_SHUTDOWN,
           base::ThreadPolicy::MUST_USE_FOREGROUND})),
      file_manager_(base::MakeRefCounted<FileManager>(
          path.AppendASCII(kPaintPreviewDir).AppendASCII(ascii_feature_name),
          task_runner_)) {}

PaintPreviewFileMixin::~PaintPreviewFileMixin() = default;

void PaintPreviewFileMixin::GetCapturedPaintPreviewProto(
    const DirectoryKey& key,
    std::optional<base::TimeDelta> expiry_horizon,
    OnReadProtoCallback on_read_proto_callback) {
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&GetProto, file_manager_, key, expiry_horizon),
      base::BindOnce(OnReadProto, std::move(on_read_proto_callback)));
}

void PaintPreviewFileMixin::WriteAXTreeUpdate(
    const DirectoryKey& key,
    base::OnceCallback<void(bool)> finished_callback,
    ui::AXTreeUpdate& ax_tree_update) {
  std::vector<uint8_t> ax_data =
      ax::mojom::AXTreeUpdate::Serialize(&ax_tree_update);
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](scoped_refptr<FileManager> file_manager, const DirectoryKey& key,
             const std::vector<uint8_t>& ax_data) {
            TRACE_EVENT0("paint_preview",
                         "PaintPreviewFileMixin::WriteAXTreeUpdate Task");
            auto directory = file_manager->CreateOrGetDirectory(key, false);
            if (!directory.has_value()) {
              return false;
            }
            return base::WriteFile(directory->AppendASCII(kAxTreeFilename),
                                   ax_data);
          },
          file_manager_, key, ax_data),
      std::move(finished_callback));
}

void PaintPreviewFileMixin::GetAXTreeUpdate(const DirectoryKey& key,
                                            OnReadAXTree callback) {
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](scoped_refptr<FileManager> file_manager,
             const DirectoryKey& key) -> std::unique_ptr<ui::AXTreeUpdate> {
            TRACE_EVENT0("paint_preview",
                         "PaintPreviewFileMixin::GetAXTreeUpdate Task");
            auto dir = file_manager->CreateOrGetDirectory(key, false);
            if (!dir.has_value()) {
              return nullptr;
            }

            auto path = dir->AppendASCII(kAxTreeFilename);
            std::string content;
            if (!base::ReadFileToString(path, &content)) {
              return nullptr;
            }

            auto update = std::make_unique<ui::AXTreeUpdate>();
            if (!ax::mojom::AXTreeUpdate::Deserialize(
                    content.data(), content.size(), update.get())) {
              return nullptr;
            }
            return update;
          },
          file_manager_, key),
      std::move(callback));
}

}  // namespace paint_preview
