/*
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "sfntly/font.h"

#include <stdio.h>

#include <algorithm>
#include <functional>
#include <iterator>
#include <limits>
#include <map>
#include <string>
#include <typeinfo>

#include "sfntly/data/font_input_stream.h"
#include "sfntly/font_factory.h"
#include "sfntly/math/fixed1616.h"
#include "sfntly/math/font_math.h"
#include "sfntly/port/exception_type.h"
#include "sfntly/table/core/font_header_table.h"
#include "sfntly/table/core/horizontal_device_metrics_table.h"
#include "sfntly/table/core/horizontal_header_table.h"
#include "sfntly/table/core/horizontal_metrics_table.h"
#include "sfntly/table/core/maximum_profile_table.h"
#include "sfntly/table/truetype/loca_table.h"
#include "sfntly/tag.h"

namespace sfntly {

namespace {

const int32_t kSFNTVersionMajor = 1;
const int32_t kSFNTVersionMinor = 0;

const int32_t kMaxTableSize = 200 * 1024 * 1024;

bool IsValidHeaderRegion(int32_t data_length, int32_t offset, int32_t length) {
  return offset >= 0 && length >= 0 &&
         offset <= std::numeric_limits<int32_t>::max() - length &&
         offset + length <= data_length;
}

}  // namespace

/******************************************************************************
 * Font class
 ******************************************************************************/
Font::~Font() {}

bool Font::HasTable(int32_t tag) const {
  return tables_.find(tag) != tables_.end();
}

Table* Font::GetTable(int32_t tag) {
  if (!HasTable(tag))
    return NULL;
  return tables_[tag];
}

const TableMap* Font::GetTableMap() {
  return &tables_;
}

void Font::Serialize(OutputStream* os, std::vector<int32_t>* table_ordering) {
  assert(table_ordering);
  std::vector<int32_t> final_table_ordering;
  GenerateTableOrdering(table_ordering, &final_table_ordering);
  TableHeaderList table_records;
  BuildTableHeadersForSerialization(&final_table_ordering, &table_records);

  FontOutputStream fos(os);
  SerializeHeader(&fos, &table_records);
  SerializeTables(&fos, &table_records);
}

Font::Font(int32_t sfnt_version, std::vector<uint8_t>* digest)
    : sfnt_version_(sfnt_version) {
  // non-trivial assignments that makes debugging hard if placed in
  // initialization list
  digest_ = *digest;
}

void Font::BuildTableHeadersForSerialization(std::vector<int32_t>* table_ordering,
                                             TableHeaderList* table_headers) {
  assert(table_headers);
  assert(table_ordering);

  std::vector<int32_t> final_table_ordering;
  GenerateTableOrdering(table_ordering, &final_table_ordering);
  int32_t table_offset = Offset::kTableRecordBegin + num_tables() *
                         Offset::kTableRecordSize;
  for (size_t i = 0; i < final_table_ordering.size(); ++i) {
    int32_t tag = final_table_ordering[i];
    TablePtr table = GetTable(tag);
    if (table == NULL)
      continue;

    HeaderPtr header = new Header(tag, table->CalculatedChecksum(),
                                  table_offset, table->header()->length());
    table_headers->push_back(header);
    table_offset += (table->DataLength() + 3) & ~3;
  }
}

void Font::SerializeHeader(FontOutputStream* fos,
                           TableHeaderList* table_headers) {
  fos->WriteFixed(sfnt_version_);
  fos->WriteUShort(table_headers->size());
  int32_t log2_of_max_power_of_2 = FontMath::Log2(table_headers->size());
  int32_t search_range = 2 << (log2_of_max_power_of_2 - 1 + 4);
  fos->WriteUShort(search_range);
  fos->WriteUShort(log2_of_max_power_of_2);
  fos->WriteUShort((table_headers->size() * 16) - search_range);

  HeaderTagSortedSet sorted_headers;
  std::copy(table_headers->begin(),
            table_headers->end(),
            std::inserter(sorted_headers, sorted_headers.end()));

  for (HeaderTagSortedSet::iterator record = sorted_headers.begin(),
                                    record_end = sorted_headers.end();
                                    record != record_end; ++record) {
    fos->WriteULong((*record)->tag());
    fos->WriteULong((int32_t)((*record)->checksum()));
    fos->WriteULong((*record)->offset());
    fos->WriteULong((*record)->length());
  }
}

void Font::SerializeTables(FontOutputStream* fos,
                           TableHeaderList* table_headers) {
  assert(fos);
  assert(table_headers);
  for (size_t i = 0; i < table_headers->size(); ++i) {
    const HeaderPtr& record = (*table_headers)[i];
    TablePtr target_table = GetTable(record->tag());
    if (target_table == NULL) {
#if !defined (SFNTLY_NO_EXCEPTION)
      throw IOException("Table out of sync with font header.");
#endif
      return;
    }
    int32_t table_size = target_table->Serialize(fos);
    assert(table_size == record->length());

    int32_t filler_size = ((table_size + 3) & ~3) - table_size;
    for (int32_t i = 0; i < filler_size; ++i) {
      fos->Write(static_cast<uint8_t>(0));
    }
  }
}

void Font::GenerateTableOrdering(std::vector<int32_t>* default_table_ordering,
                                 std::vector<int32_t>* table_ordering) {
  assert(default_table_ordering);
  assert(table_ordering);
  table_ordering->clear();
  if (default_table_ordering->empty()) {
    DefaultTableOrdering(default_table_ordering);
  }

  typedef std::map<int32_t, bool> Int2Bool;
  typedef std::pair<int32_t, bool> Int2BoolEntry;
  Int2Bool tables_in_font;
  for (TableMap::iterator table = tables_.begin(), table_end = tables_.end();
                          table != table_end; ++table) {
    tables_in_font.insert(Int2BoolEntry(table->first, false));
  }
  for (std::vector<int32_t>::iterator tag = default_table_ordering->begin(),
                             tag_end = default_table_ordering->end();
                             tag != tag_end; ++tag) {
    if (HasTable(*tag)) {
      table_ordering->push_back(*tag);
      tables_in_font[*tag] = true;
    }
  }
  for (Int2Bool::iterator table = tables_in_font.begin(),
                          table_end = tables_in_font.end();
                          table != table_end; ++table) {
    if (table->second == false)
      table_ordering->push_back(table->first);
  }
}

void Font::DefaultTableOrdering(std::vector<int32_t>* default_table_ordering) {
  assert(default_table_ordering);
  default_table_ordering->clear();
  if (HasTable(Tag::CFF)) {
    default_table_ordering->resize(CFF_TABLE_ORDERING_SIZE);
    std::copy(CFF_TABLE_ORDERING, CFF_TABLE_ORDERING + CFF_TABLE_ORDERING_SIZE,
              default_table_ordering->begin());
    return;
  }
  default_table_ordering->resize(TRUE_TYPE_TABLE_ORDERING_SIZE);
  std::copy(TRUE_TYPE_TABLE_ORDERING,
            TRUE_TYPE_TABLE_ORDERING + TRUE_TYPE_TABLE_ORDERING_SIZE,
            default_table_ordering->begin());
}

/******************************************************************************
 * Font::Builder class
 ******************************************************************************/
Font::Builder::~Builder() {}

CALLER_ATTACH Font::Builder* Font::Builder::GetOTFBuilder(FontFactory* factory,
                                                          InputStream* is) {
  FontBuilderPtr builder = new Builder(factory);
  builder->LoadFont(is);
  return builder.Detach();
}

CALLER_ATTACH Font::Builder* Font::Builder::GetOTFBuilder(
    FontFactory* factory,
    WritableFontData* wfd,
    int32_t offset_to_offset_table) {
  FontBuilderPtr builder = new Builder(factory);
  builder->LoadFont(wfd, offset_to_offset_table);
  return builder.Detach();
}

CALLER_ATTACH Font::Builder* Font::Builder::GetOTFBuilder(
    FontFactory* factory) {
  FontBuilderPtr builder = new Builder(factory);
  return builder.Detach();
}

bool Font::Builder::ReadyToBuild() {
  // just read in data with no manipulation
  if (table_builders_.empty() && !data_blocks_.empty()) {
    return true;
  }

  // TODO(stuartg): font level checks - required tables etc?
  for (TableBuilderMap::iterator table_builder = table_builders_.begin(),
                                 table_builder_end = table_builders_.end();
                                 table_builder != table_builder_end;
                                 ++table_builder) {
    if (!table_builder->second->ReadyToBuild())
      return false;
  }
  return true;
}

CALLER_ATTACH Font* Font::Builder::Build() {
  FontPtr font = new Font(sfnt_version_, &digest_);

  if (!table_builders_.empty()) {
    // Note: Different from Java. Directly use font->tables_ here to avoid
    //       STL container copying.
    BuildTablesFromBuilders(font, &table_builders_, &font->tables_);
  }

  table_builders_.clear();
  data_blocks_.clear();
  return font.Detach();
}

void Font::Builder::SetDigest(std::vector<uint8_t>* digest) {
  digest_.clear();
  digest_ = *digest;
}

void Font::Builder::ClearTableBuilders() {
  table_builders_.clear();
}

bool Font::Builder::HasTableBuilder(int32_t tag) {
  return (table_builders_.find(tag) != table_builders_.end());
}

Table::Builder* Font::Builder::GetTableBuilder(int32_t tag) {
  if (HasTableBuilder(tag))
    return table_builders_[tag];
  return NULL;
}

Table::Builder* Font::Builder::NewTableBuilder(int32_t tag) {
  HeaderPtr header = new Header(tag);
  TableBuilderPtr builder;
  builder.Attach(Table::Builder::GetBuilder(header, NULL));
  table_builders_.insert(TableBuilderEntry(header->tag(), builder));
  return builder;
}

Table::Builder* Font::Builder::NewTableBuilder(int32_t tag,
                                               ReadableFontData* src_data) {
  assert(src_data);
  WritableFontDataPtr data;
  data.Attach(WritableFontData::CreateWritableFontData(src_data->Length()));
  // TODO(stuarg): take over original data instead?
  src_data->CopyTo(data);

  HeaderPtr header = new Header(tag, data->Length());
  TableBuilderPtr builder;
  builder.Attach(Table::Builder::GetBuilder(header, data));
  table_builders_.insert(TableBuilderEntry(tag, builder));
  return builder;
}

void Font::Builder::RemoveTableBuilder(int32_t tag) {
  table_builders_.erase(tag);
}

Font::Builder::Builder(FontFactory* factory)
    : factory_(factory),
      sfnt_version_(Fixed1616::Fixed(kSFNTVersionMajor, kSFNTVersionMinor)) {
}

void Font::Builder::LoadFont(InputStream* is) {
  // Note: we do not throw exception here for is.  This is more of an assertion.
  assert(is);
  FontInputStream font_is(is);
  HeaderOffsetSortedSet records;
  ReadHeader(&font_is, &records);
  LoadTableData(&records, &font_is, &data_blocks_);
  BuildAllTableBuilders(&data_blocks_, &table_builders_);
  font_is.Close();
}

void Font::Builder::LoadFont(WritableFontData* wfd,
                             int32_t offset_to_offset_table) {
  // Note: we do not throw exception here for is.  This is more of an assertion.
  assert(wfd);
  HeaderOffsetSortedSet records;
  ReadHeader(wfd, offset_to_offset_table, &records);
  LoadTableData(&records, wfd, &data_blocks_);
  BuildAllTableBuilders(&data_blocks_, &table_builders_);
}

int32_t Font::Builder::SfntWrapperSize() {
  return Offset::kSfntHeaderSize +
         (Offset::kTableRecordSize * table_builders_.size());
}

void Font::Builder::BuildAllTableBuilders(DataBlockMap* table_data,
                                          TableBuilderMap* builder_map) {
  for (DataBlockMap::iterator record = table_data->begin(),
                              record_end = table_data->end();
                              record != record_end; ++record) {
    TableBuilderPtr builder;
    builder.Attach(GetTableBuilder(record->first.p_, record->second.p_));
    builder_map->insert(TableBuilderEntry(record->first->tag(), builder));
  }
  InterRelateBuilders(&table_builders_);
}

CALLER_ATTACH
Table::Builder* Font::Builder::GetTableBuilder(Header* header,
                                               WritableFontData* data) {
  return Table::Builder::GetBuilder(header, data);
}

void Font::Builder::BuildTablesFromBuilders(Font* font,
                                            TableBuilderMap* builder_map,
                                            TableMap* table_map) {
  UNREFERENCED_PARAMETER(font);
  InterRelateBuilders(builder_map);

  // Now build all the tables.
  for (TableBuilderMap::iterator builder = builder_map->begin(),
                                 builder_end = builder_map->end();
                                 builder != builder_end; ++builder) {
    TablePtr table;
    if (builder->second && builder->second->ReadyToBuild()) {
      table.Attach(down_cast<Table*>(builder->second->Build()));
    }
    if (table == NULL) {
      table_map->clear();
#if !defined (SFNTLY_NO_EXCEPTION)
      std::string builder_string = "Unable to build table - ";
      char* table_name = TagToString(builder->first);
      builder_string += table_name;
      delete[] table_name;
      throw RuntimeException(builder_string.c_str());
#endif
      return;
    }
    table_map->insert(TableMapEntry(table->header()->tag(), table));
  }
}

static Table::Builder* GetBuilder(TableBuilderMap* builder_map, int32_t tag) {
  if (!builder_map)
    return NULL;

  TableBuilderMap::iterator target = builder_map->find(tag);
  if (target == builder_map->end())
    return NULL;

  return target->second.p_;
}

// Like GetBuilder(), but the returned Builder must be able to support reads.
static Table::Builder* GetReadBuilder(TableBuilderMap* builder_map, int32_t tag) {
  Table::Builder* builder = GetBuilder(builder_map, tag);
  if (!builder || !builder->InternalReadData())
    return NULL;

  return builder;
}

void Font::Builder::InterRelateBuilders(TableBuilderMap* builder_map) {
  Table::Builder* raw_head_builder = GetReadBuilder(builder_map, Tag::head);
  FontHeaderTableBuilderPtr header_table_builder;
  if (raw_head_builder != NULL) {
    header_table_builder =
        down_cast<FontHeaderTable::Builder*>(raw_head_builder);
  }

  Table::Builder* raw_hhea_builder = GetReadBuilder(builder_map, Tag::hhea);
  HorizontalHeaderTableBuilderPtr horizontal_header_builder;
  if (raw_head_builder != NULL) {
    horizontal_header_builder =
        down_cast<HorizontalHeaderTable::Builder*>(raw_hhea_builder);
  }

  Table::Builder* raw_maxp_builder = GetReadBuilder(builder_map, Tag::maxp);
  MaximumProfileTableBuilderPtr max_profile_builder;
  if (raw_maxp_builder != NULL) {
    max_profile_builder =
        down_cast<MaximumProfileTable::Builder*>(raw_maxp_builder);
  }

  Table::Builder* raw_loca_builder = GetBuilder(builder_map, Tag::loca);
  LocaTableBuilderPtr loca_table_builder;
  if (raw_loca_builder != NULL) {
    loca_table_builder = down_cast<LocaTable::Builder*>(raw_loca_builder);
  }

  Table::Builder* raw_hmtx_builder = GetReadBuilder(builder_map, Tag::hmtx);
  HorizontalMetricsTableBuilderPtr horizontal_metrics_builder;
  if (raw_hmtx_builder != NULL) {
    horizontal_metrics_builder =
        down_cast<HorizontalMetricsTable::Builder*>(raw_hmtx_builder);
  }

#if defined (SFNTLY_EXPERIMENTAL)
  Table::Builder* raw_hdmx_builder = GetBuilder(builder_map, Tag::hdmx);
  HorizontalDeviceMetricsTableBuilderPtr hdmx_table_builder;
  if (raw_hdmx_builder != NULL) {
    hdmx_table_builder =
        down_cast<HorizontalDeviceMetricsTable::Builder*>(raw_hdmx_builder);
  }
#endif

  // set the inter table data required to build certain tables
  if (horizontal_metrics_builder != NULL) {
    if (max_profile_builder != NULL) {
      int32_t num_glyphs = max_profile_builder->NumGlyphs();
      if (num_glyphs >= 0)
        horizontal_metrics_builder->SetNumGlyphs(num_glyphs);
    }
    if (horizontal_header_builder != NULL) {
      int32_t num_hmetrics = horizontal_header_builder->NumberOfHMetrics();
      if (num_hmetrics >= 0)
        horizontal_metrics_builder->SetNumberOfHMetrics(num_hmetrics);
    }
  }

  if (loca_table_builder != NULL) {
    if (max_profile_builder != NULL) {
      int32_t num_glyphs = max_profile_builder->NumGlyphs();
      if (num_glyphs >= 0)
        loca_table_builder->SetNumGlyphs(num_glyphs);
    }
    if (header_table_builder != NULL) {
      loca_table_builder->set_format_version(
          header_table_builder->IndexToLocFormat());
    }
  }

#if defined (SFNTLY_EXPERIMENTAL)
  // Note: In C++, hdmx_table_builder can be NULL in a subsetter.
  if (max_profile_builder != NULL && hdmx_table_builder != NULL) {
    hdmx_table_builder->SetNumGlyphs(max_profile_builder->NumGlyphs());
  }
#endif
}

void Font::Builder::ReadHeader(FontInputStream* is,
                               HeaderOffsetSortedSet* records) {
  assert(records);
  sfnt_version_ = is->ReadFixed();
  num_tables_ = is->ReadUShort();
  search_range_ = is->ReadUShort();
  entry_selector_ = is->ReadUShort();
  range_shift_ = is->ReadUShort();

  for (int32_t table_number = 0; table_number < num_tables_; ++table_number) {
    // Need to use temporary vars here.  C++ evaluates function parameters from
    // right to left and thus breaks the order of input stream.
    int32_t tag = is->ReadULongAsInt();
    int64_t checksum = is->ReadULong();
    int32_t offset = is->ReadULongAsInt();
    int32_t length = is->ReadULongAsInt();
    if (!IsValidHeaderRegion(is->Length(), offset, length))
      continue;

    HeaderPtr table = new Header(tag, checksum, offset, length);
    records->insert(table);
  }
}

void Font::Builder::ReadHeader(ReadableFontData* fd,
                               int32_t offset,
                               HeaderOffsetSortedSet* records) {
  assert(records);
  sfnt_version_ = fd->ReadFixed(offset + Offset::kSfntVersion);
  num_tables_ = fd->ReadUShort(offset + Offset::kNumTables);
  search_range_ = fd->ReadUShort(offset + Offset::kSearchRange);
  entry_selector_ = fd->ReadUShort(offset + Offset::kEntrySelector);
  range_shift_ = fd->ReadUShort(offset + Offset::kRangeShift);

  if (num_tables_ > fd->Size() / Offset::kTableRecordSize)
    return;

  int32_t table_offset = offset + Offset::kTableRecordBegin;
  for (int32_t table_number = 0;
       table_number < num_tables_;
       table_number++, table_offset += Offset::kTableRecordSize) {
    int32_t tag = fd->ReadULongAsInt(table_offset + Offset::kTableTag);
    int64_t checksum = fd->ReadULong(table_offset + Offset::kTableCheckSum);
    int32_t offset = fd->ReadULongAsInt(table_offset + Offset::kTableOffset);
    int32_t length = fd->ReadULongAsInt(table_offset + Offset::kTableLength);
    if (!IsValidHeaderRegion(fd->Size(), offset, length))
      continue;

    HeaderPtr table = new Header(tag, checksum, offset, length);
    records->insert(table);
  }
}

void Font::Builder::LoadTableData(HeaderOffsetSortedSet* headers,
                                  FontInputStream* is,
                                  DataBlockMap* table_data) {
  assert(table_data);
  for (HeaderOffsetSortedSet::iterator it = headers->begin(),
                                       table_end = headers->end();
                                       it != table_end;
                                       ++it) {
    const Ptr<Header> header = *it;
    is->Skip(header->offset() - is->position());
    if (header->length() > kMaxTableSize)
      continue;

    FontInputStream table_is(is, header->length());
    WritableFontDataPtr data;
    data.Attach(WritableFontData::CreateWritableFontData(header->length()));
    data->CopyFrom(&table_is, header->length());
    table_data->insert(DataBlockEntry(header, data));
  }
}

void Font::Builder::LoadTableData(HeaderOffsetSortedSet* headers,
                                  WritableFontData* fd,
                                  DataBlockMap* table_data) {
  for (HeaderOffsetSortedSet::iterator it = headers->begin(),
                                       table_end = headers->end();
                                       it != table_end;
                                       ++it) {
    const Ptr<Header> header = *it;
    if (header->length() > kMaxTableSize)
      continue;

    FontDataPtr sliced_data;
    sliced_data.Attach(fd->Slice(header->offset(), header->length()));
    WritableFontDataPtr data = down_cast<WritableFontData*>(sliced_data.p_);
    table_data->insert(DataBlockEntry(header, data));
  }
}

}  // namespace sfntly
