// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "schema.h"
#include "message.h"
#include <kj/debug.h>

namespace capnp {

namespace schema {
  uint KJ_HASHCODE(Type::Which w) { return kj::hashCode(static_cast<uint16_t>(w)); }
  // TODO(cleanup): Cap'n Proto does not declare stringifiers nor hashers for `Which` enums, unlike
  //   all other enums. Fix that and remove this.
}

namespace _ {  // private

// Null schemas generated using the below schema file with:
//
//     capnp eval -Isrc null-schemas.capnp node --flat |
//         hexdump -v -e '8/1 "0x%02x, "' -e '1/8 "\n"'; echo
//
// I totally don't understand hexdump format strings and came up with this command based on trial
// and error.
//
//     @0x879863d4b2cc4a1e;
//
//     using Node = import "/capnp/schema.capnp".Node;
//
//     const node :Node = (
//         id = 0x0000000000000000,
//         displayName = "(null schema)");
//
//     const struct :Node = (
//         id = 0x0000000000000001,
//         displayName = "(null struct schema)",
//         struct = (
//             dataWordCount = 0,
//             pointerCount = 0,
//             preferredListEncoding = empty));
//
//     const enum :Node = (
//         id = 0x0000000000000002,
//         displayName = "(null enum schema)",
//         enum = ());
//
//     const interface :Node = (
//         id = 0x0000000000000003,
//         displayName = "(null interface schema)",
//         interface = ());
//
//     const const :Node = (
//         id = 0x0000000000000004,
//         displayName = "(null const schema)",
//         const = (type = (void = void), value = (void = void)));

static const AlignedData<13> NULL_SCHEMA_BYTES = {{
  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,   // union discriminant intentionally mangled
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x11, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x20, 0x73, 0x63,
  0x68, 0x65, 0x6d, 0x61, 0x29, 0x00, 0x00, 0x00,
}};
const RawSchema NULL_SCHEMA = {
  0x0000000000000000, NULL_SCHEMA_BYTES.words, 13,
  nullptr, nullptr, 0, 0, nullptr, nullptr, nullptr,
  { &NULL_SCHEMA, nullptr, nullptr, 0, 0, nullptr }
};

static const AlignedData<14> NULL_STRUCT_SCHEMA_BYTES = {{
  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x11, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x20, 0x73, 0x74,
  0x72, 0x75, 0x63, 0x74, 0x20, 0x73, 0x63, 0x68,
  0x65, 0x6d, 0x61, 0x29, 0x00, 0x00, 0x00, 0x00,
}};
const RawSchema NULL_STRUCT_SCHEMA = {
  0x0000000000000001, NULL_STRUCT_SCHEMA_BYTES.words, 14,
  nullptr, nullptr, 0, 0, nullptr, nullptr, nullptr,
  { &NULL_STRUCT_SCHEMA, nullptr, nullptr, 0, 0, nullptr }
};

static const AlignedData<14> NULL_ENUM_SCHEMA_BYTES = {{
  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x11, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x20, 0x65, 0x6e,
  0x75, 0x6d, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d,
  0x61, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}};
const RawSchema NULL_ENUM_SCHEMA = {
  0x0000000000000002, NULL_ENUM_SCHEMA_BYTES.words, 14,
  nullptr, nullptr, 0, 0, nullptr, nullptr, nullptr,
  { &NULL_ENUM_SCHEMA, nullptr, nullptr, 0, 0, nullptr }
};

static const AlignedData<14> NULL_INTERFACE_SCHEMA_BYTES = {{
  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
  0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x11, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x20, 0x69, 0x6e,
  0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x20,
  0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x29, 0x00,
}};
const RawSchema NULL_INTERFACE_SCHEMA = {
  0x0000000000000003, NULL_INTERFACE_SCHEMA_BYTES.words, 14,
  nullptr, nullptr, 0, 0, nullptr, nullptr, nullptr,
  { &NULL_INTERFACE_SCHEMA, nullptr, nullptr, 0, 0, nullptr }
};

static const AlignedData<20> NULL_CONST_SCHEMA_BYTES = {{
  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
  0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x11, 0x00, 0x00, 0x00, 0xa2, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00,
  0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00,
  0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x20, 0x63, 0x6f,
  0x6e, 0x73, 0x74, 0x20, 0x73, 0x63, 0x68, 0x65,
  0x6d, 0x61, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}};
const RawSchema NULL_CONST_SCHEMA = {
  0x0000000000000004, NULL_CONST_SCHEMA_BYTES.words, 20,
  nullptr, nullptr, 0, 0, nullptr, nullptr, nullptr,
  { &NULL_CONST_SCHEMA, nullptr, nullptr, 0, 0, nullptr }
};

}  // namespace _ (private)

// =======================================================================================

schema::Node::Reader Schema::getProto() const {
  return readMessageUnchecked<schema::Node>(raw->generic->encodedNode);
}

kj::ArrayPtr<const word> Schema::asUncheckedMessage() const {
  return kj::arrayPtr(raw->generic->encodedNode, raw->generic->encodedSize);
}

Schema Schema::getDependency(uint64_t id, uint location) const {
  {
    // Binary search dependency list.
    uint lower = 0;
    uint upper = raw->dependencyCount;

    while (lower < upper) {
      uint mid = (lower + upper) / 2;

      auto candidate = raw->dependencies[mid];
      if (candidate.location == location) {
        candidate.schema->ensureInitialized();
        return Schema(candidate.schema);
      } else if (candidate.location < location) {
        lower = mid + 1;
      } else {
        upper = mid;
      }
    }
  }

  {
    uint lower = 0;
    uint upper = raw->generic->dependencyCount;

    while (lower < upper) {
      uint mid = (lower + upper) / 2;

      const _::RawSchema* candidate = raw->generic->dependencies[mid];

      uint64_t candidateId = candidate->id;
      if (candidateId == id) {
        candidate->ensureInitialized();
        return Schema(&candidate->defaultBrand);
      } else if (candidateId < id) {
        lower = mid + 1;
      } else {
        upper = mid;
      }
    }
  }

  KJ_FAIL_REQUIRE("Requested ID not found in dependency table.", kj::hex(id)) {
    return Schema();
  }
}

Schema::BrandArgumentList Schema::getBrandArgumentsAtScope(uint64_t scopeId) const {
  KJ_REQUIRE(getProto().getIsGeneric(), "Not a generic type.", getProto().getDisplayName());

  for (auto scope: kj::range(raw->scopes, raw->scopes + raw->scopeCount)) {
    if (scope->typeId == scopeId) {
      // OK, this scope matches the scope we're looking for.
      if (scope->isUnbound) {
        return BrandArgumentList(scopeId, true);
      } else {
        return BrandArgumentList(scopeId, scope->bindingCount, scope->bindings);
      }
    }
  }

  // This scope is not listed in the scopes list.
  return BrandArgumentList(scopeId, raw->isUnbound());
}

StructSchema Schema::asStruct() const {
  KJ_REQUIRE(getProto().isStruct(), "Tried to use non-struct schema as a struct.",
             getProto().getDisplayName()) {
    return StructSchema();
  }
  return StructSchema(*this);
}

EnumSchema Schema::asEnum() const {
  KJ_REQUIRE(getProto().isEnum(), "Tried to use non-enum schema as an enum.",
             getProto().getDisplayName()) {
    return EnumSchema();
  }
  return EnumSchema(*this);
}

InterfaceSchema Schema::asInterface() const {
  KJ_REQUIRE(getProto().isInterface(), "Tried to use non-interface schema as an interface.",
             getProto().getDisplayName()) {
    return InterfaceSchema();
  }
  return InterfaceSchema(*this);
}

ConstSchema Schema::asConst() const {
  KJ_REQUIRE(getProto().isConst(), "Tried to use non-constant schema as a constant.",
             getProto().getDisplayName()) {
    return ConstSchema();
  }
  return ConstSchema(*this);
}

kj::StringPtr Schema::getShortDisplayName() const {
  auto proto = getProto();
  return proto.getDisplayName().slice(proto.getDisplayNamePrefixLength());
}

void Schema::requireUsableAs(const _::RawSchema* expected) const {
  KJ_REQUIRE(raw->generic == expected ||
             (expected != nullptr && raw->generic->canCastTo == expected),
             "This schema is not compatible with the requested native type.");
}

uint32_t Schema::getSchemaOffset(const schema::Value::Reader& value) const {
  const word* ptr;

  switch (value.which()) {
    case schema::Value::TEXT:
      ptr = reinterpret_cast<const word*>(value.getText().begin());
      break;
    case schema::Value::DATA:
      ptr = reinterpret_cast<const word*>(value.getData().begin());
      break;
    case schema::Value::STRUCT:
      ptr = value.getStruct().getAs<_::UncheckedMessage>();
      break;
    case schema::Value::LIST:
      ptr = value.getList().getAs<_::UncheckedMessage>();
      break;
    case schema::Value::ANY_POINTER:
      ptr = value.getAnyPointer().getAs<_::UncheckedMessage>();
      break;
    default:
      KJ_FAIL_ASSERT("getDefaultValueSchemaOffset() can only be called on struct, list, "
                     "and any-pointer fields.");
  }

  return ptr - raw->generic->encodedNode;
}

Type Schema::getBrandBinding(uint64_t scopeId, uint index) const {
  return getBrandArgumentsAtScope(scopeId)[index];
}

Type Schema::interpretType(schema::Type::Reader proto, uint location) const {
  switch (proto.which()) {
    case schema::Type::VOID:
    case schema::Type::BOOL:
    case schema::Type::INT8:
    case schema::Type::INT16:
    case schema::Type::INT32:
    case schema::Type::INT64:
    case schema::Type::UINT8:
    case schema::Type::UINT16:
    case schema::Type::UINT32:
    case schema::Type::UINT64:
    case schema::Type::FLOAT32:
    case schema::Type::FLOAT64:
    case schema::Type::TEXT:
    case schema::Type::DATA:
      return proto.which();

    case schema::Type::STRUCT: {
      auto structType = proto.getStruct();
      return getDependency(structType.getTypeId(), location).asStruct();
    }

    case schema::Type::ENUM: {
      auto enumType = proto.getEnum();
      return getDependency(enumType.getTypeId(), location).asEnum();
    }

    case schema::Type::INTERFACE: {
      auto interfaceType = proto.getInterface();
      return getDependency(interfaceType.getTypeId(), location).asInterface();
    }

    case schema::Type::LIST:
      return ListSchema::of(interpretType(proto.getList().getElementType(), location));

    case schema::Type::ANY_POINTER: {
      auto anyPointer = proto.getAnyPointer();
      switch (anyPointer.which()) {
        case schema::Type::AnyPointer::UNCONSTRAINED:
          return anyPointer.getUnconstrained().which();
        case schema::Type::AnyPointer::PARAMETER: {
          auto param = anyPointer.getParameter();
          return getBrandBinding(param.getScopeId(), param.getParameterIndex());
        }
        case schema::Type::AnyPointer::IMPLICIT_METHOD_PARAMETER:
          return Type(Type::ImplicitParameter {
              anyPointer.getImplicitMethodParameter().getParameterIndex() });
      }

      KJ_UNREACHABLE;
    }
  }

  KJ_UNREACHABLE;
}

Type Schema::BrandArgumentList::operator[](uint index) const {
  if (isUnbound) {
    return Type::BrandParameter { scopeId, index };
  }

  if (index >= size_) {
    // Binding index out-of-range. Treat as AnyPointer. This is important to allow new
    // type parameters to be added to existing types without breaking dependent
    // schemas.
    return schema::Type::ANY_POINTER;
  }

  auto& binding = bindings[index];
  Type result;
  if (binding.which == (uint)schema::Type::ANY_POINTER) {
    if (binding.scopeId != 0) {
      result = Type::BrandParameter { binding.scopeId, binding.paramIndex };
    } else if (binding.isImplicitParameter) {
      result = Type::ImplicitParameter { binding.paramIndex };
    } else {
      result = static_cast<schema::Type::AnyPointer::Unconstrained::Which>(binding.paramIndex);
    }
  } else if (binding.schema == nullptr) {
    // Builtin / primitive type.
    result = static_cast<schema::Type::Which>(binding.which);
  } else {
    binding.schema->ensureInitialized();
    result = Type(static_cast<schema::Type::Which>(binding.which), binding.schema);
  }

  return result.wrapInList(binding.listDepth);
}

kj::StringPtr KJ_STRINGIFY(const Schema& schema) {
  return schema.getProto().getDisplayName();
}

// =======================================================================================

namespace {

template <typename List>
auto findSchemaMemberByName(const _::RawSchema* raw, kj::StringPtr name, List&& list)
    -> kj::Maybe<decltype(list[0])> {
  uint lower = 0;
  uint upper = raw->memberCount;

  while (lower < upper) {
    uint mid = (lower + upper) / 2;

    uint16_t memberIndex = raw->membersByName[mid];

    auto candidate = list[memberIndex];
    kj::StringPtr candidateName = candidate.getProto().getName();
    if (candidateName == name) {
      return candidate;
    } else if (candidateName < name) {
      lower = mid + 1;
    } else {
      upper = mid;
    }
  }

  return nullptr;
}

}  // namespace

StructSchema::FieldList StructSchema::getFields() const {
  return FieldList(*this, getProto().getStruct().getFields());
}

StructSchema::FieldSubset StructSchema::getUnionFields() const {
  auto proto = getProto().getStruct();
  return FieldSubset(*this, proto.getFields(),
                     raw->generic->membersByDiscriminant, proto.getDiscriminantCount());
}

StructSchema::FieldSubset StructSchema::getNonUnionFields() const {
  auto proto = getProto().getStruct();
  auto fields = proto.getFields();
  auto offset = proto.getDiscriminantCount();
  auto size = fields.size() - offset;
  return FieldSubset(*this, fields, raw->generic->membersByDiscriminant + offset, size);
}

kj::Maybe<StructSchema::Field> StructSchema::findFieldByName(kj::StringPtr name) const {
  return findSchemaMemberByName(raw->generic, name, getFields());
}

StructSchema::Field StructSchema::getFieldByName(kj::StringPtr name) const {
  KJ_IF_MAYBE(member, findFieldByName(name)) {
    return *member;
  } else {
    KJ_FAIL_REQUIRE("struct has no such member", name);
  }
}

kj::Maybe<StructSchema::Field> StructSchema::getFieldByDiscriminant(uint16_t discriminant) const {
  auto unionFields = getUnionFields();

  if (discriminant >= unionFields.size()) {
    return nullptr;
  } else {
    return unionFields[discriminant];
  }
}

Type StructSchema::Field::getType() const {
  auto proto = getProto();
  uint location = _::RawBrandedSchema::makeDepLocation(_::RawBrandedSchema::DepKind::FIELD, index);

  switch (proto.which()) {
    case schema::Field::SLOT:
      return parent.interpretType(proto.getSlot().getType(), location);

    case schema::Field::GROUP:
      return parent.getDependency(proto.getGroup().getTypeId(), location).asStruct();
  }
  KJ_UNREACHABLE;
}

uint32_t StructSchema::Field::getDefaultValueSchemaOffset() const {
  return parent.getSchemaOffset(proto.getSlot().getDefaultValue());
}

kj::StringPtr KJ_STRINGIFY(const StructSchema::Field& field) {
  return field.getProto().getName();
}

// -------------------------------------------------------------------

EnumSchema::EnumerantList EnumSchema::getEnumerants() const {
  return EnumerantList(*this, getProto().getEnum().getEnumerants());
}

kj::Maybe<EnumSchema::Enumerant> EnumSchema::findEnumerantByName(kj::StringPtr name) const {
  return findSchemaMemberByName(raw->generic, name, getEnumerants());
}

EnumSchema::Enumerant EnumSchema::getEnumerantByName(kj::StringPtr name) const {
  KJ_IF_MAYBE(enumerant, findEnumerantByName(name)) {
    return *enumerant;
  } else {
    KJ_FAIL_REQUIRE("enum has no such enumerant", name);
  }
}

// -------------------------------------------------------------------

InterfaceSchema::MethodList InterfaceSchema::getMethods() const {
  return MethodList(*this, getProto().getInterface().getMethods());
}

kj::Maybe<InterfaceSchema::Method> InterfaceSchema::findMethodByName(kj::StringPtr name) const {
  uint counter = 0;
  return findMethodByName(name, counter);
}

static constexpr uint MAX_SUPERCLASSES = 64;

kj::Maybe<InterfaceSchema::Method> InterfaceSchema::findMethodByName(
    kj::StringPtr name, uint& counter) const {
  // Security:  Don't let someone DOS us with a dynamic schema containing cyclic inheritance.
  KJ_REQUIRE(counter++ < MAX_SUPERCLASSES, "Cyclic or absurdly-large inheritance graph detected.") {
    return nullptr;
  }

  auto result = findSchemaMemberByName(raw->generic, name, getMethods());

  if (result == nullptr) {
    // Search superclasses.
    // TODO(perf):  This may be somewhat slow, and in the case of lots of diamond dependencies it
    //   could get pathological.  Arguably we should generate a flat list of transitive
    //   superclasses to search and store it in the RawSchema.  It's problematic, though, because
    //   this means that a dynamically-loaded RawSchema cannot be correctly constructed until all
    //   superclasses have been loaded, which imposes an ordering requirement on SchemaLoader or
    //   requires updating subclasses whenever a new superclass is loaded.
    auto superclasses = getProto().getInterface().getSuperclasses();
    for (auto i: kj::indices(superclasses)) {
      auto superclass = superclasses[i];
      uint location = _::RawBrandedSchema::makeDepLocation(
          _::RawBrandedSchema::DepKind::SUPERCLASS, i);
      result = getDependency(superclass.getId(), location)
          .asInterface().findMethodByName(name, counter);
      if (result != nullptr) {
        break;
      }
    }
  }

  return result;
}

InterfaceSchema::Method InterfaceSchema::getMethodByName(kj::StringPtr name) const {
  KJ_IF_MAYBE(method, findMethodByName(name)) {
    return *method;
  } else {
    KJ_FAIL_REQUIRE("interface has no such method", name);
  }
}

InterfaceSchema::SuperclassList InterfaceSchema::getSuperclasses() const {
  return SuperclassList(*this, getProto().getInterface().getSuperclasses());
}

bool InterfaceSchema::extends(InterfaceSchema other) const {
  if (other.raw->generic == &_::NULL_INTERFACE_SCHEMA) {
    // We consider all interfaces to extend the null schema.
    return true;
  }
  uint counter = 0;
  return extends(other, counter);
}

bool InterfaceSchema::extends(InterfaceSchema other, uint& counter) const {
  // Security:  Don't let someone DOS us with a dynamic schema containing cyclic inheritance.
  KJ_REQUIRE(counter++ < MAX_SUPERCLASSES, "Cyclic or absurdly-large inheritance graph detected.") {
    return false;
  }

  if (other == *this) {
    return true;
  }

  // TODO(perf):  This may be somewhat slow.  See findMethodByName() for discussion.
  auto superclasses = getProto().getInterface().getSuperclasses();
  for (auto i: kj::indices(superclasses)) {
    auto superclass = superclasses[i];
    uint location = _::RawBrandedSchema::makeDepLocation(
        _::RawBrandedSchema::DepKind::SUPERCLASS, i);
    if (getDependency(superclass.getId(), location).asInterface().extends(other, counter)) {
      return true;
    }
  }

  return false;
}

kj::Maybe<InterfaceSchema> InterfaceSchema::findSuperclass(uint64_t typeId) const {
  if (typeId == _::NULL_INTERFACE_SCHEMA.id) {
    // We consider all interfaces to extend the null schema.
    return InterfaceSchema();
  }
  uint counter = 0;
  return findSuperclass(typeId, counter);
}

kj::Maybe<InterfaceSchema> InterfaceSchema::findSuperclass(uint64_t typeId, uint& counter) const {
  // Security:  Don't let someone DOS us with a dynamic schema containing cyclic inheritance.
  KJ_REQUIRE(counter++ < MAX_SUPERCLASSES, "Cyclic or absurdly-large inheritance graph detected.") {
    return nullptr;
  }

  if (typeId == raw->generic->id) {
    return *this;
  }

  // TODO(perf):  This may be somewhat slow.  See findMethodByName() for discussion.
  auto superclasses = getProto().getInterface().getSuperclasses();
  for (auto i: kj::indices(superclasses)) {
    auto superclass = superclasses[i];
    uint location = _::RawBrandedSchema::makeDepLocation(
        _::RawBrandedSchema::DepKind::SUPERCLASS, i);
    KJ_IF_MAYBE(result, getDependency(superclass.getId(), location).asInterface()
                            .findSuperclass(typeId, counter)) {
      return *result;
    }
  }

  return nullptr;
}

StructSchema InterfaceSchema::Method::getParamType() const {
  auto proto = getProto();
  uint location = _::RawBrandedSchema::makeDepLocation(
      _::RawBrandedSchema::DepKind::METHOD_PARAMS, ordinal);
  return parent.getDependency(proto.getParamStructType(), location).asStruct();
}

StructSchema InterfaceSchema::Method::getResultType() const {
  auto proto = getProto();
  uint location = _::RawBrandedSchema::makeDepLocation(
      _::RawBrandedSchema::DepKind::METHOD_RESULTS, ordinal);
  return parent.getDependency(proto.getResultStructType(), location).asStruct();
}

InterfaceSchema InterfaceSchema::SuperclassList::operator[](uint index) const {
  auto superclass = list[index];
  uint location = _::RawBrandedSchema::makeDepLocation(
      _::RawBrandedSchema::DepKind::SUPERCLASS, index);
  return parent.getDependency(superclass.getId(), location).asInterface();
}

// -------------------------------------------------------------------

uint32_t ConstSchema::getValueSchemaOffset() const {
  return getSchemaOffset(getProto().getConst().getValue());
}

Type ConstSchema::getType() const {
  return interpretType(getProto().getConst().getType(),
      _::RawBrandedSchema::makeDepLocation(_::RawBrandedSchema::DepKind::CONST_TYPE, 0));
}

// =======================================================================================

ListSchema ListSchema::of(schema::Type::Which primitiveType) {
  switch (primitiveType) {
    case schema::Type::VOID:
    case schema::Type::BOOL:
    case schema::Type::INT8:
    case schema::Type::INT16:
    case schema::Type::INT32:
    case schema::Type::INT64:
    case schema::Type::UINT8:
    case schema::Type::UINT16:
    case schema::Type::UINT32:
    case schema::Type::UINT64:
    case schema::Type::FLOAT32:
    case schema::Type::FLOAT64:
    case schema::Type::TEXT:
    case schema::Type::DATA:
      break;

    case schema::Type::STRUCT:
    case schema::Type::ENUM:
    case schema::Type::INTERFACE:
    case schema::Type::LIST:
      KJ_FAIL_REQUIRE("Must use one of the other ListSchema::of() overloads for complex types.");
      break;

    case schema::Type::ANY_POINTER:
      KJ_FAIL_REQUIRE("List(AnyPointer) not supported.");
      break;
  }

  return ListSchema(primitiveType);
}

ListSchema ListSchema::of(schema::Type::Reader elementType, Schema context) {
  // This method is deprecated because it can only be implemented in terms of other deprecated
  // methods. Temporarily disable warnings for those other deprecated methods.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

  switch (elementType.which()) {
    case schema::Type::VOID:
    case schema::Type::BOOL:
    case schema::Type::INT8:
    case schema::Type::INT16:
    case schema::Type::INT32:
    case schema::Type::INT64:
    case schema::Type::UINT8:
    case schema::Type::UINT16:
    case schema::Type::UINT32:
    case schema::Type::UINT64:
    case schema::Type::FLOAT32:
    case schema::Type::FLOAT64:
    case schema::Type::TEXT:
    case schema::Type::DATA:
      return of(elementType.which());

    case schema::Type::STRUCT:
      return of(context.getDependency(elementType.getStruct().getTypeId()).asStruct());

    case schema::Type::ENUM:
      return of(context.getDependency(elementType.getEnum().getTypeId()).asEnum());

    case schema::Type::INTERFACE:
      return of(context.getDependency(elementType.getInterface().getTypeId()).asInterface());

    case schema::Type::LIST:
      return of(of(elementType.getList().getElementType(), context));

    case schema::Type::ANY_POINTER:
      KJ_FAIL_REQUIRE("List(AnyPointer) not supported.");
      return ListSchema();
  }

  // Unknown type is acceptable.
  return ListSchema(elementType.which());
#pragma GCC diagnostic pop
}

// =======================================================================================

StructSchema Type::asStruct() const {
  KJ_REQUIRE(isStruct(), "Tried to interpret a non-struct type as a struct.") {
    return StructSchema();
  }
  KJ_ASSERT(schema != nullptr);
  return StructSchema(Schema(schema));
}
EnumSchema Type::asEnum() const {
  KJ_REQUIRE(isEnum(), "Tried to interpret a non-enum type as an enum.") {
    return EnumSchema();
  }
  KJ_ASSERT(schema != nullptr);
  return EnumSchema(Schema(schema));
}
InterfaceSchema Type::asInterface() const {
  KJ_REQUIRE(isInterface(), "Tried to interpret a non-interface type as an interface.") {
    return InterfaceSchema();
  }
  KJ_ASSERT(schema != nullptr);
  return InterfaceSchema(Schema(schema));
}
ListSchema Type::asList() const {
  KJ_REQUIRE(isList(), "Type::asList(): Not a list.") {
    return ListSchema::of(schema::Type::VOID);
  }
  Type elementType = *this;
  --elementType.listDepth;
  return ListSchema::of(elementType);
}

kj::Maybe<Type::BrandParameter> Type::getBrandParameter() const {
  KJ_REQUIRE(isAnyPointer(), "Type::getBrandParameter() can only be called on AnyPointer types.");

  if (scopeId == 0) {
    return nullptr;
  } else {
    return BrandParameter { scopeId, paramIndex };
  }
}

kj::Maybe<Type::ImplicitParameter> Type::getImplicitParameter() const {
  KJ_REQUIRE(isAnyPointer(),
      "Type::getImplicitParameter() can only be called on AnyPointer types.");

  if (isImplicitParam) {
    return ImplicitParameter { paramIndex };
  } else {
    return nullptr;
  }
}

bool Type::operator==(const Type& other) const {
  if (baseType != other.baseType || listDepth != other.listDepth) {
    return false;
  }

  switch (baseType) {
    case schema::Type::VOID:
    case schema::Type::BOOL:
    case schema::Type::INT8:
    case schema::Type::INT16:
    case schema::Type::INT32:
    case schema::Type::INT64:
    case schema::Type::UINT8:
    case schema::Type::UINT16:
    case schema::Type::UINT32:
    case schema::Type::UINT64:
    case schema::Type::FLOAT32:
    case schema::Type::FLOAT64:
    case schema::Type::TEXT:
    case schema::Type::DATA:
      return true;

    case schema::Type::STRUCT:
    case schema::Type::ENUM:
    case schema::Type::INTERFACE:
      return schema == other.schema;

    case schema::Type::LIST:
      KJ_UNREACHABLE;

    case schema::Type::ANY_POINTER:
      return scopeId == other.scopeId && isImplicitParam == other.isImplicitParam &&
          // Trying to comply with strict aliasing rules. Hopefully the compiler realizes that
          // both branches compile to the same instructions and can optimize it away.
          (scopeId != 0 || isImplicitParam ? paramIndex == other.paramIndex
                                           : anyPointerKind == other.anyPointerKind);
  }

  KJ_UNREACHABLE;
}

uint Type::hashCode() const {
  switch (baseType) {
    case schema::Type::VOID:
    case schema::Type::BOOL:
    case schema::Type::INT8:
    case schema::Type::INT16:
    case schema::Type::INT32:
    case schema::Type::INT64:
    case schema::Type::UINT8:
    case schema::Type::UINT16:
    case schema::Type::UINT32:
    case schema::Type::UINT64:
    case schema::Type::FLOAT32:
    case schema::Type::FLOAT64:
    case schema::Type::TEXT:
    case schema::Type::DATA:
      return kj::hashCode(baseType, listDepth);

    case schema::Type::STRUCT:
    case schema::Type::ENUM:
    case schema::Type::INTERFACE:
      return kj::hashCode(schema, listDepth);

    case schema::Type::LIST:
      KJ_UNREACHABLE;

    case schema::Type::ANY_POINTER: {
      // Trying to comply with strict aliasing rules. Hopefully the compiler realizes that
      // both branches compile to the same instructions and can optimize it away.
      uint16_t val = scopeId != 0 || isImplicitParam ?
          paramIndex : static_cast<uint16_t>(anyPointerKind);
      return kj::hashCode(val, isImplicitParam, scopeId);
    }
  }

  KJ_UNREACHABLE;
}

void Type::requireUsableAs(Type expected) const {
  KJ_REQUIRE(baseType == expected.baseType && listDepth == expected.listDepth,
             "This type is not compatible with the requested native type.");

  switch (baseType) {
    case schema::Type::VOID:
    case schema::Type::BOOL:
    case schema::Type::INT8:
    case schema::Type::INT16:
    case schema::Type::INT32:
    case schema::Type::INT64:
    case schema::Type::UINT8:
    case schema::Type::UINT16:
    case schema::Type::UINT32:
    case schema::Type::UINT64:
    case schema::Type::FLOAT32:
    case schema::Type::FLOAT64:
    case schema::Type::TEXT:
    case schema::Type::DATA:
    case schema::Type::ANY_POINTER:
      break;

    case schema::Type::STRUCT:
    case schema::Type::ENUM:
    case schema::Type::INTERFACE:
      Schema(schema).requireUsableAs(expected.schema->generic);
      break;

    case schema::Type::LIST:
      KJ_UNREACHABLE;
  }
}

}  // namespace capnp
