/*
 * 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/table/core/name_table.h"

#include <stdio.h>
#include <string.h>

#include <unicode/unistr.h>

#include "sfntly/font.h"
#include "sfntly/port/exception_type.h"

namespace sfntly {
/******************************************************************************
 * NameTable::NameEntryId class
 ******************************************************************************/
NameTable::NameEntryId::NameEntryId()
    : platform_id_(0),
      encoding_id_(0),
      language_id_(0),
      name_id_(0) {
}

NameTable::NameEntryId::NameEntryId(int32_t platform_id,
                                    int32_t encoding_id,
                                    int32_t language_id,
                                    int32_t name_id)
    : platform_id_(platform_id),
      encoding_id_(encoding_id),
      language_id_(language_id),
      name_id_(name_id) {
}

NameTable::NameEntryId::NameEntryId(const NameTable::NameEntryId& rhs) {
  *this = rhs;
}

const NameTable::NameEntryId&
    NameTable::NameEntryId::operator=(const NameTable::NameEntryId& rhs) const {
  platform_id_ = rhs.platform_id_;
  encoding_id_ = rhs.encoding_id_;
  language_id_ = rhs.language_id_;
  name_id_ = rhs.name_id_;
  return *this;
}

bool NameTable::NameEntryId::operator==(const NameEntryId& rhs) const {
  return platform_id_ == rhs.platform_id_ &&
         encoding_id_ == rhs.encoding_id_ &&
         language_id_ == rhs.language_id_ &&
         name_id_ == rhs.name_id_;
}

bool NameTable::NameEntryId::operator<(const NameEntryId& rhs) const {
  if (platform_id_ != rhs.platform_id_) return platform_id_ < rhs.platform_id_;
  if (encoding_id_ != rhs.encoding_id_) return encoding_id_ < rhs.encoding_id_;
  if (language_id_ != rhs.language_id_) return language_id_ < rhs.language_id_;
  return name_id_ < rhs.name_id_;
}

/******************************************************************************
 * NameTable::NameEntry class
 ******************************************************************************/
NameTable::NameEntry::NameEntry() {
  Init(0, 0, 0, 0, NULL);
}

NameTable::NameEntry::NameEntry(const NameEntryId& name_entry_id,
                                const std::vector<uint8_t>& name_bytes) {
  Init(name_entry_id.platform_id(),
       name_entry_id.encoding_id(),
       name_entry_id.language_id(),
       name_entry_id.name_id(),
       &name_bytes);
}

NameTable::NameEntry::NameEntry(int32_t platform_id,
                                int32_t encoding_id,
                                int32_t language_id,
                                int32_t name_id,
                                const std::vector<uint8_t>& name_bytes) {
  Init(platform_id, encoding_id, language_id, name_id, &name_bytes);
}

NameTable::NameEntry::~NameEntry() {}

std::vector<uint8_t>* NameTable::NameEntry::NameAsBytes() {
  return &name_bytes_;
}

int32_t NameTable::NameEntry::NameBytesLength() {
  return name_bytes_.size();
}

UChar* NameTable::NameEntry::Name() {
  return NameTable::ConvertFromNameBytes(&name_bytes_,
                                         platform_id(),
                                         encoding_id());
}

bool NameTable::NameEntry::operator==(const NameEntry& rhs) const {
  return (name_entry_id_ == rhs.name_entry_id_ &&
          name_bytes_ == rhs.name_bytes_);
}

void NameTable::NameEntry::Init(int32_t platform_id,
                                int32_t encoding_id,
                                int32_t language_id,
                                int32_t name_id,
                                const std::vector<uint8_t>* name_bytes) {
  name_entry_id_ = NameEntryId(platform_id, encoding_id, language_id, name_id);
  if (name_bytes) {
    name_bytes_ = *name_bytes;
  } else {
    name_bytes_.clear();
  }
}

/******************************************************************************
 * NameTable::NameEntryBuilder class
 ******************************************************************************/
NameTable::NameEntryBuilder::NameEntryBuilder() {
  Init(0, 0, 0, 0, NULL);
}

NameTable::NameEntryBuilder::NameEntryBuilder(const NameEntryId& name_entry_id,
                                              const std::vector<uint8_t>& name_bytes) {
  Init(name_entry_id.platform_id(),
       name_entry_id.encoding_id(),
       name_entry_id.language_id(),
       name_entry_id.name_id(),
       &name_bytes);
}

NameTable::NameEntryBuilder::NameEntryBuilder(
    const NameEntryId& name_entry_id) {
  Init(name_entry_id.platform_id(),
       name_entry_id.encoding_id(),
       name_entry_id.language_id(),
       name_entry_id.name_id(),
       NULL);
}

NameTable::NameEntryBuilder::NameEntryBuilder(NameEntry* b) {
  Init(b->platform_id(),
       b->encoding_id(),
       b->language_id(),
       b->name_id(),
       b->NameAsBytes());
}

NameTable::NameEntryBuilder::~NameEntryBuilder() {}

void NameTable::NameEntryBuilder::SetName(const UChar* name) {
  if (name == NULL) {
    name_entry_->name_bytes_.clear();
    return;
  }
  NameTable::ConvertToNameBytes(name,
                                name_entry_->platform_id(),
                                name_entry_->encoding_id(),
                                &name_entry_->name_bytes_);
}

void NameTable::NameEntryBuilder::SetName(const std::vector<uint8_t>& name_bytes) {
  name_entry_->name_bytes_.clear();
  std::copy(name_bytes.begin(),
            name_bytes.end(),
            name_entry_->name_bytes_.begin());
}

void NameTable::NameEntryBuilder::SetName(const std::vector<uint8_t>& name_bytes,
                                          int32_t offset,
                                          int32_t length) {
  name_entry_->name_bytes_.clear();
  std::copy(name_bytes.begin() + offset,
            name_bytes.begin() + offset + length,
            name_entry_->name_bytes_.begin());
}

void NameTable::NameEntryBuilder::Init(int32_t platform_id,
                                       int32_t encoding_id,
                                       int32_t language_id,
                                       int32_t name_id,
                                       const std::vector<uint8_t>* name_bytes) {
  name_entry_ = new NameEntry();
  name_entry_->Init(platform_id, encoding_id, language_id, name_id, name_bytes);
}

/******************************************************************************
 * NameTable::NameEntryFilterInPlace class (C++ port only)
 ******************************************************************************/
NameTable::NameEntryFilterInPlace::NameEntryFilterInPlace(int32_t platform_id,
                                                          int32_t encoding_id,
                                                          int32_t language_id,
                                                          int32_t name_id)
    : platform_id_(platform_id),
      encoding_id_(encoding_id),
      language_id_(language_id),
      name_id_(name_id) {
}

bool NameTable::NameEntryFilterInPlace::Accept(int32_t platform_id,
                                               int32_t encoding_id,
                                               int32_t language_id,
                                               int32_t name_id) {
  return (platform_id_ == platform_id &&
          encoding_id_ == encoding_id &&
          language_id_ == language_id &&
          name_id_ == name_id);
}

/******************************************************************************
 * NameTable::NameEntryIterator class
 ******************************************************************************/
NameTable::NameEntryIterator::NameEntryIterator(NameTable* table)
    : RefIterator<NameEntry, NameTable>(table),
      name_index_(0),
      filter_(NULL) {
}

NameTable::NameEntryIterator::NameEntryIterator(NameTable* table,
                                                NameEntryFilter* filter)
    : RefIterator<NameEntry, NameTable>(table),
      name_index_(0),
      filter_(filter) {
}

bool NameTable::NameEntryIterator::HasNext() {
  if (!filter_) {
    if (name_index_ < container()->NameCount()) {
      return true;
    }
    return false;
  }
  for (; name_index_ < container()->NameCount(); ++name_index_) {
    if (filter_->Accept(container()->PlatformId(name_index_),
                        container()->EncodingId(name_index_),
                        container()->LanguageId(name_index_),
                        container()->NameId(name_index_))) {
      return true;
    }
  }
  return false;
}

CALLER_ATTACH NameTable::NameEntry* NameTable::NameEntryIterator::Next() {
  if (!HasNext())
    return NULL;
  return container()->GetNameEntry(name_index_++);
}

/******************************************************************************
 * NameTable::Builder class
 ******************************************************************************/
NameTable::Builder::Builder(Header* header, WritableFontData* data)
    : SubTableContainerTable::Builder(header, data) {
}

NameTable::Builder::Builder(Header* header, ReadableFontData* data)
    : SubTableContainerTable::Builder(header, data) {
}

CALLER_ATTACH NameTable::Builder*
    NameTable::Builder::CreateBuilder(Header* header,
                                      WritableFontData* data) {
  Ptr<NameTable::Builder> builder;
  builder = new NameTable::Builder(header, data);
  return builder.Detach();
}

void NameTable::Builder::RevertNames() {
  name_entry_map_.clear();
  set_model_changed(false);
}

int32_t NameTable::Builder::BuilderCount() {
  GetNameBuilders();  // Ensure name_entry_map_ is built.
  return (int32_t)name_entry_map_.size();
}

bool NameTable::Builder::Has(int32_t platform_id,
                             int32_t encoding_id,
                             int32_t language_id,
                             int32_t name_id) {
  NameEntryId probe(platform_id, encoding_id, language_id, name_id);
  GetNameBuilders();  // Ensure name_entry_map_ is built.
  return (name_entry_map_.find(probe) != name_entry_map_.end());
}

CALLER_ATTACH NameTable::NameEntryBuilder*
    NameTable::Builder::NameBuilder(int32_t platform_id,
                                    int32_t encoding_id,
                                    int32_t language_id,
                                    int32_t name_id) {
  NameEntryId probe(platform_id, encoding_id, language_id, name_id);
  NameEntryBuilderMap builders;
  GetNameBuilders();  // Ensure name_entry_map_ is built.
  if (name_entry_map_.find(probe) != name_entry_map_.end()) {
    return name_entry_map_[probe];
  }
  NameEntryBuilderPtr builder = new NameEntryBuilder(probe);
  name_entry_map_[probe] = builder;
  return builder.Detach();
}

bool NameTable::Builder::Remove(int32_t platform_id,
                                int32_t encoding_id,
                                int32_t language_id,
                                int32_t name_id) {
  NameEntryId probe(platform_id, encoding_id, language_id, name_id);
  GetNameBuilders();  // Ensure name_entry_map_ is built.
  NameEntryBuilderMap::iterator position = name_entry_map_.find(probe);
  if (position != name_entry_map_.end()) {
    name_entry_map_.erase(position);
    return true;
  }
  return false;
}

CALLER_ATTACH FontDataTable*
    NameTable::Builder::SubBuildTable(ReadableFontData* data) {
  FontDataTablePtr table = new NameTable(header(), data);
  return table.Detach();
}

void NameTable::Builder::SubDataSet() {
  name_entry_map_.clear();
  set_model_changed(false);
}

int32_t NameTable::Builder::SubDataSizeToSerialize() {
  if (name_entry_map_.empty()) {
    return 0;
  }

  int32_t size = NameTable::Offset::kNameRecordStart +
                 name_entry_map_.size() * NameTable::Offset::kNameRecordSize;
  for (NameEntryBuilderMap::iterator b = name_entry_map_.begin(),
                                     end = name_entry_map_.end();
                                     b != end; ++b) {
    NameEntryBuilderPtr p = b->second;
    NameEntry* entry = p->name_entry();
    size += entry->NameBytesLength();
  }
  return size;
}

bool NameTable::Builder::SubReadyToSerialize() {
  return !name_entry_map_.empty();
}

int32_t NameTable::Builder::SubSerialize(WritableFontData* new_data) {
  int32_t string_table_start_offset =
      NameTable::Offset::kNameRecordStart +
      name_entry_map_.size() * NameTable::Offset::kNameRecordSize;

  // Header
  new_data->WriteUShort(NameTable::Offset::kFormat, 0);
  new_data->WriteUShort(NameTable::Offset::kCount, name_entry_map_.size());
  new_data->WriteUShort(NameTable::Offset::kStringOffset,
                        string_table_start_offset);
  int32_t name_record_offset = NameTable::Offset::kNameRecordStart;
  int32_t string_offset = 0;
  // Note: we offered operator< in NameEntryId, which will be used by std::less,
  //       and therefore our map will act like TreeMap in Java to provide
  //       sorted key set.
  for (NameEntryBuilderMap::iterator b = name_entry_map_.begin(),
                                     end = name_entry_map_.end();
                                     b != end; ++b) {
    new_data->WriteUShort(
        name_record_offset + NameTable::Offset::kNameRecordPlatformId,
        b->first.platform_id());
    new_data->WriteUShort(
        name_record_offset + NameTable::Offset::kNameRecordEncodingId,
        b->first.encoding_id());
    new_data->WriteUShort(
        name_record_offset + NameTable::Offset::kNameRecordLanguageId,
        b->first.language_id());
    new_data->WriteUShort(
        name_record_offset + NameTable::Offset::kNameRecordNameId,
        b->first.name_id());
    NameEntry* builder_entry = b->second->name_entry();
    new_data->WriteUShort(
        name_record_offset + NameTable::Offset::kNameRecordStringLength,
        builder_entry->NameBytesLength());
    new_data->WriteUShort(
        name_record_offset + NameTable::Offset::kNameRecordStringOffset,
        string_offset);
    name_record_offset += NameTable::Offset::kNameRecordSize;
    string_offset += new_data->WriteBytes(
        string_offset + string_table_start_offset,
        builder_entry->NameAsBytes());
  }

  return string_offset + string_table_start_offset;
}

void NameTable::Builder::Initialize(ReadableFontData* data) {
  if (data) {
    NameTablePtr table = new NameTable(header(), data);
    Ptr<NameEntryIterator> name_iter;
    name_iter.Attach(table->Iterator());
    while (name_iter->HasNext()) {
      NameEntryPtr name_entry;
      name_entry.Attach(name_iter->Next());
      NameEntryBuilderPtr name_entry_builder = new NameEntryBuilder(name_entry);
      NameEntry* builder_entry = name_entry_builder->name_entry();
      NameEntryId probe = builder_entry->name_entry_id();
      name_entry_map_[probe] = name_entry_builder;
    }
  }
}

NameTable::NameEntryBuilderMap* NameTable::Builder::GetNameBuilders() {
  if (name_entry_map_.empty()) {
    Initialize(InternalReadData());
  }
  set_model_changed();
  return &name_entry_map_;
}

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

int32_t NameTable::Format() {
  return data_->ReadUShort(Offset::kFormat);
}

int32_t NameTable::NameCount() {
  return data_->ReadUShort(Offset::kCount);
}

int32_t NameTable::PlatformId(int32_t index) {
  return data_->ReadUShort(Offset::kNameRecordPlatformId +
                           OffsetForNameRecord(index));
}

int32_t NameTable::EncodingId(int32_t index) {
  return data_->ReadUShort(Offset::kNameRecordEncodingId +
                           OffsetForNameRecord(index));
}

int32_t NameTable::LanguageId(int32_t index) {
  return data_->ReadUShort(Offset::kNameRecordLanguageId +
                           OffsetForNameRecord(index));
}

int32_t NameTable::NameId(int32_t index) {
  return data_->ReadUShort(Offset::kNameRecordNameId +
                           OffsetForNameRecord(index));
}

void NameTable::NameAsBytes(int32_t index, std::vector<uint8_t>* b) {
  assert(b);
  b->clear();

  int32_t length = NameLength(index);
  if (length <= 0)
    return;

  int32_t offset = NameOffset(index);
  if (offset < 0)
    return;

  b->resize(length);
  data_->ReadBytes(offset, &((*b)[0]), 0, length);
}

void NameTable::NameAsBytes(int32_t platform_id,
                            int32_t encoding_id,
                            int32_t language_id,
                            int32_t name_id,
                            std::vector<uint8_t>* b) {
  assert(b);
  NameEntryPtr entry;
  entry.Attach(GetNameEntry(platform_id, encoding_id, language_id, name_id));
  if (entry) {
    std::vector<uint8_t>* name = entry->NameAsBytes();
    std::copy(name->begin(), name->end(), b->begin());
  }
}

UChar* NameTable::Name(int32_t index) {
  std::vector<uint8_t> b;
  NameAsBytes(index, &b);
  return ConvertFromNameBytes(&b, PlatformId(index), EncodingId(index));
}

UChar* NameTable::Name(int32_t platform_id,
                       int32_t encoding_id,
                       int32_t language_id,
                       int32_t name_id) {
  NameEntryPtr entry;
  entry.Attach(GetNameEntry(platform_id, encoding_id, language_id, name_id));
  if (entry) {
    return entry->Name();
  }
  return NULL;
}

CALLER_ATTACH NameTable::NameEntry* NameTable::GetNameEntry(int32_t index) {
  std::vector<uint8_t> b;
  NameAsBytes(index, &b);
  NameEntryPtr instance = new NameEntry(PlatformId(index),
                                        EncodingId(index),
                                        LanguageId(index),
                                        NameId(index), b);
  return instance.Detach();
}

CALLER_ATTACH NameTable::NameEntry* NameTable::GetNameEntry(int32_t platform_id,
                                                            int32_t encoding_id,
                                                            int32_t language_id,
                                                            int32_t name_id) {
  NameTable::NameEntryFilterInPlace
      filter(platform_id, encoding_id, language_id, name_id);
  Ptr<NameTable::NameEntryIterator> name_entry_iter;
  name_entry_iter.Attach(Iterator(&filter));
  NameEntryPtr result;
  if (name_entry_iter->HasNext()) {
    result = name_entry_iter->Next();
  }
  return result;
}

CALLER_ATTACH NameTable::NameEntryIterator* NameTable::Iterator() {
  Ptr<NameEntryIterator> output = new NameTable::NameEntryIterator(this);
  return output.Detach();
}

CALLER_ATTACH
NameTable::NameEntryIterator* NameTable::Iterator(NameEntryFilter* filter) {
  Ptr<NameEntryIterator> output =
      new NameTable::NameEntryIterator(this, filter);
  return output.Detach();
}

NameTable::NameTable(Header* header, ReadableFontData* data)
    : SubTableContainerTable(header, data) {}

int32_t NameTable::StringOffset() {
  return data_->ReadUShort(Offset::kStringOffset);
}

int32_t NameTable::OffsetForNameRecord(int32_t index) {
  return Offset::kNameRecordStart + index * Offset::kNameRecordSize;
}

int32_t NameTable::NameLength(int32_t index) {
  return data_->ReadUShort(Offset::kNameRecordStringLength +
                           OffsetForNameRecord(index));
}

int32_t NameTable::NameOffset(int32_t index) {
  return data_->ReadUShort(Offset::kNameRecordStringOffset +
                           OffsetForNameRecord(index)) + StringOffset();
}

const char* NameTable::GetEncodingName(int32_t platform_id,
                                       int32_t encoding_id) {
  switch (platform_id) {
    case PlatformId::kUnicode:
      return "UTF-16BE";
    case PlatformId::kMacintosh:
      switch (encoding_id) {
        case MacintoshEncodingId::kRoman:
          return "MacRoman";
        case MacintoshEncodingId::kJapanese:
          return "Shift-JIS";
        case MacintoshEncodingId::kChineseTraditional:
          return "Big5";
        case MacintoshEncodingId::kKorean:
          return "EUC-KR";
        case MacintoshEncodingId::kArabic:
          return "MacArabic";
        case MacintoshEncodingId::kHebrew:
          return "MacHebrew";
        case MacintoshEncodingId::kGreek:
          return "MacGreek";
        case MacintoshEncodingId::kRussian:
          return "MacCyrillic";
        case MacintoshEncodingId::kRSymbol:
          return "MacSymbol";
        case MacintoshEncodingId::kThai:
          return "MacThai";
        case MacintoshEncodingId::kChineseSimplified:
          return "EUC-CN";
        default:  // Note: unknown/unconfirmed cases are not ported.
          break;
      }
      break;
    case PlatformId::kISO:
      break;
    case PlatformId::kWindows:
      switch (encoding_id) {
        case WindowsEncodingId::kSymbol:
        case WindowsEncodingId::kUnicodeUCS2:
          return "UTF-16BE";
        case WindowsEncodingId::kShiftJIS:
          return "windows-933";
        case WindowsEncodingId::kPRC:
          return "windows-936";
        case WindowsEncodingId::kBig5:
          return "windows-950";
        case WindowsEncodingId::kWansung:
          return "windows-949";
        case WindowsEncodingId::kJohab:
          return "ms1361";
        case WindowsEncodingId::kUnicodeUCS4:
          return "UCS-4";
      }
      break;
    case PlatformId::kCustom:
      break;
    default:
      break;
  }
  return NULL;
}

UConverter* NameTable::GetCharset(int32_t platform_id, int32_t encoding_id) {
  UErrorCode error_code = U_ZERO_ERROR;
  UConverter* conv = ucnv_open(GetEncodingName(platform_id, encoding_id),
                               &error_code);
  if (U_SUCCESS(error_code)) {
    return conv;
  }

  if (conv) {
    ucnv_close(conv);
  }
  return NULL;
}

void NameTable::ConvertToNameBytes(const UChar* name,
                                   int32_t platform_id,
                                   int32_t encoding_id,
                                   std::vector<uint8_t>* b) {
  assert(b);
  assert(name);
  b->clear();
  UConverter* cs = GetCharset(platform_id, encoding_id);
  if (cs == NULL) {
    return;
  }

  // Preflight to get buffer size.
  UErrorCode error_code = U_ZERO_ERROR;
  int32_t length = ucnv_fromUChars(cs, NULL, 0, name, -1, &error_code);
  b->resize(length + 4);  // The longest termination "\0" is 4 bytes.
  memset(&((*b)[0]), 0, length + 4);
  error_code = U_ZERO_ERROR;
  ucnv_fromUChars(cs,
                  reinterpret_cast<char*>(&((*b)[0])),
                  length + 4,
                  name,
                  -1,
                  &error_code);
  if (!U_SUCCESS(error_code)) {
    b->clear();
  }
  ucnv_close(cs);
}

UChar* NameTable::ConvertFromNameBytes(std::vector<uint8_t>* name_bytes,
                                       int32_t platform_id,
                                       int32_t encoding_id) {
  if (name_bytes == NULL || name_bytes->size() == 0) {
    return NULL;
  }
  UConverter* cs = GetCharset(platform_id, encoding_id);
  UErrorCode error_code = U_ZERO_ERROR;
  if (cs == NULL) {
    char buffer[11] = {0};
#if defined (WIN32)
    _itoa_s(platform_id, buffer, 16);
#else
    snprintf(buffer, sizeof(buffer), "%x", platform_id);
#endif
    UChar* result = new UChar[12];
    memset(result, 0, sizeof(UChar) * 12);
    cs = ucnv_open("utf-8", &error_code);
    if (U_SUCCESS(error_code)) {
      ucnv_toUChars(cs, result, 12, buffer, 11, &error_code);
      ucnv_close(cs);
      if (U_SUCCESS(error_code)) {
        return result;
      }
    }
    delete[] result;
    return NULL;
  }

  // No preflight needed here, we will be bigger.
  UChar* output_buffer = new UChar[name_bytes->size() + 1];
  memset(output_buffer, 0, sizeof(UChar) * (name_bytes->size() + 1));
  int32_t length = ucnv_toUChars(cs,
                                 output_buffer,
                                 name_bytes->size(),
                                 reinterpret_cast<char*>(&((*name_bytes)[0])),
                                 name_bytes->size(),
                                 &error_code);
  ucnv_close(cs);
  if (length > 0) {
    return output_buffer;
  }

  delete[] output_buffer;
  return NULL;
}

}  // namespace sfntly
