/*
 * 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.
 */

// type.h needs to be included first because of building issues on Windows
// Type aliases we delcare are defined in other headers and make the build
// fail otherwise.
#include "sfntly/port/type.h"
#include "sfntly/table/core/cmap_table.h"

#include <stdio.h>
#include <stdlib.h>

#include <utility>

#include "sfntly/font.h"
#include "sfntly/math/font_math.h"
#include "sfntly/port/endian.h"
#include "sfntly/port/exception_type.h"
#include "sfntly/table/core/name_table.h"

namespace sfntly {

const int32_t CMapTable::NOTDEF = 0;

CMapTable::CMapId CMapTable::WINDOWS_BMP = {
  PlatformId::kWindows,
  WindowsEncodingId::kUnicodeUCS2
};
CMapTable::CMapId CMapTable::WINDOWS_UCS4 = {
  PlatformId::kWindows,
  WindowsEncodingId::kUnicodeUCS4
};
CMapTable::CMapId CMapTable::MAC_ROMAN = {
  PlatformId::kWindows,
  MacintoshEncodingId::kRoman
};

/******************************************************************************
 * CMapTable class
 ******************************************************************************/
CMapTable::CMapTable(Header* header, ReadableFontData* data)
  : SubTableContainerTable(header, data) {
}

CMapTable::~CMapTable() {}

CALLER_ATTACH CMapTable::CMap* CMapTable::GetCMap(const int32_t index) {
  if (index < 0 || index > NumCMaps()) {
#ifndef SFNTLY_NO_EXCEPTION
    throw IndexOutOfBoundException("Requested CMap index is out of bounds.");
#else
    return NULL;
#endif
  }
  int32_t platform_id = PlatformId(index);
  int32_t encoding_id = EncodingId(index);
  CMapId cmap_id = NewCMapId(platform_id, encoding_id);
  int32_t offset_ = Offset(index);
  Ptr<FontDataTable::Builder> cmap_builder =
      (CMap::Builder::GetBuilder(data_, offset_, cmap_id));
  if (!cmap_builder) {
#ifndef SFNTLY_NO_EXCEPTION
    throw NoSuchElementException("Cannot find builder for requested CMap.");
#else
    return NULL;
#endif
  }
  return down_cast<CMapTable::CMap*>(cmap_builder->Build());
}

CALLER_ATTACH CMapTable::CMap* CMapTable::GetCMap(const int32_t platform_id,
                                                  const int32_t encoding_id) {
  return GetCMap(NewCMapId(platform_id, encoding_id));
}

CALLER_ATTACH CMapTable::CMap*
CMapTable::GetCMap(const CMapTable::CMapId cmap_id) {
  CMapIdFilter id_filter(cmap_id);
  CMapIterator cmap_iterator(this, &id_filter);
  // There can only be one cmap with a particular CMapId
  if (cmap_iterator.HasNext()) {
    Ptr<CMapTable::CMap> cmap;
    cmap.Attach(cmap_iterator.Next());
    return cmap.Detach();
  }
#ifndef SFNTLY_NO_EXCEPTION
  throw NoSuchElementException();
#else
  return NULL;
#endif
}

int32_t CMapTable::Version() {
  return data_->ReadUShort(Offset::kVersion);
}

int32_t CMapTable::NumCMaps() {
  return data_->ReadUShort(Offset::kNumTables);
}

CMapTable::CMapId CMapTable::GetCMapId(int32_t index) {
  return NewCMapId(PlatformId(index), EncodingId(index));
}

int32_t CMapTable::PlatformId(int32_t index) {
  return data_->ReadUShort(Offset::kEncodingRecordPlatformId +
                           OffsetForEncodingRecord(index));
}

int32_t CMapTable::EncodingId(int32_t index) {
  return data_->ReadUShort(Offset::kEncodingRecordEncodingId +
                           OffsetForEncodingRecord(index));
}

int32_t CMapTable::Offset(int32_t index) {
  return data_->ReadULongAsInt(Offset::kEncodingRecordOffset +
                               OffsetForEncodingRecord(index));
}

int32_t CMapTable::OffsetForEncodingRecord(int32_t index) {
  return Offset::kEncodingRecordStart + index * Offset::kEncodingRecordSize;
}

CMapTable::CMapId CMapTable::NewCMapId(int32_t platform_id,
                                       int32_t encoding_id) {
  CMapId result;
  result.platform_id = platform_id;
  result.encoding_id = encoding_id;
  return result;
}

CMapTable::CMapId CMapTable::NewCMapId(const CMapId& obj) {
  CMapId result;
  result.platform_id = obj.platform_id;
  result.encoding_id = obj.encoding_id;
  return result;
}

/******************************************************************************
 * CMapTable::CMapIterator class
 ******************************************************************************/
CMapTable::CMapIterator::CMapIterator(CMapTable* table,
                                      const CMapFilter* filter)
    : table_index_(0), filter_(filter), table_(table) {
}

bool CMapTable::CMapIterator::HasNext() {
  if (!filter_) {
    if (table_index_ < table_->NumCMaps()) {
      return true;
    }
    return false;
  }

  for (; table_index_ < table_->NumCMaps(); ++table_index_) {
    if (filter_->accept(table_->GetCMapId(table_index_))) {
      return true;
    }
  }
  return false;
}

CALLER_ATTACH CMapTable::CMap* CMapTable::CMapIterator::Next() {
  if (!HasNext()) {
#ifndef SFNTLY_NO_EXCEPTION
    throw NoSuchElementException();
#else
    return NULL;
#endif
  }
  CMapPtr next_cmap;
  next_cmap.Attach(table_->GetCMap(table_index_++));
  if (next_cmap == NULL) {
#ifndef SFNTLY_NO_EXCEPTION
    throw NoSuchElementException("Error during the creation of the CMap");
#else
    return NULL;
#endif
  }
  return next_cmap.Detach();
}

/******************************************************************************
 * CMapTable::CMapId class
 ******************************************************************************/

/******************************************************************************
 * CMapTable::CMapIdComparator class
 ******************************************************************************/

bool CMapTable::CMapIdComparator::operator()(const CMapId& lhs,
                                             const CMapId& rhs) const {
  return ((lhs.platform_id << 8 | lhs.encoding_id) >
      (rhs.platform_id << 8 | rhs.encoding_id));
}

/******************************************************************************
 * CMapTable::CMapIdFilter class
 ******************************************************************************/
CMapTable::CMapIdFilter::CMapIdFilter(const CMapId wanted_id)
    : wanted_id_(wanted_id),
      comparator_(NULL) {
}

CMapTable::CMapIdFilter::CMapIdFilter(const CMapId wanted_id,
                                      const CMapIdComparator* comparator)
    : wanted_id_(wanted_id),
      comparator_(comparator) {
}

bool CMapTable::CMapIdFilter::accept(const CMapId& cmap_id) const {
  if (!comparator_)
    return wanted_id_ == cmap_id;
  return (*comparator_)(wanted_id_, cmap_id);
}

/******************************************************************************
 * CMapTable::CMap class
 ******************************************************************************/
CMapTable::CMap::CMap(ReadableFontData* data, int32_t format,
                      const CMapId& cmap_id)
    : SubTable(data), format_(format), cmap_id_(cmap_id) {
}

CMapTable::CMap::~CMap() {
}

/******************************************************************************
 * CMapTable::CMap::Builder class
 ******************************************************************************/
CMapTable::CMap::Builder::~Builder() {
}

CALLER_ATTACH CMapTable::CMap::Builder*
    CMapTable::CMap::Builder::GetBuilder(ReadableFontData* data, int32_t offset,
                                         const CMapId& cmap_id) {
  // NOT IMPLEMENTED: Java enum value validation
  int32_t format = data->ReadUShort(offset);
  CMapBuilderPtr builder;
  switch (format) {
    case CMapFormat::kFormat0:
      builder.Attach(CMapFormat0::Builder::NewInstance(data, offset, cmap_id));
      break;
    case CMapFormat::kFormat2:
#if defined (SFNTLY_DEBUG_CMAP)
      fprintf(stderr, "Requesting Format2 builder, but it's unsupported; "
              "returning NULL\n");
#endif
      break;
    case CMapFormat::kFormat4:
      builder.Attach(CMapFormat4::Builder::NewInstance(data, offset, cmap_id));
      break;
    default:
#ifdef SFNTLY_DEBUG_CMAP
      fprintf(stderr, "Unknown builder format requested\n");
#endif
      break;
  }
  return builder.Detach();
}

CALLER_ATTACH CMapTable::CMap::Builder*
CMapTable::CMap::Builder::GetBuilder(int32_t format, const CMapId& cmap_id) {
  Ptr<CMapTable::CMap::Builder> builder;
  switch (format) {
    case CMapFormat::kFormat0:
      builder.Attach(CMapFormat0::Builder::NewInstance(cmap_id));
      break;
    case CMapFormat::kFormat2:
#if defined (SFNTLY_DEBUG_CMAP)
      fprintf(stderr, "Requesting Format2 builder, but it's unsupported; "
              "returning NULL\n");
#endif
      break;
    case CMapFormat::kFormat4:
      builder.Attach(CMapFormat4::Builder::NewInstance(cmap_id));
      break;
    default:
#ifdef SFNTLY_DEBUG_CMAP
      fprintf(stderr, "Unknown builder format requested\n");
#endif
      break;
  }
  return builder.Detach();
}

CMapTable::CMap::Builder::Builder(ReadableFontData* data,
                                  int32_t format,
                                  const CMapId& cmap_id)
    : SubTable::Builder(data),
      format_(format),
      cmap_id_(cmap_id),
      language_(0) {
}

CMapTable::CMap::Builder::Builder(WritableFontData* data,
                                  int32_t format,
                                  const CMapId& cmap_id)
    : SubTable::Builder(data),
      format_(format),
      cmap_id_(cmap_id),
      language_(0) {
}

int32_t CMapTable::CMap::Builder::SubSerialize(WritableFontData* new_data) {
  return InternalReadData()->CopyTo(new_data);
}

bool CMapTable::CMap::Builder::SubReadyToSerialize() {
  return true;
}

int32_t CMapTable::CMap::Builder::SubDataSizeToSerialize() {
  ReadableFontDataPtr read_data = InternalReadData();
  if (!read_data)
    return 0;
  return read_data->Length();
}

void CMapTable::CMap::Builder::SubDataSet() {
  // NOP
}

/******************************************************************************
 * CMapTable::CMapFormat0
 ******************************************************************************/
CMapTable::CMapFormat0::~CMapFormat0() {
}

int32_t CMapTable::CMapFormat0::Language() {
  return 0;
}

int32_t CMapTable::CMapFormat0::GlyphId(int32_t character) {
  if (character < 0 || character > 255) {
    return CMapTable::NOTDEF;
  }
  return data_->ReadUByte(character + Offset::kFormat0GlyphIdArray);
}

CMapTable::CMapFormat0::CMapFormat0(ReadableFontData* data,
                                    const CMapId& cmap_id)
    : CMap(data, CMapFormat::kFormat0, cmap_id) {
}

CMapTable::CMap::CharacterIterator* CMapTable::CMapFormat0::Iterator() {
  return new CMapTable::CMapFormat0::CharacterIterator(0, 0xff);
}


/******************************************************************************
 * CMapTable::CMapFormat0::CharacterIterator
 ******************************************************************************/
CMapTable::CMapFormat0::CharacterIterator::CharacterIterator(int32_t start,
                                                             int32_t end)
    : character_(start),
    max_character_(end) {
}

CMapTable::CMapFormat0::CharacterIterator::~CharacterIterator() {}

bool CMapTable::CMapFormat0::CharacterIterator::HasNext() {
  return character_ < max_character_;
}

int32_t CMapTable::CMapFormat0::CharacterIterator::Next() {
  if (HasNext())
    return character_++;
#ifndef SFNTLY_NO_EXCEPTION
  throw NoSuchElementException("No more characters to iterate.");
#endif
  return -1;
}

/******************************************************************************
 * CMapTable::CMapFormat0::Builder
 ******************************************************************************/
// static
CALLER_ATTACH CMapTable::CMapFormat0::Builder*
CMapTable::CMapFormat0::Builder::NewInstance(WritableFontData* data,
                                             int32_t offset,
                                             const CMapId& cmap_id) {
  WritableFontDataPtr wdata;
  if (data) {
    wdata.Attach(down_cast<WritableFontData*>(
        data->Slice(offset,
                    data->ReadUShort(offset + Offset::kFormat0Length))));
  }
  return new Builder(wdata, CMapFormat::kFormat0, cmap_id);
}

// static
CALLER_ATTACH CMapTable::CMapFormat0::Builder*
CMapTable::CMapFormat0::Builder::NewInstance(ReadableFontData* data,
                                             int32_t offset,
                                             const CMapId& cmap_id) {
  ReadableFontDataPtr rdata;
  if (data) {
    rdata.Attach(down_cast<ReadableFontData*>(
        data->Slice(offset,
                    data->ReadUShort(offset + Offset::kFormat0Length))));
  }
  return new Builder(rdata, CMapFormat::kFormat0, cmap_id);
}

// static
CALLER_ATTACH CMapTable::CMapFormat0::Builder*
CMapTable::CMapFormat0::Builder::NewInstance(const CMapId& cmap_id) {
  return new Builder(cmap_id);
}

// Always call NewInstance instead of the constructor for creating a new builder
// object! This refactoring avoids memory leaks when slicing the font data.
CMapTable::CMapFormat0::Builder::Builder(WritableFontData* data, int32_t offset,
                                         const CMapId& cmap_id)
    : CMapTable::CMap::Builder(data, CMapFormat::kFormat0, cmap_id) {
  UNREFERENCED_PARAMETER(offset);
}

CMapTable::CMapFormat0::Builder::Builder(
    ReadableFontData* data,
    int32_t offset,
    const CMapId& cmap_id)
    : CMapTable::CMap::Builder(data, CMapFormat::kFormat0, cmap_id) {
  UNREFERENCED_PARAMETER(offset);
}

CMapTable::CMapFormat0::Builder::Builder(const CMapId& cmap_id)
    : CMap::Builder(static_cast<ReadableFontData*>(NULL),
                    CMapFormat::kFormat0,
                    cmap_id) {
}

CMapTable::CMapFormat0::Builder::~Builder() {
}

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

/******************************************************************************
 * CMapTable::CMapFormat2
 ******************************************************************************/
CMapTable::CMapFormat2::~CMapFormat2() {
}

int32_t CMapTable::CMapFormat2::Language() {
  return 0;
}

int32_t CMapTable::CMapFormat2::GlyphId(int32_t character) {
  if (character > 0xffff) {
    return CMapTable::NOTDEF;
  }

  uint32_t c = ToBE32(character);
  byte_t high_byte = (c >> 8) & 0xff;
  byte_t low_byte = c & 0xff;
  int32_t offset = SubHeaderOffset(high_byte);

  if (offset == 0) {
    low_byte = high_byte;
    high_byte = 0;
  }

  int32_t first_code = FirstCode(high_byte);
  int32_t entry_count = EntryCount(high_byte);

  if (low_byte < first_code || low_byte >= first_code + entry_count) {
    return CMapTable::NOTDEF;
  }

  int32_t id_range_offset = IdRangeOffset(high_byte);

  // position of idRangeOffset + value of idRangeOffset + index for low byte
  // = firstcode
  int32_t p_location = (offset + Offset::kFormat2SubHeader_idRangeOffset) +
      id_range_offset +
      (low_byte - first_code) * DataSize::kUSHORT;
  int p = data_->ReadUShort(p_location);
  if (p == 0) {
    return CMapTable::NOTDEF;
  }

  if (offset == 0) {
    return p;
  }
  int id_delta = IdDelta(high_byte);
  return (p + id_delta) % 65536;
}

int32_t CMapTable::CMapFormat2::BytesConsumed(int32_t character) {
  uint32_t c = ToBE32(character);
  int32_t high_byte = (c >> 8) & 0xff;
  int32_t offset = SubHeaderOffset(high_byte);
  return (offset == 0) ? 1 : 2;
}

CMapTable::CMapFormat2::CMapFormat2(ReadableFontData* data,
                                    const CMapId& cmap_id)
    : CMap(data, CMapFormat::kFormat2, cmap_id) {
}

int32_t CMapTable::CMapFormat2::SubHeaderOffset(int32_t sub_header_index) {
  return data_->ReadUShort(Offset::kFormat2SubHeaderKeys +
                           sub_header_index * DataSize::kUSHORT);
}

int32_t CMapTable::CMapFormat2::FirstCode(int32_t sub_header_index) {
  int32_t sub_header_offset = SubHeaderOffset(sub_header_index);
  return data_->ReadUShort(sub_header_offset +
                           Offset::kFormat2SubHeaderKeys +
                           Offset::kFormat2SubHeader_firstCode);
}

int32_t CMapTable::CMapFormat2::EntryCount(int32_t sub_header_index) {
  int32_t sub_header_offset = SubHeaderOffset(sub_header_index);
  return data_->ReadUShort(sub_header_offset +
                           Offset::kFormat2SubHeaderKeys +
                           Offset::kFormat2SubHeader_entryCount);
}

int32_t CMapTable::CMapFormat2::IdRangeOffset(int32_t sub_header_index) {
  int32_t sub_header_offset = SubHeaderOffset(sub_header_index);
  return data_->ReadUShort(sub_header_offset +
                           Offset::kFormat2SubHeaderKeys +
                           Offset::kFormat2SubHeader_idRangeOffset);
}

int32_t CMapTable::CMapFormat2::IdDelta(int32_t sub_header_index) {
  int32_t sub_header_offset = SubHeaderOffset(sub_header_index);
  return data_->ReadUShort(sub_header_offset +
                           Offset::kFormat2SubHeaderKeys +
                           Offset::kFormat2SubHeader_idDelta);
}

CMapTable::CMap::CharacterIterator* CMapTable::CMapFormat2::Iterator() {
  // UNIMPLEMENTED
  return NULL;
}

/******************************************************************************
 * CMapTable::CMapFormat2::Builder
 ******************************************************************************/
CMapTable::CMapFormat2::Builder::Builder(WritableFontData* data,
                                         int32_t offset,
                                         const CMapId& cmap_id)
    : CMapTable::CMap::Builder(data ? down_cast<WritableFontData*>(
                                   data->Slice(offset, data->ReadUShort(
                                       offset + Offset::kFormat0Length)))
                               : static_cast<WritableFontData*>(NULL),
                               CMapFormat::kFormat2, cmap_id) {
  // TODO(arthurhsu): FIXIT: heavy lifting and leak, need fix.
}

CMapTable::CMapFormat2::Builder::Builder(ReadableFontData* data,
                                         int32_t offset,
                                         const CMapId& cmap_id)
    : CMapTable::CMap::Builder(data ? down_cast<ReadableFontData*>(
                                   data->Slice(offset, data->ReadUShort(
                                       offset + Offset::kFormat0Length)))
                               : static_cast<ReadableFontData*>(NULL),
                               CMapFormat::kFormat2, cmap_id) {
  // TODO(arthurhsu): FIXIT: heavy lifting and leak, need fix.
}

CMapTable::CMapFormat2::Builder::~Builder() {
}

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

/******************************************************************************
 * CMapTable::CMapFormat4
 ******************************************************************************/
CMapTable::CMapFormat4::CMapFormat4(ReadableFontData* data,
                                    const CMapId& cmap_id)
    : CMap(data, CMapFormat::kFormat4, cmap_id),
      seg_count_(SegCount(data)),
      start_code_offset_(StartCodeOffset(seg_count_)),
      id_delta_offset_(IdDeltaOffset(seg_count_)),
      glyph_id_array_offset_(GlyphIdArrayOffset(seg_count_)) {
}

CMapTable::CMapFormat4::~CMapFormat4() {
}

int32_t CMapTable::CMapFormat4::GlyphId(int32_t character) {
  int32_t segment = data_->SearchUShort(StartCodeOffset(seg_count_),
                                        DataSize::kUSHORT,
                                        Offset::kFormat4EndCount,
                                        DataSize::kUSHORT,
                                        seg_count_,
                                        character);
  if (segment == -1) {
    return CMapTable::NOTDEF;
  }
  int32_t start_code = StartCode(segment);
  return RetrieveGlyphId(segment, start_code, character);
}

int32_t CMapTable::CMapFormat4::RetrieveGlyphId(int32_t segment,
                                                int32_t start_code,
                                                int32_t character) {
  if (character < start_code) {
    return CMapTable::NOTDEF;
  }
  int32_t id_range_offset = IdRangeOffset(segment);
  if (id_range_offset == 0) {
    return (character + IdDelta(segment)) % 65536;
  }
  return data_->ReadUShort(id_range_offset +
                           IdRangeOffsetLocation(segment) +
                           2 * (character - start_code));
}

int32_t CMapTable::CMapFormat4::seg_count() {
  return seg_count_;
}

int32_t CMapTable::CMapFormat4::Length() {
  return Length(data_);
}

int32_t CMapTable::CMapFormat4::StartCode(int32_t segment) {
  if (!IsValidIndex(segment)) {
    return -1;
  }
  return StartCode(data_.p_, seg_count_, segment);
}

// static
int32_t CMapTable::CMapFormat4::Language(ReadableFontData* data) {
  int32_t language = data->ReadUShort(Offset::kFormat4Language);
  return language;
}

// static
int32_t CMapTable::CMapFormat4::Length(ReadableFontData* data) {
  int32_t length = data->ReadUShort(Offset::kFormat4Length);
  return length;
}

// static
int32_t CMapTable::CMapFormat4::SegCount(ReadableFontData* data) {
  int32_t seg_count = data->ReadUShort(Offset::kFormat4SegCountX2) / 2;
  return seg_count;
}

// static
int32_t CMapTable::CMapFormat4::StartCode(ReadableFontData* data,
                                          int32_t seg_count,
                                          int32_t index) {
  int32_t start_code = data->ReadUShort(StartCodeOffset(seg_count) +
                                        index * DataSize::kUSHORT);
  return start_code;
}

// static
int32_t CMapTable::CMapFormat4::StartCodeOffset(int32_t seg_count) {
  int32_t start_code_offset = Offset::kFormat4EndCount +
      (seg_count + 1) * DataSize::kUSHORT;
  return start_code_offset;
}

// static
int32_t CMapTable::CMapFormat4::EndCode(ReadableFontData* data,
                                        int32_t seg_count,
                                        int32_t index) {
  UNREFERENCED_PARAMETER(seg_count);
  int32_t end_code = data->ReadUShort(Offset::kFormat4EndCount +
                                      index * DataSize::kUSHORT);
  return end_code;
}

// static
int32_t CMapTable::CMapFormat4::IdDelta(ReadableFontData* data,
                                        int32_t seg_count,
                                        int32_t index) {
  int32_t id_delta = data->ReadUShort(IdDeltaOffset(seg_count) +
                                      index * DataSize::kUSHORT);
  return id_delta;
}

// static
int32_t CMapTable::CMapFormat4::IdDeltaOffset(int32_t seg_count) {
  int32_t id_delta_offset =
      Offset::kFormat4EndCount + (2 * seg_count + 1) * DataSize::kUSHORT;
  return id_delta_offset;
}

// static
int32_t CMapTable::CMapFormat4::IdRangeOffset(ReadableFontData* data,
                                              int32_t seg_count,
                                              int32_t index) {
  int32_t id_range_offset =
      data->ReadUShort(IdRangeOffsetOffset(seg_count)
                       + index * DataSize::kUSHORT);
  return id_range_offset;
}

// static
int32_t CMapTable::CMapFormat4::IdRangeOffsetOffset(int32_t seg_count) {
  int32_t id_range_offset_offset =
      Offset::kFormat4EndCount + (2 * seg_count + 1) * DataSize::kUSHORT +
      seg_count * DataSize::kSHORT;
  return id_range_offset_offset;
}

// static
int32_t CMapTable::CMapFormat4::GlyphIdArrayOffset(int32_t seg_count) {
  int32_t glyph_id_array_offset =
      Offset::kFormat4EndCount + (3 * seg_count + 1) * DataSize::kUSHORT +
      seg_count * DataSize::kSHORT;
  return glyph_id_array_offset;
}

int32_t CMapTable::CMapFormat4::EndCode(int32_t segment) {
  if (IsValidIndex(segment)) {
    return EndCode(data_, seg_count_, segment);
  }
#if defined (SFNTLY_NO_EXCEPTION)
  return -1;
#else
  throw IllegalArgumentException();
#endif
}

bool CMapTable::CMapFormat4::IsValidIndex(int32_t segment) {
  if (segment < 0 || segment >= seg_count_) {
#if defined (SFNTLY_NO_EXCEPTION)
    return false;
#else
    throw IllegalArgumentException();
#endif
  }
  return true;
}

int32_t CMapTable::CMapFormat4::IdDelta(int32_t segment) {
  if (IsValidIndex(segment))
    return IdDelta(data_, seg_count_, segment);
  return -1;
}

int32_t CMapTable::CMapFormat4::IdRangeOffset(int32_t segment) {
  if (IsValidIndex(segment))
    return data_->ReadUShort(IdRangeOffsetLocation(segment));
  return -1;
}

int32_t CMapTable::CMapFormat4::IdRangeOffsetLocation(int32_t segment) {
  if (IsValidIndex(segment))
    return IdRangeOffsetOffset(seg_count_) + segment * DataSize::kUSHORT;
  return -1;
}

int32_t CMapTable::CMapFormat4::GlyphIdArray(int32_t index) {
  return data_->ReadUShort(glyph_id_array_offset_ + index * DataSize::kUSHORT);
}

int32_t CMapTable::CMapFormat4::Language() {
  return Language(data_);
}


CMapTable::CMap::CharacterIterator* CMapTable::CMapFormat4::Iterator() {
  return new CharacterIterator(this);
}

/******************************************************************************
 * CMapTable::CMapFormat4::CharacterIterator class
 ******************************************************************************/
CMapTable::CMapFormat4::CharacterIterator::CharacterIterator(
    CMapFormat4* parent)
    : parent_(parent),
      segment_index_(0),
      first_char_in_segment_(-1),
      last_char_in_segment_(-1),
      next_char_(-1),
      next_char_set_(false) {
}

bool CMapTable::CMapFormat4::CharacterIterator::HasNext() {
  if (next_char_set_)
    return true;
  while (segment_index_ < parent_->seg_count_) {
    if (first_char_in_segment_ < 0) {
      first_char_in_segment_ = parent_->StartCode(segment_index_);
      last_char_in_segment_ = parent_->EndCode(segment_index_);
      next_char_ = first_char_in_segment_;
      next_char_set_ = true;
      return true;
    }
    if (next_char_ < last_char_in_segment_) {
      next_char_++;
      next_char_set_ = true;
      return true;
    }
    segment_index_++;
    first_char_in_segment_ = -1;
  }
  return false;
}

int32_t CMapTable::CMapFormat4::CharacterIterator::Next() {
  if (!next_char_set_) {
    if (!HasNext()) {
#if defined (SFNTLY_NO_EXCEPTION)
      return -1;
#else
      throw NoSuchElementException("No more characters to iterate.");
#endif
    }
  }
  next_char_set_ = false;
  return next_char_;
}

/******************************************************************************
 * CMapTable::CMapFormat4::Builder::Segment class
 ******************************************************************************/
CMapTable::CMapFormat4::Builder::Segment::Segment() {}

CMapTable::CMapFormat4::Builder::Segment::Segment(Segment* other)
    : start_count_(other->start_count_),
      end_count_(other->end_count_),
      id_delta_(other->id_delta_),
      id_range_offset_(other->id_range_offset_) {
}

CMapTable::CMapFormat4::Builder::Segment::Segment(int32_t start_count,
                                                  int32_t end_count,
                                                  int32_t id_delta,
                                                  int32_t id_range_offset)
    : start_count_(start_count),
      end_count_(end_count),
      id_delta_(id_delta),
      id_range_offset_(id_range_offset) {
}

CMapTable::CMapFormat4::Builder::Segment::~Segment() {}

int32_t CMapTable::CMapFormat4::Builder::Segment::start_count() {
  return start_count_;
}

void
CMapTable::CMapFormat4::Builder::Segment::set_start_count(int32_t start_count) {
  start_count_ = start_count;
}

int32_t CMapTable::CMapFormat4::Builder::Segment::end_count() {
  return end_count_;
}

void
CMapTable::CMapFormat4::Builder::Segment::set_end_count(int32_t end_count) {
  end_count_ = end_count;
}

int32_t CMapTable::CMapFormat4::Builder::Segment::id_delta() {
  return id_delta_;
}

void
CMapTable::CMapFormat4::Builder::Segment::set_id_delta(int32_t id_delta) {
  id_delta_ = id_delta;
}

int32_t CMapTable::CMapFormat4::Builder::Segment::id_range_offset() {
  return id_range_offset_;
}

void
CMapTable::CMapFormat4::Builder::Segment::
set_id_range_offset(int32_t id_range_offset) {
  id_range_offset_ = id_range_offset;
}

// static
CALLER_ATTACH SegmentList*
CMapTable::CMapFormat4::Builder::Segment::DeepCopy(SegmentList* original) {
  SegmentList* list = new SegmentList;
  for (SegmentList::iterator it = original->begin(),
           e = original->end(); it != e; ++it) {
    list->push_back(*it);
  }
  return list;
}

/******************************************************************************
 * CMapTable::CMapFormat4::Builder class
 ******************************************************************************/
CALLER_ATTACH CMapTable::CMapFormat4::Builder*
CMapTable::CMapFormat4::Builder::NewInstance(ReadableFontData* data,
                                             int32_t offset,
                                             const CMapId& cmap_id) {
  ReadableFontDataPtr rdata;
  if (data) {
    rdata.Attach
        (down_cast<ReadableFontData*>
         (data->Slice(offset,
                      data->ReadUShort(offset + Offset::kFormat4Length))));
  }
  return new Builder(rdata, CMapFormat::kFormat4, cmap_id);
}

CALLER_ATTACH CMapTable::CMapFormat4::Builder*
CMapTable::CMapFormat4::Builder::NewInstance(WritableFontData* data,
                                             int32_t offset,
                                             const CMapId& cmap_id) {
  WritableFontDataPtr wdata;
  if (data) {
    wdata.Attach
        (down_cast<WritableFontData*>
         (data->Slice(offset,
                      data->ReadUShort(offset + Offset::kFormat4Length))));
  }
  return new Builder(wdata, CMapFormat::kFormat4, cmap_id);
}

CALLER_ATTACH CMapTable::CMapFormat4::Builder*
CMapTable::CMapFormat4::Builder::NewInstance(const CMapId& cmap_id) {
  return new Builder(cmap_id);
}

CMapTable::CMapFormat4::Builder::Builder(ReadableFontData* data, int32_t offset,
                                         const CMapId& cmap_id)
    : CMap::Builder(data, CMapFormat::kFormat4, cmap_id) {
  UNREFERENCED_PARAMETER(offset);
}

CMapTable::CMapFormat4::Builder::Builder(WritableFontData* data, int32_t offset,
                                         const CMapId& cmap_id)
    : CMap::Builder(data, CMapFormat::kFormat4, cmap_id) {
  UNREFERENCED_PARAMETER(offset);
}

CMapTable::CMapFormat4::Builder::Builder(SegmentList* segments,
                                         IntegerList* glyph_id_array,
                                         const CMapId& cmap_id)
    : CMap::Builder(static_cast<ReadableFontData*>(NULL),
                    CMapFormat::kFormat4, cmap_id),
      segments_(segments->begin(), segments->end()),
      glyph_id_array_(glyph_id_array->begin(), glyph_id_array->end()) {
  set_model_changed();
}

CMapTable::CMapFormat4::Builder::Builder(const CMapId& cmap_id)
    : CMap::Builder(static_cast<ReadableFontData*>(NULL),
                    CMapFormat::kFormat4, cmap_id) {
}

CMapTable::CMapFormat4::Builder::~Builder() {}

void CMapTable::CMapFormat4::Builder::Initialize(ReadableFontData* data) {
  if (data == NULL || data->Length() == 0)
    return;

  // build segments
  int32_t seg_count = CMapFormat4::SegCount(data);
  for (int32_t index = 0; index < seg_count; ++index) {
    Ptr<Segment> segment = new Segment;
    segment->set_start_count(CMapFormat4::StartCode(data, seg_count, index));
#if defined SFNTLY_DEBUG_CMAP
    fprintf(stderr, "Segment %d; start %d\n", index, segment->start_count());
#endif
    segment->set_end_count(CMapFormat4::EndCode(data, seg_count, index));
    segment->set_id_delta(CMapFormat4::IdDelta(data, seg_count, index));
    segment->set_id_range_offset(CMapFormat4::IdRangeOffset(data,
                                                           seg_count,
                                                           index));
    segments_.push_back(segment);
  }

  // build glyph id array
  int32_t glyph_id_array_offset = CMapFormat4::GlyphIdArrayOffset(seg_count);
  int32_t glyph_id_array_length =
      (CMapFormat4::Length(data) - glyph_id_array_offset)
      / DataSize::kUSHORT;
  fprintf(stderr, "id array size %d\n", glyph_id_array_length);
  for (int32_t i = 0; i < glyph_id_array_length; i += DataSize::kUSHORT) {
    glyph_id_array_.push_back(data->ReadUShort(glyph_id_array_offset + i));
  }
}

SegmentList* CMapTable::CMapFormat4::Builder::segments() {
  if (segments_.empty()) {
    Initialize(InternalReadData());
    set_model_changed();
  }
  return &segments_;
}

void CMapTable::CMapFormat4::Builder::set_segments(SegmentList* segments) {
  segments_.assign(segments->begin(), segments->end());
  set_model_changed();
}

IntegerList* CMapTable::CMapFormat4::Builder::glyph_id_array() {
  if (glyph_id_array_.empty()) {
    Initialize(InternalReadData());
    set_model_changed();
  }
  return &glyph_id_array_;
}

void CMapTable::CMapFormat4::Builder::
set_glyph_id_array(IntegerList* glyph_id_array) {
  glyph_id_array_.assign(glyph_id_array->begin(), glyph_id_array->end());
  set_model_changed();
}

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

void CMapTable::CMapFormat4::Builder::SubDataSet() {
  segments_.clear();
  glyph_id_array_.clear();
  set_model_changed();
}

int32_t CMapTable::CMapFormat4::Builder::SubDataSizeToSerialize() {
  if (!model_changed()) {
    return CMap::Builder::SubDataSizeToSerialize();
  }
  int32_t size = Offset::kFormat4FixedSize + segments_.size()
      * (3 * DataSize::kUSHORT + DataSize::kSHORT)
      + glyph_id_array_.size() * DataSize::kSHORT;
  return size;
}

bool CMapTable::CMapFormat4::Builder::SubReadyToSerialize() {
  if (!model_changed()) {
    return CMap::Builder::SubReadyToSerialize();
  }
  if (!segments()->empty()) {
    return true;
  }
  return false;
}

int32_t
CMapTable::CMapFormat4::Builder::SubSerialize(WritableFontData* new_data) {
  if (!model_changed()) {
    return CMap::Builder::SubSerialize(new_data);
  }
  int32_t index = 0;
  index += new_data->WriteUShort(index, CMapFormat::kFormat4);
  index += DataSize::kUSHORT;  // length - write this at the end
  index += new_data->WriteUShort(index, language());

  int32_t seg_count = segments_.size();
  index += new_data->WriteUShort(index, seg_count * 2);
  int32_t log2_seg_count = FontMath::Log2(seg_count);
  int32_t search_range = 1 << (log2_seg_count + 1);
  index += new_data->WriteUShort(index, search_range);
  int32_t entry_selector = log2_seg_count;
  index += new_data->WriteUShort(index, entry_selector);
  int32_t range_shift = 2 * seg_count - search_range;
  index += new_data->WriteUShort(index, range_shift);

  for (int32_t i = 0; i < seg_count; ++i) {
    index += new_data->WriteUShort(index, segments_[i]->end_count());
  }
  index += new_data->WriteUShort(index, 0);  // reserved ushort
  for (int32_t i = 0; i < seg_count; ++i) {
#if defined SFNTLY_DEBUG_CMAP
    fprintf(stderr, "Segment %d; start %d\n", i, segments_[i]->start_count());
#endif
    index += new_data->WriteUShort(index, segments_[i]->start_count());
  }
  for (int32_t i = 0; i < seg_count; ++i) {
    index += new_data->WriteShort(index, segments_[i]->id_delta());
  }
  for (int32_t i = 0; i < seg_count; ++i) {
    index += new_data->WriteUShort(index, segments_[i]->id_range_offset());
  }

#if defined SFNTLY_DEBUG_CMAP
  fprintf(stderr, "Glyph id array size %lu\n", glyph_id_array_.size());
#endif
  for (size_t i = 0; i < glyph_id_array_.size(); ++i) {
    index += new_data->WriteUShort(index, glyph_id_array_[i]);
  }

  new_data->WriteUShort(Offset::kFormat4Length, index);
  return index;
}

/******************************************************************************
 * CMapTable::Builder class
 ******************************************************************************/
CMapTable::Builder::Builder(Header* header, WritableFontData* data)
    : SubTableContainerTable::Builder(header, data), version_(0) {
}

CMapTable::Builder::Builder(Header* header, ReadableFontData* data)
    : SubTableContainerTable::Builder(header, data), version_(0) {
}

CMapTable::Builder::~Builder() {
}

int32_t CMapTable::Builder::SubSerialize(WritableFontData* new_data) {
  int32_t size = new_data->WriteUShort(CMapTable::Offset::kVersion,
                                       version_);
  size += new_data->WriteUShort(CMapTable::Offset::kNumTables,
                                GetCMapBuilders()->size());

  int32_t index_offset = size;
  size += GetCMapBuilders()->size() * CMapTable::Offset::kEncodingRecordSize;
  for (CMapBuilderMap::iterator it = GetCMapBuilders()->begin(),
           e = GetCMapBuilders()->end(); it != e; ++it) {
    CMapBuilderPtr b = it->second;
    // header entry
    index_offset += new_data->WriteUShort(index_offset, b->platform_id());
    index_offset += new_data->WriteUShort(index_offset, b->encoding_id());
    index_offset += new_data->WriteULong(index_offset, size);

    // cmap
    FontDataPtr slice;
    slice.Attach(new_data->Slice(size));
    size += b->SubSerialize(down_cast<WritableFontData*>(slice.p_));
  }
  return size;
}

bool CMapTable::Builder::SubReadyToSerialize() {
  if (GetCMapBuilders()->empty())
    return false;

  // check each table
  for (CMapBuilderMap::iterator it = GetCMapBuilders()->begin(),
           e = GetCMapBuilders()->end(); it != e; ++it) {
    if (!it->second->SubReadyToSerialize())
      return false;
  }
  return true;
}

int32_t CMapTable::Builder::SubDataSizeToSerialize() {
  if (GetCMapBuilders()->empty())
    return 0;

  bool variable = false;
  int32_t size = CMapTable::Offset::kEncodingRecordStart +
      GetCMapBuilders()->size() * CMapTable::Offset::kEncodingRecordSize;

  // calculate size of each table
  for (CMapBuilderMap::iterator it = GetCMapBuilders()->begin(),
           e = GetCMapBuilders()->end(); it != e; ++it) {
    int32_t cmap_size = it->second->SubDataSizeToSerialize();
    size += abs(cmap_size);
    variable |= cmap_size <= 0;
  }
  return variable ? -size : size;
}

void CMapTable::Builder::SubDataSet() {
  GetCMapBuilders()->clear();
  Table::Builder::set_model_changed();
}

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

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

// static
CALLER_ATTACH CMapTable::CMap::Builder*
    CMapTable::Builder::CMapBuilder(ReadableFontData* data, int32_t index) {
  if (index < 0 || index > NumCMaps(data)) {
#if !defined (SFNTLY_NO_EXCEPTION)
    throw IndexOutOfBoundException(
              "CMap table is outside of the bounds of the known tables.");
#endif
    return NULL;
  }

  int32_t platform_id = data->ReadUShort(Offset::kEncodingRecordPlatformId +
                                         OffsetForEncodingRecord(index));
  int32_t encoding_id = data->ReadUShort(Offset::kEncodingRecordEncodingId +
                                         OffsetForEncodingRecord(index));
  int32_t offset = data->ReadULongAsInt(Offset::kEncodingRecordOffset +
                                        OffsetForEncodingRecord(index));
  return CMap::Builder::GetBuilder(data, offset,
                                   NewCMapId(platform_id, encoding_id));
}

// static
int32_t CMapTable::Builder::NumCMaps(ReadableFontData* data) {
  if (data == NULL) {
    return 0;
  }
  return data->ReadUShort(Offset::kNumTables);
}

int32_t CMapTable::Builder::NumCMaps() {
  return GetCMapBuilders()->size();
}

void CMapTable::Builder::Initialize(ReadableFontData* data) {
  int32_t num_cmaps = NumCMaps(data);
  for (int32_t i = 0; i < num_cmaps; ++i) {
    CMapTable::CMap::Builder* cmap_builder = CMapBuilder(data, i);
    if (!cmap_builder)
      continue;
    cmap_builders_[cmap_builder->cmap_id()] = cmap_builder;
  }
}

CMapTable::CMap::Builder* CMapTable::Builder::NewCMapBuilder(
    const CMapId& cmap_id,
    ReadableFontData* data) {
  Ptr<WritableFontData> wfd;
  wfd.Attach(WritableFontData::CreateWritableFontData(data->Size()));
  data->CopyTo(wfd.p_);
  CMapTable::CMapBuilderPtr builder;
  builder.Attach(CMap::Builder::GetBuilder(wfd.p_, 0, cmap_id));
  CMapBuilderMap* cmap_builders = CMapTable::Builder::GetCMapBuilders();
  cmap_builders->insert(std::make_pair(cmap_id, builder.p_));
  return builder.Detach();
}

CMapTable::CMap::Builder*
CMapTable::Builder::NewCMapBuilder(int32_t format, const CMapId& cmap_id) {
  Ptr<CMapTable::CMap::Builder> cmap_builder;
  cmap_builder.Attach(CMap::Builder::GetBuilder(format, cmap_id));
  CMapBuilderMap* cmap_builders = CMapTable::Builder::GetCMapBuilders();
  cmap_builders->insert(std::make_pair(cmap_id, cmap_builder.p_));
  return cmap_builder.Detach();
}

CMapTable::CMap::Builder*
CMapTable::Builder::CMapBuilder(const CMapId& cmap_id) {
  CMapBuilderMap* cmap_builders = this->GetCMapBuilders();
  CMapBuilderMap::iterator builder = cmap_builders->find(cmap_id);
  if (builder != cmap_builders->end())
    return builder->second;
#ifndef SFNTLY_NO_EXCEPTION
  throw NoSuchElementException("No builder found for cmap_id");
#else
  return NULL;
#endif
}

CMapTable::CMapBuilderMap* CMapTable::Builder::GetCMapBuilders() {
  if (cmap_builders_.empty()) {
    Initialize(InternalReadData());
    set_model_changed();
  }
  return &cmap_builders_;
}

}  // namespace sfntly
