// 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 "core/fxge/cfx_cttgsubtable.h"

#include <stdint.h>

#include <utility>
#include <variant>

#include "core/fxcrt/byteorder.h"
#include "core/fxcrt/data_vector.h"
#include "core/fxcrt/stl_util.h"
#include "core/fxge/cfx_fontmapper.h"

namespace {

bool IsVerticalFeatureTag(uint32_t tag) {
  return tag == CFX_FontMapper::MakeTag('v', 'r', 't', '2') ||
         tag == CFX_FontMapper::MakeTag('v', 'e', 'r', 't');
}

}  // namespace

CFX_CTTGSUBTable::CFX_CTTGSUBTable(pdfium::span<const uint8_t> gsub) {
  if (!LoadGSUBTable(gsub)) {
    return;
  }

  for (const auto& script : script_list_) {
    for (const auto& record : script) {
      for (uint16_t index : record) {
        if (index < feature_list_.size() &&
            IsVerticalFeatureTag(feature_list_[index].feature_tag)) {
          feature_set_.insert(index);
        }
      }
    }
  }
  if (!feature_set_.empty()) {
    return;
  }

  int i = 0;
  for (const FeatureRecord& feature : feature_list_) {
    if (IsVerticalFeatureTag(feature.feature_tag)) {
      feature_set_.insert(i);
    }
    ++i;
  }
}

CFX_CTTGSUBTable::~CFX_CTTGSUBTable() = default;

bool CFX_CTTGSUBTable::LoadGSUBTable(pdfium::span<const uint8_t> gsub) {
  if (fxcrt::GetUInt32MSBFirst(gsub.first<4u>()) != 0x00010000) {
    return false;
  }

  auto scriptlist_span = gsub.subspan<4u, 2u>();
  auto featurelist_span = gsub.subspan<6u, 2u>();
  auto lookuplist_span = gsub.subspan<8u, 2u>();
  size_t scriptlist_index = fxcrt::GetUInt16MSBFirst(scriptlist_span);
  size_t featurelist_index = fxcrt::GetUInt16MSBFirst(featurelist_span);
  size_t lookuplist_index = fxcrt::GetUInt16MSBFirst(lookuplist_span);
  Parse(gsub.subspan(scriptlist_index), gsub.subspan(featurelist_index),
        gsub.subspan(lookuplist_index));
  return true;
}

uint32_t CFX_CTTGSUBTable::GetVerticalGlyph(uint32_t glyphnum) const {
  for (uint32_t item : feature_set_) {
    std::optional<uint32_t> result =
        GetVerticalGlyphSub(feature_list_[item], glyphnum);
    if (result.has_value()) {
      return result.value();
    }
  }
  return 0;
}

std::optional<uint32_t> CFX_CTTGSUBTable::GetVerticalGlyphSub(
    const FeatureRecord& feature,
    uint32_t glyphnum) const {
  for (int index : feature.lookup_list_indices) {
    if (!fxcrt::IndexInBounds(lookup_list_, index)) {
      continue;
    }
    if (lookup_list_[index].lookup_type != 1) {
      continue;
    }
    std::optional<uint32_t> result =
        GetVerticalGlyphSub2(lookup_list_[index], glyphnum);
    if (result.has_value()) {
      return result.value();
    }
  }
  return std::nullopt;
}

std::optional<uint32_t> CFX_CTTGSUBTable::GetVerticalGlyphSub2(
    const Lookup& lookup,
    uint32_t glyphnum) const {
  for (const auto& sub_table : lookup.sub_tables) {
    if (std::holds_alternative<std::monostate>(sub_table.table_data)) {
      continue;
    }
    int index = GetCoverageIndex(sub_table.coverage, glyphnum);
    if (std::holds_alternative<int16_t>(sub_table.table_data)) {
      if (index >= 0) {
        return glyphnum + std::get<int16_t>(sub_table.table_data);
      }
    } else {
      const auto& substitutes =
          std::get<DataVector<uint16_t>>(sub_table.table_data);
      if (fxcrt::IndexInBounds(substitutes, index)) {
        return substitutes[index];
      }
    }
  }
  return std::nullopt;
}

int CFX_CTTGSUBTable::GetCoverageIndex(const CoverageFormat& coverage,
                                       uint32_t g) const {
  if (std::holds_alternative<std::monostate>(coverage)) {
    return -1;
  }

  if (std::holds_alternative<DataVector<uint16_t>>(coverage)) {
    int i = 0;
    const auto& glyph_array = std::get<DataVector<uint16_t>>(coverage);
    for (const auto& glyph : glyph_array) {
      if (static_cast<uint32_t>(glyph) == g) {
        return i;
      }
      ++i;
    }
    return -1;
  }

  const auto& range_records = std::get<std::vector<RangeRecord>>(coverage);
  for (const auto& range_rec : range_records) {
    uint32_t s = range_rec.start;
    uint32_t e = range_rec.end;
    uint32_t si = range_rec.start_coverage_index;
    if (s <= g && g <= e) {
      return si + g - s;
    }
  }
  return -1;
}

uint8_t CFX_CTTGSUBTable::GetUInt8(pdfium::span<const uint8_t>& p) const {
  uint8_t ret = p.front();
  p = p.subspan<1u>();
  return ret;
}

int16_t CFX_CTTGSUBTable::GetInt16(pdfium::span<const uint8_t>& p) const {
  uint16_t ret = fxcrt::GetUInt16MSBFirst(p.first<2u>());
  p = p.subspan<2u>();
  return static_cast<int16_t>(ret);
}

uint16_t CFX_CTTGSUBTable::GetUInt16(pdfium::span<const uint8_t>& p) const {
  uint16_t ret = fxcrt::GetUInt16MSBFirst(p.first<2u>());
  p = p.subspan<2u>();
  return ret;
}

int32_t CFX_CTTGSUBTable::GetInt32(pdfium::span<const uint8_t>& p) const {
  uint32_t ret = fxcrt::GetUInt32MSBFirst(p.first<4u>());
  p = p.subspan<4u>();
  return static_cast<int32_t>(ret);
}

uint32_t CFX_CTTGSUBTable::GetUInt32(pdfium::span<const uint8_t>& p) const {
  uint32_t ret = fxcrt::GetUInt32MSBFirst(p.first<4u>());
  p = p.subspan<4u>();
  return ret;
}

void CFX_CTTGSUBTable::Parse(pdfium::span<const uint8_t> scriptlist,
                             pdfium::span<const uint8_t> featurelist,
                             pdfium::span<const uint8_t> lookuplist) {
  ParseScriptList(scriptlist);
  ParseFeatureList(featurelist);
  ParseLookupList(lookuplist);
}

void CFX_CTTGSUBTable::ParseScriptList(pdfium::span<const uint8_t> raw) {
  pdfium::span<const uint8_t> sp = raw;
  script_list_ = std::vector<ScriptRecord>(GetUInt16(sp));
  for (auto& script : script_list_) {
    // Skip over "ScriptTag" field.
    sp = sp.subspan(4u);
    script = ParseScript(raw.subspan(GetUInt16(sp)));
  }
}

CFX_CTTGSUBTable::ScriptRecord CFX_CTTGSUBTable::ParseScript(
    pdfium::span<const uint8_t> raw) {
  // Skip over "DefaultLangSys" field.
  pdfium::span<const uint8_t> sp = raw.subspan(2u);
  ScriptRecord result(GetUInt16(sp));
  for (auto& record : result) {
    // Skip over "LangSysTag" field.
    sp = sp.subspan(4u);
    record = ParseLangSys(raw.subspan(GetUInt16(sp)));
  }
  return result;
}

CFX_CTTGSUBTable::FeatureIndices CFX_CTTGSUBTable::ParseLangSys(
    pdfium::span<const uint8_t> raw) {
  // Skip over "LookupOrder" and "ReqFeatureIndex" fields.
  pdfium::span<const uint8_t> sp = raw.subspan(4u);
  FeatureIndices result(GetUInt16(sp));
  for (auto& element : result) {
    element = GetUInt16(sp);
  }
  return result;
}

void CFX_CTTGSUBTable::ParseFeatureList(pdfium::span<const uint8_t> raw) {
  pdfium::span<const uint8_t> sp = raw;
  feature_list_ = std::vector<FeatureRecord>(GetUInt16(sp));
  for (auto& record : feature_list_) {
    record.feature_tag = GetUInt32(sp);
    record.lookup_list_indices =
        ParseFeatureLookupListIndices(raw.subspan(GetUInt16(sp)));
  }
}

DataVector<uint16_t> CFX_CTTGSUBTable::ParseFeatureLookupListIndices(
    pdfium::span<const uint8_t> raw) {
  // Skip over "FeatureParams" field.
  pdfium::span<const uint8_t> sp = raw.subspan(2u);
  DataVector<uint16_t> result(GetUInt16(sp));
  for (auto& index : result) {
    index = GetUInt16(sp);
  }
  return result;
}

void CFX_CTTGSUBTable::ParseLookupList(pdfium::span<const uint8_t> raw) {
  pdfium::span<const uint8_t> sp = raw;
  lookup_list_ = std::vector<Lookup>(GetUInt16(sp));
  for (auto& lookup : lookup_list_) {
    lookup = ParseLookup(raw.subspan(GetUInt16(sp)));
  }
}

CFX_CTTGSUBTable::Lookup CFX_CTTGSUBTable::ParseLookup(
    pdfium::span<const uint8_t> raw) {
  pdfium::span<const uint8_t> sp = raw;
  CFX_CTTGSUBTable::Lookup result;
  result.lookup_type = GetUInt16(sp);
  // Skip over "LookupFlag" field.
  sp = sp.subspan(2u);
  result.sub_tables = Lookup::SubTables(GetUInt16(sp));
  if (result.lookup_type != 1) {
    return result;
  }
  for (auto& sub_table : result.sub_tables) {
    sub_table = ParseSingleSubst(raw.subspan(GetUInt16(sp)));
  }
  return result;
}

CFX_CTTGSUBTable::CoverageFormat CFX_CTTGSUBTable::ParseCoverage(
    pdfium::span<const uint8_t> raw) {
  pdfium::span<const uint8_t> sp = raw;
  uint16_t format = GetUInt16(sp);
  if (format != 1 && format != 2) {
    return std::monostate();
  }
  if (format == 1) {
    DataVector<uint16_t> glyph_array(GetUInt16(sp));
    for (auto& glyph : glyph_array) {
      glyph = GetUInt16(sp);
    }
    return glyph_array;
  }
  std::vector<RangeRecord> range_records(GetUInt16(sp));
  for (auto& range_rec : range_records) {
    range_rec.start = GetUInt16(sp);
    range_rec.end = GetUInt16(sp);
    range_rec.start_coverage_index = GetUInt16(sp);
  }
  return range_records;
}

CFX_CTTGSUBTable::SubTable CFX_CTTGSUBTable::ParseSingleSubst(
    pdfium::span<const uint8_t> raw) {
  pdfium::span<const uint8_t> sp = raw;
  uint16_t format = GetUInt16(sp);
  SubTable rec;
  if (format != 1 && format != 2) {
    return rec;
  }
  rec.coverage = ParseCoverage(raw.subspan(GetUInt16(sp)));
  if (format == 1) {
    rec.table_data = GetInt16(sp);
  } else {
    DataVector<uint16_t> table_data(GetUInt16(sp));
    for (auto& substitute : table_data) {
      substitute = GetUInt16(sp);
    }
    rec.table_data = std::move(table_data);
  }
  return rec;
}

CFX_CTTGSUBTable::FeatureRecord::FeatureRecord() = default;

CFX_CTTGSUBTable::FeatureRecord::~FeatureRecord() = default;

CFX_CTTGSUBTable::RangeRecord::RangeRecord() = default;

CFX_CTTGSUBTable::SubTable::SubTable() = default;

CFX_CTTGSUBTable::SubTable::SubTable(SubTable&& that) noexcept = default;

CFX_CTTGSUBTable::SubTable& CFX_CTTGSUBTable::SubTable::operator=(
    SubTable&& that) noexcept = default;

CFX_CTTGSUBTable::SubTable::~SubTable() = default;

CFX_CTTGSUBTable::Lookup::Lookup() = default;

CFX_CTTGSUBTable::Lookup::Lookup(Lookup&& that) noexcept = default;

CFX_CTTGSUBTable::Lookup& CFX_CTTGSUBTable::Lookup::operator=(
    Lookup&& that) noexcept = default;

CFX_CTTGSUBTable::Lookup::~Lookup() = default;
