// 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/browser/printing/pdf_to_emf_converter.h"

#include <windows.h>

#include <stdint.h>

#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/containers/queue.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/printing/printing_service.h"
#include "chrome/services/printing/public/mojom/pdf_to_emf_converter.mojom.h"
#include "chrome/services/printing/public/mojom/printing_service.mojom.h"
#include "components/device_event_log/device_event_log.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "printing/emf_win.h"
#include "printing/pdf_render_settings.h"
#include "url/gurl.h"

using content::BrowserThread;

namespace printing {

namespace {

// Class for converting PDF to another format for printing (Emf, Postscript).
// Class lives on the UI thread.
// Internal workflow is following:
// 1. Create instance on the UI thread.
// 2. Initialize() binds to printing service and start conversion on the UI
//    thread (Mojo actually makes that happen transparently on the IO thread).
// 3. Printing service returns page count.
// 4. For each page:
//   1. Clients requests page.
//   2. Utility converts the page, and sends back the data in a memory region.
class PdfConverterImpl : public PdfConverter {
 public:
  PdfConverterImpl(scoped_refptr<base::RefCountedMemory> data,
                   const PdfRenderSettings& conversion_settings,
                   const std::optional<bool>& use_skia,
                   const GURL& url,
                   StartCallback start_callback);

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

  ~PdfConverterImpl() override;

  static void set_fail_when_initializing_conversion_for_tests(bool fail) {
    simulate_failure_initializing_conversion_ = fail;
  }

  static bool fail_when_initializing_conversion_for_tests() {
    return simulate_failure_initializing_conversion_;
  }

 private:
  class GetPageCallbackData {
   public:
    GetPageCallbackData(uint32_t page_index,
                        PdfConverter::GetPageCallback callback)
        : page_index_(page_index), callback_(callback) {}

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

    GetPageCallbackData(GetPageCallbackData&& other) {
      *this = std::move(other);
    }

    GetPageCallbackData& operator=(GetPageCallbackData&& rhs) {
      page_index_ = rhs.page_index_;
      callback_ = rhs.callback_;
      return *this;
    }

    uint32_t page_index() const { return page_index_; }

    PdfConverter::GetPageCallback callback() const { return callback_; }

   private:
    uint32_t page_index_;

    PdfConverter::GetPageCallback callback_;
  };

  void Initialize(scoped_refptr<base::RefCountedMemory> data);

  void GetPage(uint32_t page_index,
               PdfConverter::GetPageCallback get_page_callback) override;

  void Stop();

  std::unique_ptr<MetafilePlayer> GetMetaFileFromMapping(
      base::ReadOnlySharedMemoryMapping mapping);

  void OnPageCount(mojo::PendingRemote<mojom::PdfToEmfConverter> converter,
                   uint32_t page_count);
  void OnPageDone(base::ReadOnlySharedMemoryRegion emf_region,
                  float scale_factor);

  void OnFailed(std::string_view error_message);

  const PdfRenderSettings settings_;

  std::optional<bool> use_skia_;

  const GURL url_;

  // Document loaded callback.
  PdfConverter::StartCallback start_callback_;

  // Queue of callbacks for GetPage() requests. Utility process should reply
  // with PageDone in the same order as requests were received.
  // Use containers that keeps element pointers valid after push() and pop().
  using GetPageCallbacks = base::queue<GetPageCallbackData>;
  GetPageCallbacks get_page_callbacks_;

  mojo::Remote<mojom::PdfToEmfConverter> pdf_to_emf_converter_;

  mojo::Remote<mojom::PdfToEmfConverterFactory> pdf_to_emf_converter_factory_;

  base::WeakPtrFactory<PdfConverterImpl> weak_ptr_factory_{this};

  static bool simulate_failure_initializing_conversion_;
};

// static
bool PdfConverterImpl::simulate_failure_initializing_conversion_ = false;

std::unique_ptr<MetafilePlayer> PdfConverterImpl::GetMetaFileFromMapping(
    base::ReadOnlySharedMemoryMapping mapping) {
  std::unique_ptr<Emf> metafile;
  if (settings_.mode == PdfRenderSettings::Mode::POSTSCRIPT_LEVEL2 ||
      settings_.mode == PdfRenderSettings::Mode::POSTSCRIPT_LEVEL3 ||
      settings_.mode ==
          PdfRenderSettings::Mode::POSTSCRIPT_LEVEL3_WITH_TYPE42_FONTS ||
      settings_.mode == PdfRenderSettings::Mode::TEXTONLY) {
    metafile = std::make_unique<PostScriptMetaFile>();
  } else {
    metafile = std::make_unique<Emf>();
  }
  if (!metafile->InitFromData(mapping.GetMemoryAsSpan<const uint8_t>()))
    metafile.reset();
  return metafile;
}

PdfConverterImpl::PdfConverterImpl(scoped_refptr<base::RefCountedMemory> data,
                                   const PdfRenderSettings& settings,
                                   const std::optional<bool>& use_skia,
                                   const GURL& url,
                                   StartCallback start_callback)
    : settings_(settings),
      use_skia_(use_skia),
      url_(url),
      start_callback_(std::move(start_callback)) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(start_callback_);

  Initialize(data);
}

PdfConverterImpl::~PdfConverterImpl() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
}

void PdfConverterImpl::Initialize(scoped_refptr<base::RefCountedMemory> data) {
  if (simulate_failure_initializing_conversion_) {
    OnFailed("Failed to create PDF data mapping.");
    return;
  }

  base::MappedReadOnlyRegion memory =
      base::ReadOnlySharedMemoryRegion::Create(data->size());
  if (!memory.IsValid()) {
    OnFailed("Failed to create PDF data mapping.");
    return;
  }

  PRINTER_LOG(EVENT) << "PdfConverter created. Mode: " << settings_.mode;
  memory.mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(*data);

  GetPrintingService()->BindPdfToEmfConverterFactory(
      pdf_to_emf_converter_factory_.BindNewPipeAndPassReceiver());
  pdf_to_emf_converter_factory_.set_disconnect_handler(base::BindOnce(
      &PdfConverterImpl::OnFailed, weak_ptr_factory_.GetWeakPtr(),
      "Connection to PdfToEmfConverterFactory error."));

  pdf_to_emf_converter_factory_->CreateConverter(
      std::move(memory.region), settings_,
      base::BindOnce(&PdfConverterImpl::OnPageCount,
                     weak_ptr_factory_.GetWeakPtr()));
}

void PdfConverterImpl::OnPageCount(
    mojo::PendingRemote<mojom::PdfToEmfConverter> converter,
    uint32_t page_count) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!pdf_to_emf_converter_.is_bound());
  pdf_to_emf_converter_.Bind(std::move(converter));
  pdf_to_emf_converter_.set_disconnect_handler(base::BindOnce(
      &PdfConverterImpl::OnFailed, weak_ptr_factory_.GetWeakPtr(),
      "Connection to PdfToEmfConverter error."));
  pdf_to_emf_converter_->SetWebContentsURL(url_);
  if (use_skia_) {
    pdf_to_emf_converter_->SetUseSkiaRendererPolicy(*use_skia_);
  }
  std::move(start_callback_).Run(page_count);
}

void PdfConverterImpl::GetPage(
    uint32_t page_index,
    PdfConverter::GetPageCallback get_page_callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(pdf_to_emf_converter_.is_bound());

  // Store callback before any OnFailed() call to make it called on failure.
  get_page_callbacks_.push(GetPageCallbackData(page_index, get_page_callback));

  if (!pdf_to_emf_converter_)
    return OnFailed("No PdfToEmfConverter.");

  pdf_to_emf_converter_->ConvertPage(
      page_index, base::BindOnce(&PdfConverterImpl::OnPageDone,
                                 weak_ptr_factory_.GetWeakPtr()));
}

void PdfConverterImpl::OnPageDone(base::ReadOnlySharedMemoryRegion emf_region,
                                  float scale_factor) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (get_page_callbacks_.empty())
    return OnFailed("No get_page callbacks.");

  GetPageCallbackData& data = get_page_callbacks_.front();
  std::unique_ptr<MetafilePlayer> metafile;
  if (emf_region.IsValid()) {
    base::ReadOnlySharedMemoryMapping mapping = emf_region.Map();
    if (mapping.IsValid()) {
      metafile = GetMetaFileFromMapping(std::move(mapping));
    }
  }

  base::WeakPtr<PdfConverterImpl> weak_this = weak_ptr_factory_.GetWeakPtr();
  data.callback().Run(data.page_index(), scale_factor, std::move(metafile));
  // WARNING: the callback might have deleted `this`!
  if (!weak_this)
    return;
  get_page_callbacks_.pop();
}

void PdfConverterImpl::Stop() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // Disconnect interface ptrs so that the printing service process stop.
  pdf_to_emf_converter_factory_.reset();
  pdf_to_emf_converter_.reset();
}

void PdfConverterImpl::OnFailed(std::string_view error_message) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  LOG(ERROR) << "Failed to convert PDF: " << error_message;
  base::WeakPtr<PdfConverterImpl> weak_this = weak_ptr_factory_.GetWeakPtr();
  if (start_callback_) {
    std::move(start_callback_).Run(/*page_count=*/0);
    if (!weak_this)
      return;  // Protect against the `start_callback_` deleting `this`.
  }

  while (!get_page_callbacks_.empty()) {
    OnPageDone(base::ReadOnlySharedMemoryRegion(), 0.0f);
    if (!weak_this) {
      // OnPageDone invokes the GetPageCallback which might end up deleting
      // this.
      return;
    }
  }

  Stop();
}

}  // namespace

PdfConverter::~PdfConverter() = default;

// static
std::unique_ptr<PdfConverter> PdfConverter::StartPdfConverter(
    scoped_refptr<base::RefCountedMemory> data,
    const PdfRenderSettings& conversion_settings,
    const std::optional<bool>& use_skia,
    const GURL& url,
    StartCallback start_callback) {
  return std::make_unique<PdfConverterImpl>(data, conversion_settings, use_skia,
                                            url, std::move(start_callback));
}

ScopedSimulateFailureCreatingTempFileForTests::
    ScopedSimulateFailureCreatingTempFileForTests() {
  PdfConverterImpl::set_fail_when_initializing_conversion_for_tests(true);
}

ScopedSimulateFailureCreatingTempFileForTests::
    ~ScopedSimulateFailureCreatingTempFileForTests() {
  PdfConverterImpl::set_fail_when_initializing_conversion_for_tests(false);
}

}  // namespace printing
