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

#include "testing/helpers/dump.h"

#include <limits.h>
#include <stdio.h>

#include <algorithm>
#include <functional>
#include <iterator>
#include <string>
#include <utility>

#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_memcpy_wrappers.h"
#include "public/cpp/fpdf_scopers.h"
#include "public/fpdf_doc.h"
#include "public/fpdf_transformpage.h"
#include "testing/fx_string_testhelpers.h"

using GetBoxInfoFunc =
    std::function<bool(FPDF_PAGE, float*, float*, float*, float*)>;

namespace {

std::wstring ConvertToWString(const unsigned short* buf,
                              unsigned long buf_size) {
  std::wstring result;
  result.reserve(buf_size);
  std::copy(buf, UNSAFE_TODO(buf + buf_size), std::back_inserter(result));
  return result;
}

void DumpBoxInfo(GetBoxInfoFunc func,
                 const char* box_type,
                 FPDF_PAGE page,
                 int page_idx) {
  FS_RECTF rect;
  bool ret = func(page, &rect.left, &rect.bottom, &rect.right, &rect.top);
  if (!ret) {
    UNSAFE_TODO(printf("Page %d: No %s.\n", page_idx, box_type));
    return;
  }
  UNSAFE_TODO(printf("Page %d: %s: %0.2f %0.2f %0.2f %0.2f\n", page_idx,
                     box_type, rect.left, rect.bottom, rect.right, rect.top));
}

void DumpStructureElementAttributeValues(
    FPDF_STRUCTELEMENT_ATTR_VALUE attr_value,
    const char* name,
    int indent) {
  if (!attr_value) {
    UNSAFE_TODO(printf("%*s FPDF_StructElement_Attr_GetValue failed for %s\n",
                       indent, "", name));
    return;
  }

  FPDF_OBJECT_TYPE type = FPDF_StructElement_Attr_GetType(attr_value);
  switch (type) {
    case FPDF_OBJECT_BOOLEAN: {
      int value;
      if (FPDF_StructElement_Attr_GetBooleanValue(attr_value, &value)) {
        UNSAFE_TODO(printf("%*s %s: %d\n", indent, "", name, value));
      } else {
        UNSAFE_TODO(
            printf("%*s %s: Failed FPDF_StructElement_Attr_GetBooleanValue\n",
                   indent, "", name));
      }
      break;
    }
    case FPDF_OBJECT_NUMBER: {
      float value;
      if (FPDF_StructElement_Attr_GetNumberValue(attr_value, &value)) {
        UNSAFE_TODO(printf("%*s %s: %f\n", indent, "", name, value));
      } else {
        UNSAFE_TODO(
            printf("%*s %s: Failed FPDF_StructElement_Attr_GetNumberValue\n",
                   indent, "", name));
      }
      break;
    }
    case FPDF_OBJECT_STRING:
    case FPDF_OBJECT_NAME: {
      static const size_t kBufSize = 1024;
      unsigned short buffer[kBufSize];
      unsigned long len;
      if (FPDF_StructElement_Attr_GetStringValue(attr_value, buffer,
                                                 sizeof(buffer), &len)) {
        UNSAFE_TODO(printf("%*s %s: %ls\n", indent, "", name,
                           ConvertToWString(buffer, len).c_str()));
      } else {
        UNSAFE_TODO(
            printf("%*s %s: Failed FPDF_StructElement_Attr_GetStringValue\n",
                   indent, "", name));
      }
      break;
    }
    case FPDF_OBJECT_ARRAY: {
      UNSAFE_TODO(printf("%*s %s:\n", indent, "", name));
      int count = FPDF_StructElement_Attr_CountChildren(attr_value);
      for (int i = 0; i < count; ++i) {
        DumpStructureElementAttributeValues(
            FPDF_StructElement_Attr_GetChildAtIndex(attr_value, i), name,
            indent + 2);
      }
      break;
    }
    case FPDF_OBJECT_UNKNOWN: {
      UNSAFE_TODO(printf("%*s %s: FPDF_OBJECT_UNKNOWN\n", indent, "", name));
      break;
    }
    default: {
      UNSAFE_TODO(
          printf("%*s %s: NOT_YET_IMPLEMENTED: %d\n", indent, "", name, type));
      break;
    }
  }
}

void DumpStructureElementAttributes(FPDF_STRUCTELEMENT_ATTR attr, int indent) {
  static const size_t kBufSize = 1024;
  int count = FPDF_StructElement_Attr_GetCount(attr);
  for (int i = 0; i < count; ++i) {
    char name[kBufSize];
    unsigned long len;
    if (!FPDF_StructElement_Attr_GetName(attr, i, name, sizeof(name), &len)) {
      printf("%*s FPDF_StructElement_Attr_GetName failed for %d\n", indent, "",
             i);
      continue;
    }

    DumpStructureElementAttributeValues(
        FPDF_StructElement_Attr_GetValue(attr, name), name, indent);
  }
}

}  // namespace

void DumpChildStructure(FPDF_STRUCTELEMENT child, int indent) {
  static const size_t kBufSize = 1024;
  unsigned short buf[kBufSize];
  unsigned long len = FPDF_StructElement_GetType(child, buf, kBufSize);
  if (len > 0) {
    printf("%*s S: %ls\n", indent * 2, "", ConvertToWString(buf, len).c_str());
  }

  int attr_count = FPDF_StructElement_GetAttributeCount(child);
  for (int i = 0; i < attr_count; i++) {
    FPDF_STRUCTELEMENT_ATTR child_attr =
        FPDF_StructElement_GetAttributeAtIndex(child, i);
    if (!child_attr) {
      continue;
    }
    printf("%*s A[%d]:\n", indent * 2, "", i);
    DumpStructureElementAttributes(child_attr, indent * 2 + 2);
  }

  UNSAFE_TODO(FXSYS_memset(buf, 0, sizeof(buf)));
  len = FPDF_StructElement_GetActualText(child, buf, kBufSize);
  if (len > 0) {
    printf("%*s ActualText: %ls\n", indent * 2, "",
           ConvertToWString(buf, len).c_str());
  }

  UNSAFE_TODO(FXSYS_memset(buf, 0, sizeof(buf)));
  len = FPDF_StructElement_GetAltText(child, buf, kBufSize);
  if (len > 0) {
    printf("%*s AltText: %ls\n", indent * 2, "",
           ConvertToWString(buf, len).c_str());
  }

  UNSAFE_TODO(FXSYS_memset(buf, 0, sizeof(buf)));
  len = FPDF_StructElement_GetID(child, buf, kBufSize);
  if (len > 0) {
    printf("%*s ID: %ls\n", indent * 2, "", ConvertToWString(buf, len).c_str());
  }

  UNSAFE_TODO(FXSYS_memset(buf, 0, sizeof(buf)));
  len = FPDF_StructElement_GetLang(child, buf, kBufSize);
  if (len > 0) {
    printf("%*s Lang: %ls\n", indent * 2, "",
           ConvertToWString(buf, len).c_str());
  }

  const int mcid_count = FPDF_StructElement_GetMarkedContentIdCount(child);
  for (int i = 0; i < mcid_count; ++i) {
    int mcid = FPDF_StructElement_GetMarkedContentIdAtIndex(child, i);
    if (mcid != -1) {
      printf("%*s MCID%d: %d\n", indent * 2, "", i, mcid);
    }
  }

  FPDF_STRUCTELEMENT parent = FPDF_StructElement_GetParent(child);
  if (parent) {
    UNSAFE_TODO(FXSYS_memset(buf, 0, sizeof(buf)));
    len = FPDF_StructElement_GetID(parent, buf, kBufSize);
    if (len > 0) {
      printf("%*s Parent ID: %ls\n", indent * 2, "",
             ConvertToWString(buf, len).c_str());
    }
  }

  UNSAFE_TODO(FXSYS_memset(buf, 0, sizeof(buf)));
  len = FPDF_StructElement_GetTitle(child, buf, kBufSize);
  if (len > 0) {
    printf("%*s Title: %ls\n", indent * 2, "",
           ConvertToWString(buf, len).c_str());
  }

  UNSAFE_TODO(FXSYS_memset(buf, 0, sizeof(buf)));
  len = FPDF_StructElement_GetObjType(child, buf, kBufSize);
  if (len > 0) {
    printf("%*s Type: %ls\n", indent * 2, "",
           ConvertToWString(buf, len).c_str());
  }

  for (int i = 0; i < FPDF_StructElement_CountChildren(child); ++i) {
    FPDF_STRUCTELEMENT sub_child = FPDF_StructElement_GetChildAtIndex(child, i);
    // If the child is not an Element then this will return null. This can
    // happen if the element is things like an object reference or a stream.
    if (!sub_child) {
      continue;
    }

    DumpChildStructure(sub_child, indent + 1);
  }
}

void DumpPageInfo(FPDF_PAGE page, int page_idx) {
  DumpBoxInfo(&FPDFPage_GetMediaBox, "MediaBox", page, page_idx);
  DumpBoxInfo(&FPDFPage_GetCropBox, "CropBox", page, page_idx);
  DumpBoxInfo(&FPDFPage_GetBleedBox, "BleedBox", page, page_idx);
  DumpBoxInfo(&FPDFPage_GetTrimBox, "TrimBox", page, page_idx);
  DumpBoxInfo(&FPDFPage_GetArtBox, "ArtBox", page, page_idx);
}

void DumpPageStructure(FPDF_PAGE page, int page_idx) {
  ScopedFPDFStructTree tree(FPDF_StructTree_GetForPage(page));
  if (!tree) {
    fprintf(stderr, "Failed to load struct tree for page %d\n", page_idx);
    return;
  }

  printf("Structure Tree for Page %d\n", page_idx);
  for (int i = 0; i < FPDF_StructTree_CountChildren(tree.get()); ++i) {
    FPDF_STRUCTELEMENT child = FPDF_StructTree_GetChildAtIndex(tree.get(), i);
    if (!child) {
      fprintf(stderr, "Failed to load child %d for page %d\n", i, page_idx);
      continue;
    }
    DumpChildStructure(child, 0);
  }
  printf("\n\n");
}

void DumpMetaData(FPDF_DOCUMENT doc) {
  static constexpr const char* kMetaTags[] = {
      "Title",   "Author",   "Subject",      "Keywords",
      "Creator", "Producer", "CreationDate", "ModDate"};
  for (const char* meta_tag : kMetaTags) {
    char meta_buffer[4096];
    unsigned long len =
        FPDF_GetMetaText(doc, meta_tag, meta_buffer, sizeof(meta_buffer));
    if (!len) {
      continue;
    }

    auto* meta_string = reinterpret_cast<unsigned short*>(meta_buffer);
    UNSAFE_TODO(printf("%-12s = %ls (%lu bytes)\n", meta_tag,
                       GetPlatformWString(meta_string).c_str(), len));
  }
}
