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

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "public/fpdf_ppo.h"

#include <memory>
#include <numeric>
#include <utility>
#include <vector>

#include "constants/catalog.h"
#include "core/fpdfapi/edit/cpdf_npagetooneexporter.h"
#include "core/fpdfapi/edit/cpdf_pageexporter.h"
#include "core/fpdfapi/page/cpdf_form.h"
#include "core/fpdfapi/page/cpdf_formobject.h"
#include "core/fpdfapi/page/cpdf_page.h"
#include "core/fpdfapi/page/cpdf_pageimagecache.h"
#include "core/fpdfapi/page/cpdf_pageobject.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fpdfapi/parser/cpdf_object.h"
#include "core/fpdfapi/parser/fpdf_parser_utility.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/retain_ptr.h"
#include "core/fxcrt/span.h"
#include "fpdfsdk/cpdfsdk_helpers.h"
#include "public/cpp/fpdf_scopers.h"

namespace {

std::vector<uint32_t> GetPageIndices(const CPDF_Document& doc,
                                     const ByteString& page_range) {
  uint32_t count = doc.GetPageCount();
  if (!page_range.IsEmpty()) {
    return ParsePageRangeString(page_range, count);
  }

  std::vector<uint32_t> page_indices(count);
  std::iota(page_indices.begin(), page_indices.end(), 0);
  return page_indices;
}

// Make sure arrays only contain objects of basic types.
bool IsValidViewerPreferencesArray(const CPDF_Array* array) {
  CPDF_ArrayLocker locker(array);
  for (const auto& obj : locker) {
    if (obj->IsArray() || obj->IsDictionary() || obj->IsReference() ||
        obj->IsStream()) {
      return false;
    }
  }
  return true;
}

bool IsValidViewerPreferencesObject(const CPDF_Object* obj) {
  // Per spec, there are no valid entries of these types.
  if (obj->IsDictionary() || obj->IsNull() || obj->IsReference() ||
      obj->IsStream()) {
    return false;
  }

  const CPDF_Array* array = obj->AsArray();
  if (!array) {
    return true;
  }

  return IsValidViewerPreferencesArray(array);
}

}  // namespace

FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDF_ImportPagesByIndex(FPDF_DOCUMENT dest_doc,
                        FPDF_DOCUMENT src_doc,
                        const int* page_indices,
                        unsigned long length,
                        int index) {
  CPDF_Document* cdest_doc = CPDFDocumentFromFPDFDocument(dest_doc);
  if (!cdest_doc) {
    return false;
  }

  CPDF_Document* csrc_doc = CPDFDocumentFromFPDFDocument(src_doc);
  if (!csrc_doc) {
    return false;
  }

  size_t length_size_t = pdfium::checked_cast<size_t>(length);
  CPDF_Document::Extension* extension = cdest_doc->GetExtension();

  CPDF_PageExporter exporter(cdest_doc, csrc_doc);

  if (!page_indices) {
    std::vector<uint32_t> page_indices_vec(csrc_doc->GetPageCount());
    std::iota(page_indices_vec.begin(), page_indices_vec.end(), 0);
    if (!exporter.ExportPages(page_indices_vec, index)) {
      return false;
    }
    if (extension) {
      extension->PagesInserted(index, length_size_t);
    }
    return true;
  }
  if (length_size_t == 0) {
    return false;
  }

  // SAFETY: required from caller.
  auto page_span = UNSAFE_BUFFERS(pdfium::span(page_indices, length_size_t));
  for (int page_index : page_span) {
    if (page_index < 0) {
      return false;
    }
  }
  if (!exporter.ExportPages(fxcrt::reinterpret_span<const uint32_t>(page_span),
                            index)) {
    return false;
  }
  if (extension) {
    extension->PagesInserted(index, length_size_t);
  }
  return true;
}

FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc,
                                                     FPDF_DOCUMENT src_doc,
                                                     FPDF_BYTESTRING pagerange,
                                                     int index) {
  CPDF_Document* cdest_doc = CPDFDocumentFromFPDFDocument(dest_doc);
  if (!cdest_doc) {
    return false;
  }

  CPDF_Document* csrc_doc = CPDFDocumentFromFPDFDocument(src_doc);
  if (!csrc_doc) {
    return false;
  }

  std::vector<uint32_t> page_indices = GetPageIndices(*csrc_doc, pagerange);
  if (page_indices.empty()) {
    return false;
  }

  CPDF_PageExporter exporter(cdest_doc, csrc_doc);
  if (!exporter.ExportPages(page_indices, index)) {
    return false;
  }

  CPDF_Document::Extension* extension = cdest_doc->GetExtension();
  if (extension) {
    extension->PagesInserted(index, page_indices.size());
  }

  return true;
}

FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV
FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc,
                       float output_width,
                       float output_height,
                       size_t pages_on_x_axis,
                       size_t pages_on_y_axis) {
  CPDF_Document* csrc_doc = CPDFDocumentFromFPDFDocument(src_doc);
  if (!csrc_doc) {
    return nullptr;
  }

  if (output_width <= 0 || output_height <= 0 || pages_on_x_axis <= 0 ||
      pages_on_y_axis <= 0) {
    return nullptr;
  }

  ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
  if (!output_doc) {
    return nullptr;
  }

  CPDF_Document* dest_doc = CPDFDocumentFromFPDFDocument(output_doc.get());
  DCHECK(dest_doc);

  std::vector<uint32_t> page_indices = GetPageIndices(*csrc_doc, ByteString());
  if (page_indices.empty()) {
    return nullptr;
  }

  if (pages_on_x_axis == 1 && pages_on_y_axis == 1) {
    CPDF_PageExporter exporter(dest_doc, csrc_doc);
    if (!exporter.ExportPages(page_indices, 0)) {
      return nullptr;
    }
    return output_doc.release();
  }

  CPDF_NPageToOneExporter exporter(dest_doc, csrc_doc);
  if (!exporter.ExportNPagesToOne(page_indices,
                                  CFX_SizeF(output_width, output_height),
                                  pages_on_x_axis, pages_on_y_axis)) {
    return nullptr;
  }
  return output_doc.release();
}

FPDF_EXPORT FPDF_XOBJECT FPDF_CALLCONV
FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc,
                        FPDF_DOCUMENT src_doc,
                        int src_page_index) {
  CPDF_Document* dest = CPDFDocumentFromFPDFDocument(dest_doc);
  if (!dest) {
    return nullptr;
  }

  CPDF_Document* src = CPDFDocumentFromFPDFDocument(src_doc);
  if (!src) {
    return nullptr;
  }

  CPDF_NPageToOneExporter exporter(dest, src);
  std::unique_ptr<XObjectContext> xobject =
      exporter.CreateXObjectContextFromPage(src_page_index);
  return FPDFXObjectFromXObjectContext(xobject.release());
}

FPDF_EXPORT void FPDF_CALLCONV FPDF_CloseXObject(FPDF_XOBJECT xobject) {
  std::unique_ptr<XObjectContext> xobject_deleter(
      XObjectContextFromFPDFXObject(xobject));
}

FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV
FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject) {
  XObjectContext* xobj = XObjectContextFromFPDFXObject(xobject);
  if (!xobj) {
    return nullptr;
  }

  auto form = std::make_unique<CPDF_Form>(xobj->dest_doc, nullptr,
                                          xobj->xobject, nullptr);
  form->ParseContent(nullptr, nullptr, nullptr);
  auto form_object = std::make_unique<CPDF_FormObject>(
      CPDF_PageObject::kNoContentStream, std::move(form), CFX_Matrix());
  return FPDFPageObjectFromCPDFPageObject(form_object.release());
}

FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc) {
  CPDF_Document* cdest_doc = CPDFDocumentFromFPDFDocument(dest_doc);
  if (!cdest_doc) {
    return false;
  }

  CPDF_Document* csrc_doc = CPDFDocumentFromFPDFDocument(src_doc);
  if (!csrc_doc) {
    return false;
  }

  RetainPtr<const CPDF_Dictionary> pref_dict =
      csrc_doc->GetRoot()->GetDictFor(pdfium::catalog::kViewerPreferences);
  if (!pref_dict) {
    return false;
  }

  RetainPtr<CPDF_Dictionary> dest_dict = cdest_doc->GetMutableRoot();
  if (!dest_dict) {
    return false;
  }

  auto cloned_dict = cdest_doc->New<CPDF_Dictionary>();
  CPDF_DictionaryLocker locker(pref_dict);
  for (const auto& it : locker) {
    if (IsValidViewerPreferencesObject(it.second)) {
      cloned_dict->SetFor(it.first, it.second->Clone());
    }
  }

  dest_dict->SetFor(pdfium::catalog::kViewerPreferences,
                    std::move(cloned_dict));
  return true;
}
