// Copyright (c) 2018 The OTS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "layout.h"

#include "fvar.h"

// OpenType Variations Common Table Formats

#define TABLE_NAME "Variations" // XXX: use individual table names

namespace {

bool ParseVariationRegionList(const ots::Font* font, const uint8_t* data, const size_t length,
                              uint16_t* regionCount) {
  ots::Buffer subtable(data, length);

  uint16_t axisCount;

  if (!subtable.ReadU16(&axisCount) ||
      !subtable.ReadU16(regionCount)) {
    return OTS_FAILURE_MSG("Failed to read variation region list header");
  }

  if (*regionCount == 0) {
    return true;
  }

  const ots::OpenTypeFVAR* fvar =
    static_cast<ots::OpenTypeFVAR*>(font->GetTypedTable(OTS_TAG_FVAR));
  if (!fvar) {
    return OTS_FAILURE_MSG("Required fvar table is missing");
  }
  if (axisCount != fvar->AxisCount()) {
    return OTS_FAILURE_MSG("Axis count mismatch");
  }

  for (unsigned i = 0; i < *regionCount; i++) {
    for (unsigned j = 0; j < axisCount; j++) {
      int16_t startCoord, peakCoord, endCoord;
      if (!subtable.ReadS16(&startCoord) ||
          !subtable.ReadS16(&peakCoord) ||
          !subtable.ReadS16(&endCoord)) {
        return OTS_FAILURE_MSG("Failed to read region axis coordinates");
      }
      if (startCoord > peakCoord || peakCoord > endCoord) {
        return OTS_FAILURE_MSG("Region axis coordinates out of order");
      }
      if (startCoord < -0x4000 || endCoord > 0x4000) {
        return OTS_FAILURE_MSG("Region axis coordinate out of range");
      }
      if ((peakCoord < 0 && endCoord > 0) ||
          (peakCoord > 0 && startCoord < 0)) {
        return OTS_FAILURE_MSG("Invalid region axis coordinates");
      }
    }
  }

  return true;
}

bool
ParseVariationDataSubtable(const ots::Font* font, const uint8_t* data, const size_t length,
                           const uint16_t regionCount,
                           uint16_t* regionIndexCount) {
  ots::Buffer subtable(data, length);

  uint16_t itemCount;
  uint16_t wordDeltaCount;

  const uint16_t LONG_WORDS	= 0x8000u;
  const uint16_t WORD_DELTA_COUNT_MASK = 0x7FFF;

  if (!subtable.ReadU16(&itemCount) ||
      !subtable.ReadU16(&wordDeltaCount) ||
      !subtable.ReadU16(regionIndexCount)) {
    return OTS_FAILURE_MSG("Failed to read variation data subtable header");
  }

  size_t valueSize = (wordDeltaCount & LONG_WORDS) ? 2 : 1;
  wordDeltaCount &= WORD_DELTA_COUNT_MASK;

  if (wordDeltaCount > *regionIndexCount) {
    return OTS_FAILURE_MSG("Bad word delta count");
  }

  for (unsigned i = 0; i < *regionIndexCount; i++) {
    uint16_t regionIndex;
    if (!subtable.ReadU16(&regionIndex) || regionIndex >= regionCount) {
      return OTS_FAILURE_MSG("Bad region index");
    }
  }

  if (!subtable.Skip(valueSize * size_t(itemCount) * (size_t(wordDeltaCount) + size_t(*regionIndexCount)))) {
    return OTS_FAILURE_MSG("Failed to read delta data");
  }

  return true;
}

} // namespace

namespace ots {

bool
ParseItemVariationStore(const Font* font,
                        const uint8_t* data, const size_t length,
                        std::vector<uint16_t>* regionIndexCounts) {
  Buffer subtable(data, length);

  uint16_t format;
  uint32_t variationRegionListOffset;
  uint16_t itemVariationDataCount;

  if (!subtable.ReadU16(&format) ||
      !subtable.ReadU32(&variationRegionListOffset) ||
      !subtable.ReadU16(&itemVariationDataCount)) {
    return OTS_FAILURE_MSG("Failed to read item variation store header");
  }

  if (format != 1) {
    return OTS_FAILURE_MSG("Unknown item variation store format");
  }

  if (variationRegionListOffset < subtable.offset() + 4 * itemVariationDataCount ||
      variationRegionListOffset > length) {
    return OTS_FAILURE_MSG("Invalid variation region list offset");
  }

  uint16_t regionCount;
  if (!ParseVariationRegionList(font,
                                data + variationRegionListOffset,
                                length - variationRegionListOffset,
                                &regionCount)) {
    return OTS_FAILURE_MSG("Failed to parse variation region list");
  }

  for (unsigned i = 0; i < itemVariationDataCount; i++) {
    uint32_t offset;
    if (!subtable.ReadU32(&offset)) {
      return OTS_FAILURE_MSG("Failed to read variation data subtable offset");
    }
    if (offset >= length) {
      return OTS_FAILURE_MSG("Bad offset to variation data subtable");
    }
    uint16_t regionIndexCount = 0;
    if (!ParseVariationDataSubtable(font, data + offset, length - offset,
                                    regionCount,
                                    &regionIndexCount)) {
      return OTS_FAILURE_MSG("Failed to parse variation data subtable");
    }
    if (regionIndexCounts) {
      regionIndexCounts->push_back(regionIndexCount);
    }
  }

  return true;
}

bool ParseDeltaSetIndexMap(const Font* font, const uint8_t* data, const size_t length) {
  Buffer subtable(data, length);

  uint16_t entryFormat;
  uint16_t mapCount;

  if (!subtable.ReadU16(&entryFormat) ||
      !subtable.ReadU16(&mapCount)) {
    return OTS_FAILURE_MSG("Failed to read delta set index map header");
  }

  const uint16_t MAP_ENTRY_SIZE_MASK = 0x0030;

  const uint16_t entrySize = (((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1);
  if (!subtable.Skip(entrySize * mapCount)) {
    return OTS_FAILURE_MSG("Failed to read delta set index map data");
  }

  return true;
}

bool ParseVariationData(const Font* font, const uint8_t* data, size_t length,
                        size_t axisCount, size_t sharedTupleCount) {
  Buffer subtable(data, length);

  uint16_t tupleVariationCount;
  uint16_t dataOffset;
  if (!subtable.ReadU16(&tupleVariationCount) ||
      !subtable.ReadU16(&dataOffset)) {
    return OTS_FAILURE_MSG("Failed to read variation data header");
  }

  if (dataOffset > length) {
    return OTS_FAILURE_MSG("Invalid serialized data offset");
  }

  tupleVariationCount &= 0x0FFF; // mask off flags

  const uint16_t EMBEDDED_PEAK_TUPLE = 0x8000;
  const uint16_t INTERMEDIATE_REGION = 0x4000;
  const uint16_t TUPLE_INDEX_MASK    = 0x0FFF;

  for (unsigned i = 0; i < tupleVariationCount; i++) {
    uint16_t variationDataSize;
    uint16_t tupleIndex;

    if (!subtable.ReadU16(&variationDataSize) ||
        !subtable.ReadU16(&tupleIndex)) {
      return OTS_FAILURE_MSG("Failed to read tuple variation header");
    }

    if (tupleIndex & EMBEDDED_PEAK_TUPLE) {
      for (unsigned axis = 0; axis < axisCount; axis++) {
        int16_t coordinate;
        if (!subtable.ReadS16(&coordinate)) {
          return OTS_FAILURE_MSG("Failed to read tuple coordinate");
        }
        if (coordinate < -0x4000 || coordinate > 0x4000) {
          return OTS_FAILURE_MSG("Tuple coordinate not in the range [-1.0, 1.0]: %g", coordinate / 16384.);
        }
      }
    }

    if (tupleIndex & INTERMEDIATE_REGION) {
      std::vector<int16_t> startTuple;
      startTuple.reserve(axisCount);
      for (unsigned axis = 0; axis < axisCount; axis++) {
        int16_t coordinate;
        if (!subtable.ReadS16(&coordinate)) {
          return OTS_FAILURE_MSG("Failed to read tuple coordinate");
        }
        if (coordinate < -0x4000 || coordinate > 0x4000) {
          return OTS_FAILURE_MSG("Tuple coordinate not in the range [-1.0, 1.0]: %g", coordinate / 16384.);
        }
        startTuple.push_back(coordinate);
      }

      std::vector<int16_t> endTuple;
      endTuple.reserve(axisCount);
      for (unsigned axis = 0; axis < axisCount; axis++) {
        int16_t coordinate;
        if (!subtable.ReadS16(&coordinate)) {
          return OTS_FAILURE_MSG("Failed to read tuple coordinate");
        }
        if (coordinate < -0x4000 || coordinate > 0x4000) {
          return OTS_FAILURE_MSG("Tuple coordinate not in the range [-1.0, 1.0]: %g", coordinate / 16384.);
        }
        endTuple.push_back(coordinate);
      }

      for (unsigned axis = 0; axis < axisCount; axis++) {
        if (startTuple[axis] > endTuple[axis]) {
          return OTS_FAILURE_MSG("Invalid intermediate range");
        }
      }
    }

    if (!(tupleIndex & EMBEDDED_PEAK_TUPLE)) {
      tupleIndex &= TUPLE_INDEX_MASK;
      if (tupleIndex >= sharedTupleCount) {
        return OTS_FAILURE_MSG("Tuple index out of range");
      }
    }
  }

  // TODO: we don't attempt to interpret the serialized data block

  return true;
}

} // namespace ots

#undef TABLE_NAME
