//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Dispatch
import LanguageServerProtocol
import LSPLogging
import SKSupport
import sourcekitd
import TSCBasic

/// A wrapper for accessing the API of a sourcekitd library loaded via `dlopen`.
final class SwiftSourceKitFramework {

  /// The path to the sourcekitd dylib.
  let path: AbsolutePath

  /// The handle to the dylib.
  let dylib: DLHandle

  /// The sourcekitd API functions.
  let api: sourcekitd_functions_t

  /// Convenience for accessing known keys.
  let keys: sourcekitd_keys

  /// Convenience for accessing known keys.
  let requests: sourcekitd_requests

  /// Convenience for accessing known keys.
  let values: sourcekitd_values

  enum Error: Swift.Error {
    case missingRequiredSymbol(String)
  }

  init(dylib path: AbsolutePath) throws {
    self.path = path
    #if os(Windows)
    self.dylib = try dlopen(path.pathString, mode: [])
    #elseif os(Android)
    self.dylib = try dlopen(path.pathString, mode: [.lazy, .local, .first])
    #else
    self.dylib = try dlopen(path.pathString, mode: [.lazy, .local, .first, .deepBind])
    #endif

    func dlsym_required<T>(_ handle: DLHandle, symbol: String) throws -> T {
      guard let sym: T = dlsym(handle, symbol: symbol) else {
        throw Error.missingRequiredSymbol(symbol)
      }
      return sym
    }

    // Workaround rdar://problem/43656704 by not constructing the value directly.
    // self.api = sourcekitd_functions_t(
    let ptr = UnsafeMutablePointer<sourcekitd_functions_t>.allocate(capacity: 1)
    memset(UnsafeMutableRawPointer(ptr), 0, MemoryLayout<sourcekitd_functions_t>.stride)
    var api = ptr.pointee
    ptr.deallocate()

    // The following crashes the compiler rdar://43658464
    // api.variant_dictionary_apply = try dlsym_required(dylib, symbol: "sourcekitd_variant_dictionary_apply")
    // api.variant_array_apply = try dlsym_required(dylib, symbol: "sourcekitd_variant_array_apply")

    api.initialize = try dlsym_required(dylib, symbol: "sourcekitd_initialize")
    api.shutdown = try dlsym_required(dylib, symbol: "sourcekitd_shutdown")
    api.uid_get_from_cstr = try dlsym_required(dylib, symbol: "sourcekitd_uid_get_from_cstr")
    api.uid_get_from_buf = try dlsym_required(dylib, symbol: "sourcekitd_uid_get_from_buf")
    api.uid_get_length = try dlsym_required(dylib, symbol: "sourcekitd_uid_get_length")
    api.uid_get_string_ptr = try dlsym_required(dylib, symbol: "sourcekitd_uid_get_string_ptr")
    api.request_retain = try dlsym_required(dylib, symbol: "sourcekitd_request_retain")
    api.request_release = try dlsym_required(dylib, symbol: "sourcekitd_request_release")
    api.request_dictionary_create = try dlsym_required(dylib, symbol: "sourcekitd_request_dictionary_create")
    api.request_dictionary_set_value = try dlsym_required(dylib, symbol: "sourcekitd_request_dictionary_set_value")
    api.request_dictionary_set_string = try dlsym_required(dylib, symbol: "sourcekitd_request_dictionary_set_string")
    api.request_dictionary_set_stringbuf = try dlsym_required(dylib, symbol: "sourcekitd_request_dictionary_set_stringbuf")
    api.request_dictionary_set_int64 = try dlsym_required(dylib, symbol: "sourcekitd_request_dictionary_set_int64")
    api.request_dictionary_set_uid = try dlsym_required(dylib, symbol: "sourcekitd_request_dictionary_set_uid")
    api.request_array_create = try dlsym_required(dylib, symbol: "sourcekitd_request_array_create")
    api.request_array_set_value = try dlsym_required(dylib, symbol: "sourcekitd_request_array_set_value")
    api.request_array_set_string = try dlsym_required(dylib, symbol: "sourcekitd_request_array_set_string")
    api.request_array_set_stringbuf = try dlsym_required(dylib, symbol: "sourcekitd_request_array_set_stringbuf")
    api.request_array_set_int64 = try dlsym_required(dylib, symbol: "sourcekitd_request_array_set_int64")
    api.request_array_set_uid = try dlsym_required(dylib, symbol: "sourcekitd_request_array_set_uid")
    api.request_int64_create = try dlsym_required(dylib, symbol: "sourcekitd_request_int64_create")
    api.request_string_create = try dlsym_required(dylib, symbol: "sourcekitd_request_string_create")
    api.request_uid_create = try dlsym_required(dylib, symbol: "sourcekitd_request_uid_create")
    api.request_create_from_yaml = try dlsym_required(dylib, symbol: "sourcekitd_request_create_from_yaml")
    api.request_description_dump = try dlsym_required(dylib, symbol: "sourcekitd_request_description_dump")
    api.request_description_copy = try dlsym_required(dylib, symbol: "sourcekitd_request_description_copy")
    api.response_dispose = try dlsym_required(dylib, symbol: "sourcekitd_response_dispose")
    api.response_is_error = try dlsym_required(dylib, symbol: "sourcekitd_response_is_error")
    api.response_error_get_kind = try dlsym_required(dylib, symbol: "sourcekitd_response_error_get_kind")
    api.response_error_get_description = try dlsym_required(dylib, symbol: "sourcekitd_response_error_get_description")
    api.response_get_value = try dlsym_required(dylib, symbol: "sourcekitd_response_get_value")
    api.variant_get_type = try dlsym_required(dylib, symbol: "sourcekitd_variant_get_type")
    api.variant_dictionary_get_value = try dlsym_required(dylib, symbol: "sourcekitd_variant_dictionary_get_value")
    api.variant_dictionary_get_string = try dlsym_required(dylib, symbol: "sourcekitd_variant_dictionary_get_string")
    api.variant_dictionary_get_int64 = try dlsym_required(dylib, symbol: "sourcekitd_variant_dictionary_get_int64")
    api.variant_dictionary_get_bool = try dlsym_required(dylib, symbol: "sourcekitd_variant_dictionary_get_bool")
    api.variant_dictionary_get_uid = try dlsym_required(dylib, symbol: "sourcekitd_variant_dictionary_get_uid")

    api.variant_array_get_count = try dlsym_required(dylib, symbol: "sourcekitd_variant_array_get_count")
    api.variant_array_get_value = try dlsym_required(dylib, symbol: "sourcekitd_variant_array_get_value")
    api.variant_array_get_string = try dlsym_required(dylib, symbol: "sourcekitd_variant_array_get_string")
    api.variant_array_get_int64 = try dlsym_required(dylib, symbol: "sourcekitd_variant_array_get_int64")
    api.variant_array_get_bool = try dlsym_required(dylib, symbol: "sourcekitd_variant_array_get_bool")
    api.variant_array_get_uid = try dlsym_required(dylib, symbol: "sourcekitd_variant_array_get_uid")

    api.variant_int64_get_value = try dlsym_required(dylib, symbol: "sourcekitd_variant_int64_get_value")
    api.variant_bool_get_value = try dlsym_required(dylib, symbol: "sourcekitd_variant_bool_get_value")
    api.variant_string_get_length = try dlsym_required(dylib, symbol: "sourcekitd_variant_string_get_length")
    api.variant_string_get_ptr = try dlsym_required(dylib, symbol: "sourcekitd_variant_string_get_ptr")
    api.variant_data_get_size = dlsym(dylib, symbol: "sourcekitd_variant_data_get_size") // Optional
    api.variant_data_get_ptr = dlsym(dylib, symbol: "sourcekitd_variant_data_get_ptr") // Optional
    api.variant_uid_get_value = try dlsym_required(dylib, symbol: "sourcekitd_variant_uid_get_value")
    api.response_description_dump = try dlsym_required(dylib, symbol: "sourcekitd_response_description_dump")
    api.response_description_dump_filedesc = try dlsym_required(dylib, symbol: "sourcekitd_response_description_dump_filedesc")
    api.response_description_copy = try dlsym_required(dylib, symbol: "sourcekitd_response_description_copy")
    api.variant_description_dump = try dlsym_required(dylib, symbol: "sourcekitd_variant_description_dump")
    api.variant_description_dump_filedesc = try dlsym_required(dylib, symbol: "sourcekitd_variant_description_dump_filedesc")
    api.variant_description_copy = try dlsym_required(dylib, symbol: "sourcekitd_variant_description_copy")
    api.send_request_sync = try dlsym_required(dylib, symbol: "sourcekitd_send_request_sync")
    api.send_request = try dlsym_required(dylib, symbol: "sourcekitd_send_request")
    api.cancel_request = try dlsym_required(dylib, symbol: "sourcekitd_cancel_request")
    api.set_notification_handler = try dlsym_required(dylib, symbol: "sourcekitd_set_notification_handler")
    api.set_uid_handlers = try dlsym_required(dylib, symbol: "sourcekitd_set_uid_handlers")

    self.api = api

    self.keys = sourcekitd_keys(api: self.api)
    self.requests = sourcekitd_requests(api: self.api)
    self.values = sourcekitd_values(api: self.api)
  }

  deinit {
    // FIXME: is it safe to dlclose() sourcekitd? If so, do that here. For now, let the handle leak.
    dylib.leak()
  }
}

extension SwiftSourceKitFramework {

  // MARK: - Convenience API for requests.

  /// Send the given request and synchronously receive a reply dictionary (or error).
  func sendSync(_ req: SKRequestDictionary) -> LSPResult<SKResponseDictionary> {
    logAsync { _ in req.description }

    let resp = SKResponse(api.send_request_sync(req.dict), sourcekitd: self)

    guard let dict = resp.value else {
      log(resp.description, level: .error)
      return .failure(resp.error!)
    }

    logAsync(level: .debug) { _ in dict.description }

    return .success(dict)
  }

  /// Send the given request and asynchronously receive a reply dictionary (or error) on the given queue.
  func send(
    _ req: SKRequestDictionary,
    _ queue: DispatchQueue,
    reply: @escaping (LSPResult<SKResponseDictionary>) -> Void
  ) -> sourcekitd_request_handle_t? {
    logAsync { _ in req.description }

    var handle: sourcekitd_request_handle_t? = nil

    api.send_request(req.dict, &handle) { [weak self] _resp in
      guard let self = self else { return }

      let resp = SKResponse(_resp, sourcekitd: self)

      guard let dict = resp.value else {
        log(resp.description, level: .error)
        queue.async {
         reply(.failure(resp.error!))
        }
        return
      }

      logAsync(level: .debug) { _ in dict.description }

      queue.async {
        reply(.success(dict))
      }
    }

    return handle
  }

}

struct sourcekitd_keys {

  let request: sourcekitd_uid_t
  let compilerargs: sourcekitd_uid_t
  let offset: sourcekitd_uid_t
  let length: sourcekitd_uid_t
  let sourcefile: sourcekitd_uid_t
  let sourcetext: sourcekitd_uid_t
  let results: sourcekitd_uid_t
  let description: sourcekitd_uid_t
  let name: sourcekitd_uid_t
  let kind: sourcekitd_uid_t
  let notification: sourcekitd_uid_t
  let fixits: sourcekitd_uid_t
  let diagnostics: sourcekitd_uid_t
  let diagnostic_stage: sourcekitd_uid_t
  let severity: sourcekitd_uid_t
  let line: sourcekitd_uid_t
  let column: sourcekitd_uid_t
  let endline: sourcekitd_uid_t
  let endcolumn: sourcekitd_uid_t
  let filepath: sourcekitd_uid_t
  let ranges: sourcekitd_uid_t
  let usr: sourcekitd_uid_t
  let typename: sourcekitd_uid_t
  let annotated_decl: sourcekitd_uid_t
  let doc_full_as_xml: sourcekitd_uid_t
  let syntactic_only: sourcekitd_uid_t
  let substructure: sourcekitd_uid_t
  let bodyoffset: sourcekitd_uid_t
  let bodylength: sourcekitd_uid_t
  let syntaxmap: sourcekitd_uid_t
  let namelength: sourcekitd_uid_t
  let nameoffset: sourcekitd_uid_t
  let retrieve_refactor_actions: sourcekitd_uid_t
  let refactor_actions: sourcekitd_uid_t
  let actionname: sourcekitd_uid_t
  let actionuid: sourcekitd_uid_t
  let categorizededits: sourcekitd_uid_t
  let edits: sourcekitd_uid_t
  let text: sourcekitd_uid_t

  // Code Completion related keys.
  let context: sourcekitd_uid_t
  let doc: sourcekitd_uid_t
  let not_recommended: sourcekitd_uid_t
  let num_bytes_to_erase: sourcekitd_uid_t
  let associated_usrs: sourcekitd_uid_t


  init(api: sourcekitd_functions_t) {
    request = api.uid_get_from_cstr("key.request")!
    compilerargs = api.uid_get_from_cstr("key.compilerargs")!
    offset = api.uid_get_from_cstr("key.offset")!
    length = api.uid_get_from_cstr("key.length")!
    sourcefile = api.uid_get_from_cstr("key.sourcefile")!
    sourcetext = api.uid_get_from_cstr("key.sourcetext")!
    results = api.uid_get_from_cstr("key.results")!
    description = api.uid_get_from_cstr("key.description")!
    name = api.uid_get_from_cstr("key.name")!
    kind = api.uid_get_from_cstr("key.kind")!
    notification = api.uid_get_from_cstr("key.notification")!
    fixits = api.uid_get_from_cstr("key.fixits")!
    diagnostics = api.uid_get_from_cstr("key.diagnostics")!
    diagnostic_stage = api.uid_get_from_cstr("key.diagnostic_stage")!
    severity = api.uid_get_from_cstr("key.severity")!
    line = api.uid_get_from_cstr("key.line")!
    column = api.uid_get_from_cstr("key.column")!
    endline = api.uid_get_from_cstr("key.endline")!
    endcolumn = api.uid_get_from_cstr("key.endcolumn")!
    filepath = api.uid_get_from_cstr("key.filepath")!
    ranges = api.uid_get_from_cstr("key.ranges")!
    usr = api.uid_get_from_cstr("key.usr")!
    typename = api.uid_get_from_cstr("key.typename")!
    annotated_decl = api.uid_get_from_cstr("key.annotated_decl")!
    doc_full_as_xml = api.uid_get_from_cstr("key.doc.full_as_xml")!
    syntactic_only = api.uid_get_from_cstr("key.syntactic_only")!
    substructure = api.uid_get_from_cstr("key.substructure")!
    bodyoffset = api.uid_get_from_cstr("key.bodyoffset")!
    bodylength = api.uid_get_from_cstr("key.bodylength")!
    syntaxmap = api.uid_get_from_cstr("key.syntaxmap")!
    namelength = api.uid_get_from_cstr("key.namelength")!
    nameoffset = api.uid_get_from_cstr("key.nameoffset")!
    retrieve_refactor_actions = api.uid_get_from_cstr("key.retrieve_refactor_actions")!
    refactor_actions = api.uid_get_from_cstr("key.refactor_actions")!
    actionname = api.uid_get_from_cstr("key.actionname")!
    actionuid = api.uid_get_from_cstr("key.actionuid")!
    categorizededits = api.uid_get_from_cstr("key.categorizededits")!
    edits = api.uid_get_from_cstr("key.edits")!
    text = api.uid_get_from_cstr("key.text")!

    // Code Completion related keys.
    context = api.uid_get_from_cstr("key.context")!
    doc = api.uid_get_from_cstr("key.doc.brief")!
    not_recommended = api.uid_get_from_cstr("key.not_recommended")!
    num_bytes_to_erase = api.uid_get_from_cstr("key.num_bytes_to_erase")!
    associated_usrs = api.uid_get_from_cstr("key.associated_usrs")!
  }
}

struct sourcekitd_requests {

  let editor_open: sourcekitd_uid_t
  let editor_close: sourcekitd_uid_t
  let editor_replacetext: sourcekitd_uid_t
  let codecomplete: sourcekitd_uid_t
  let cursorinfo: sourcekitd_uid_t
  let relatedidents: sourcekitd_uid_t
  let semantic_refactoring: sourcekitd_uid_t

  init(api: sourcekitd_functions_t) {
    editor_open = api.uid_get_from_cstr("source.request.editor.open")!
    editor_close = api.uid_get_from_cstr("source.request.editor.close")!
    editor_replacetext = api.uid_get_from_cstr("source.request.editor.replacetext")!
    codecomplete = api.uid_get_from_cstr("source.request.codecomplete")!
    cursorinfo = api.uid_get_from_cstr("source.request.cursorinfo")!
    relatedidents = api.uid_get_from_cstr("source.request.relatedidents")!
    semantic_refactoring = api.uid_get_from_cstr("source.request.semantic.refactoring")!
  }
}

struct sourcekitd_values {

  let notification_documentupdate: sourcekitd_uid_t
  let diag_error: sourcekitd_uid_t
  let diag_warning: sourcekitd_uid_t
  let diag_note: sourcekitd_uid_t
  let diag_stage_parse: sourcekitd_uid_t
  let diag_stage_sema: sourcekitd_uid_t

  // MARK: Symbol Kinds

  let decl_function_free: sourcekitd_uid_t
  let ref_function_free: sourcekitd_uid_t
  let decl_function_method_instance: sourcekitd_uid_t
  let ref_function_method_instance: sourcekitd_uid_t
  let decl_function_method_static: sourcekitd_uid_t
  let ref_function_method_static: sourcekitd_uid_t
  let decl_function_method_class: sourcekitd_uid_t
  let ref_function_method_class: sourcekitd_uid_t
  let decl_function_accessor_getter: sourcekitd_uid_t
  let ref_function_accessor_getter: sourcekitd_uid_t
  let decl_function_accessor_setter: sourcekitd_uid_t
  let ref_function_accessor_setter: sourcekitd_uid_t
  let decl_function_accessor_willset: sourcekitd_uid_t
  let ref_function_accessor_willset: sourcekitd_uid_t
  let decl_function_accessor_didset: sourcekitd_uid_t
  let ref_function_accessor_didset: sourcekitd_uid_t
  let decl_function_accessor_address: sourcekitd_uid_t
  let ref_function_accessor_address: sourcekitd_uid_t
  let decl_function_accessor_mutableaddress: sourcekitd_uid_t
  let ref_function_accessor_mutableaddress: sourcekitd_uid_t
  let decl_function_accessor_read: sourcekitd_uid_t
  let ref_function_accessor_read: sourcekitd_uid_t
  let decl_function_accessor_modify: sourcekitd_uid_t
  let ref_function_accessor_modify: sourcekitd_uid_t
  let decl_function_constructor: sourcekitd_uid_t
  let ref_function_constructor: sourcekitd_uid_t
  let decl_function_destructor: sourcekitd_uid_t
  let ref_function_destructor: sourcekitd_uid_t
  let decl_function_operator_prefix: sourcekitd_uid_t
  let decl_function_operator_postfix: sourcekitd_uid_t
  let decl_function_operator_infix: sourcekitd_uid_t
  let ref_function_operator_prefix: sourcekitd_uid_t
  let ref_function_operator_postfix: sourcekitd_uid_t
  let ref_function_operator_infix: sourcekitd_uid_t
  let decl_precedencegroup: sourcekitd_uid_t
  let ref_precedencegroup: sourcekitd_uid_t
  let decl_function_subscript: sourcekitd_uid_t
  let ref_function_subscript: sourcekitd_uid_t
  let decl_var_global: sourcekitd_uid_t
  let ref_var_global: sourcekitd_uid_t
  let decl_var_instance: sourcekitd_uid_t
  let ref_var_instance: sourcekitd_uid_t
  let decl_var_static: sourcekitd_uid_t
  let ref_var_static: sourcekitd_uid_t
  let decl_var_class: sourcekitd_uid_t
  let ref_var_class: sourcekitd_uid_t
  let decl_var_local: sourcekitd_uid_t
  let ref_var_local: sourcekitd_uid_t
  let decl_var_parameter: sourcekitd_uid_t
  let decl_module: sourcekitd_uid_t
  let decl_class: sourcekitd_uid_t
  let ref_class: sourcekitd_uid_t
  let decl_struct: sourcekitd_uid_t
  let ref_struct: sourcekitd_uid_t
  let decl_enum: sourcekitd_uid_t
  let ref_enum: sourcekitd_uid_t
  let decl_enumcase: sourcekitd_uid_t
  let decl_enumelement: sourcekitd_uid_t
  let ref_enumelement: sourcekitd_uid_t
  let decl_protocol: sourcekitd_uid_t
  let ref_protocol: sourcekitd_uid_t
  let decl_extension: sourcekitd_uid_t
  let decl_extension_struct: sourcekitd_uid_t
  let decl_extension_class: sourcekitd_uid_t
  let decl_extension_enum: sourcekitd_uid_t
  let decl_extension_protocol: sourcekitd_uid_t
  let decl_associatedtype: sourcekitd_uid_t
  let ref_associatedtype: sourcekitd_uid_t
  let decl_typealias: sourcekitd_uid_t
  let ref_typealias: sourcekitd_uid_t
  let decl_generic_type_param: sourcekitd_uid_t
  let ref_generic_type_param: sourcekitd_uid_t
  let ref_module: sourcekitd_uid_t
  let syntaxtype_comment: sourcekitd_uid_t
  let syntaxtype_comment_marker: sourcekitd_uid_t
  let syntaxtype_comment_url: sourcekitd_uid_t
  let syntaxtype_doccomment: sourcekitd_uid_t
  let syntaxtype_doccomment_field: sourcekitd_uid_t
  let expr_object_literal: sourcekitd_uid_t

  let kind_keyword: sourcekitd_uid_t

  init(api: sourcekitd_functions_t) {
    notification_documentupdate = api.uid_get_from_cstr("source.notification.editor.documentupdate")!
    diag_error = api.uid_get_from_cstr("source.diagnostic.severity.error")!
    diag_warning = api.uid_get_from_cstr("source.diagnostic.severity.warning")!
    diag_note = api.uid_get_from_cstr("source.diagnostic.severity.note")!
    diag_stage_parse = api.uid_get_from_cstr("source.diagnostic.stage.swift.parse")!
    diag_stage_sema = api.uid_get_from_cstr("source.diagnostic.stage.swift.sema")!

    // MARK: Symbol Kinds

    decl_function_free = api.uid_get_from_cstr("source.lang.swift.decl.function.free")!
    ref_function_free = api.uid_get_from_cstr("source.lang.swift.ref.function.free")!
    decl_function_method_instance = api.uid_get_from_cstr("source.lang.swift.decl.function.method.instance")!
    ref_function_method_instance = api.uid_get_from_cstr("source.lang.swift.ref.function.method.instance")!
    decl_function_method_static = api.uid_get_from_cstr("source.lang.swift.decl.function.method.static")!
    ref_function_method_static = api.uid_get_from_cstr("source.lang.swift.ref.function.method.static")!
    decl_function_method_class = api.uid_get_from_cstr("source.lang.swift.decl.function.method.class")!
    ref_function_method_class = api.uid_get_from_cstr("source.lang.swift.ref.function.method.class")!
    decl_function_accessor_getter = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.getter")!
    ref_function_accessor_getter = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.getter")!
    decl_function_accessor_setter = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.setter")!
    ref_function_accessor_setter = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.setter")!
    decl_function_accessor_willset = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.willset")!
    ref_function_accessor_willset = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.willset")!
    decl_function_accessor_didset = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.didset")!
    ref_function_accessor_didset = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.didset")!
    decl_function_accessor_address = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.address")!
    ref_function_accessor_address = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.address")!
    decl_function_accessor_mutableaddress = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.mutableaddress")!
    ref_function_accessor_mutableaddress = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.mutableaddress")!
    decl_function_accessor_read = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.read")!
    ref_function_accessor_read = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.read")!
    decl_function_accessor_modify = api.uid_get_from_cstr("source.lang.swift.decl.function.accessor.modify")!
    ref_function_accessor_modify = api.uid_get_from_cstr("source.lang.swift.ref.function.accessor.modify")!
    decl_function_constructor = api.uid_get_from_cstr("source.lang.swift.decl.function.constructor")!
    ref_function_constructor = api.uid_get_from_cstr("source.lang.swift.ref.function.constructor")!
    decl_function_destructor = api.uid_get_from_cstr("source.lang.swift.decl.function.destructor")!
    ref_function_destructor = api.uid_get_from_cstr("source.lang.swift.ref.function.destructor")!
    decl_function_operator_prefix = api.uid_get_from_cstr("source.lang.swift.decl.function.operator.prefix")!
    decl_function_operator_postfix = api.uid_get_from_cstr("source.lang.swift.decl.function.operator.postfix")!
    decl_function_operator_infix = api.uid_get_from_cstr("source.lang.swift.decl.function.operator.infix")!
    ref_function_operator_prefix = api.uid_get_from_cstr("source.lang.swift.ref.function.operator.prefix")!
    ref_function_operator_postfix = api.uid_get_from_cstr("source.lang.swift.ref.function.operator.postfix")!
    ref_function_operator_infix = api.uid_get_from_cstr("source.lang.swift.ref.function.operator.infix")!
    decl_precedencegroup = api.uid_get_from_cstr("source.lang.swift.decl.precedencegroup")!
    ref_precedencegroup = api.uid_get_from_cstr("source.lang.swift.ref.precedencegroup")!
    decl_function_subscript = api.uid_get_from_cstr("source.lang.swift.decl.function.subscript")!
    ref_function_subscript = api.uid_get_from_cstr("source.lang.swift.ref.function.subscript")!
    decl_var_global = api.uid_get_from_cstr("source.lang.swift.decl.var.global")!
    ref_var_global = api.uid_get_from_cstr("source.lang.swift.ref.var.global")!
    decl_var_instance = api.uid_get_from_cstr("source.lang.swift.decl.var.instance")!
    ref_var_instance = api.uid_get_from_cstr("source.lang.swift.ref.var.instance")!
    decl_var_static = api.uid_get_from_cstr("source.lang.swift.decl.var.static")!
    ref_var_static = api.uid_get_from_cstr("source.lang.swift.ref.var.static")!
    decl_var_class = api.uid_get_from_cstr("source.lang.swift.decl.var.class")!
    ref_var_class = api.uid_get_from_cstr("source.lang.swift.ref.var.class")!
    decl_var_local = api.uid_get_from_cstr("source.lang.swift.decl.var.local")!
    ref_var_local = api.uid_get_from_cstr("source.lang.swift.ref.var.local")!
    decl_var_parameter = api.uid_get_from_cstr("source.lang.swift.decl.var.parameter")!
    decl_module = api.uid_get_from_cstr("source.lang.swift.decl.module")!
    decl_class = api.uid_get_from_cstr("source.lang.swift.decl.class")!
    ref_class = api.uid_get_from_cstr("source.lang.swift.ref.class")!
    decl_struct = api.uid_get_from_cstr("source.lang.swift.decl.struct")!
    ref_struct = api.uid_get_from_cstr("source.lang.swift.ref.struct")!
    decl_enum = api.uid_get_from_cstr("source.lang.swift.decl.enum")!
    ref_enum = api.uid_get_from_cstr("source.lang.swift.ref.enum")!
    decl_enumcase = api.uid_get_from_cstr("source.lang.swift.decl.enumcase")!
    decl_enumelement = api.uid_get_from_cstr("source.lang.swift.decl.enumelement")!
    ref_enumelement = api.uid_get_from_cstr("source.lang.swift.ref.enumelement")!
    decl_protocol = api.uid_get_from_cstr("source.lang.swift.decl.protocol")!
    ref_protocol = api.uid_get_from_cstr("source.lang.swift.ref.protocol")!
    decl_extension = api.uid_get_from_cstr("source.lang.swift.decl.extension")!
    decl_extension_struct = api.uid_get_from_cstr("source.lang.swift.decl.extension.struct")!
    decl_extension_class = api.uid_get_from_cstr("source.lang.swift.decl.extension.class")!
    decl_extension_enum = api.uid_get_from_cstr("source.lang.swift.decl.extension.enum")!
    decl_extension_protocol = api.uid_get_from_cstr("source.lang.swift.decl.extension.protocol")!
    decl_associatedtype = api.uid_get_from_cstr("source.lang.swift.decl.associatedtype")!
    ref_associatedtype = api.uid_get_from_cstr("source.lang.swift.ref.associatedtype")!
    decl_typealias = api.uid_get_from_cstr("source.lang.swift.decl.typealias")!
    ref_typealias = api.uid_get_from_cstr("source.lang.swift.ref.typealias")!
    decl_generic_type_param = api.uid_get_from_cstr("source.lang.swift.decl.generic_type_param")!
    ref_generic_type_param = api.uid_get_from_cstr("source.lang.swift.ref.generic_type_param")!
    ref_module = api.uid_get_from_cstr("source.lang.swift.ref.module")!
    syntaxtype_comment = api.uid_get_from_cstr("source.lang.swift.syntaxtype.comment")!
    syntaxtype_comment_marker = api.uid_get_from_cstr("source.lang.swift.syntaxtype.comment.mark")!
    syntaxtype_comment_url = api.uid_get_from_cstr("source.lang.swift.syntaxtype.comment.url")!
    syntaxtype_doccomment = api.uid_get_from_cstr("source.lang.swift.syntaxtype.doccomment")!
    syntaxtype_doccomment_field = api.uid_get_from_cstr("source.lang.swift.syntaxtype.doccomment.field")!
    expr_object_literal = api.uid_get_from_cstr("source.lang.swift.expr.object_literal")!

    kind_keyword = api.uid_get_from_cstr("source.lang.swift.keyword")!
  }
}

final class SKRequestDictionary {
  let dict: sourcekitd_object_t?
  let sourcekitd: SwiftSourceKitFramework

  init(_ dict: sourcekitd_object_t? = nil, sourcekitd: SwiftSourceKitFramework) {
    self.dict = dict ?? sourcekitd.api.request_dictionary_create(nil, nil, 0)
    self.sourcekitd = sourcekitd
  }

  deinit {
    sourcekitd.api.request_release(dict)
  }

  subscript(key: sourcekitd_uid_t?) -> String {
    get { fatalError("request is set-only") }
    set { sourcekitd.api.request_dictionary_set_string(dict, key, newValue) }
  }
  subscript(key: sourcekitd_uid_t?) -> Int {
    get { fatalError("request is set-only") }
    set { sourcekitd.api.request_dictionary_set_int64(dict, key, Int64(newValue)) }
  }
  subscript(key: sourcekitd_uid_t?) -> sourcekitd_uid_t? {
    get { fatalError("request is set-only") }
    set { sourcekitd.api.request_dictionary_set_uid(dict, key, newValue) }
  }
  subscript(key: sourcekitd_uid_t?) -> SKRequestDictionary {
    get { fatalError("request is set-only") }
    set { sourcekitd.api.request_dictionary_set_value(dict, key, newValue.dict) }
  }
  subscript<S>(key: sourcekitd_uid_t?) -> S where S: Sequence, S.Element == String {
    get { fatalError("request is set-only") }
    set {
      let array = SKRequestArray(sourcekitd: sourcekitd)
      newValue.forEach { array.append($0) }
      sourcekitd.api.request_dictionary_set_value(dict, key, array.array)
    }
  }
  subscript(key: sourcekitd_uid_t?) -> SKRequestArray {
    get { fatalError("request is set-only") }
    set { sourcekitd.api.request_dictionary_set_value(dict, key, newValue.array) }
  }
}

final class SKRequestArray {
  let array: sourcekitd_object_t?
  let sourcekitd: SwiftSourceKitFramework

  init(_ array: sourcekitd_object_t? = nil, sourcekitd: SwiftSourceKitFramework) {
    self.array = array ?? sourcekitd.api.request_array_create(nil, 0)
    self.sourcekitd = sourcekitd
  }

  deinit {
    sourcekitd.api.request_release(array)
  }

  func append(_ value: String) {
    sourcekitd.api.request_array_set_string(array, -1, value)
  }
}

final class SKResponse {
  let response: sourcekitd_response_t?
  let sourcekitd: SwiftSourceKitFramework

  init(_ response: sourcekitd_response_t?, sourcekitd: SwiftSourceKitFramework) {
    self.response = response
    self.sourcekitd = sourcekitd
  }

  deinit {
    sourcekitd.api.response_dispose(response)
  }

  var error: ResponseError? {
    if !sourcekitd.api.response_is_error(response) {
      return nil
    }
    switch sourcekitd.api.response_error_get_kind(response) {
    case SOURCEKITD_ERROR_REQUEST_CANCELLED:
      return .cancelled
    default:
      return .unknown(description)
    }
  }

  var value: SKResponseDictionary? {
    if sourcekitd.api.response_is_error(response) {
      return nil
    }
    return SKResponseDictionary(sourcekitd.api.response_get_value(response), response: self)
  }
}

final class SKResponseDictionary {
  let dict: sourcekitd_variant_t
  let resp: SKResponse
  var sourcekitd: SwiftSourceKitFramework { return resp.sourcekitd }

  init(_ dict: sourcekitd_variant_t, response: SKResponse) {
    self.dict = dict
    self.resp = response
  }

  subscript(key: sourcekitd_uid_t?) -> String? {
    return sourcekitd.api.variant_dictionary_get_string(dict, key).map(String.init(cString:))
  }
  subscript(key: sourcekitd_uid_t?) -> Int? {
    return Int(sourcekitd.api.variant_dictionary_get_int64(dict, key))
  }
  subscript(key: sourcekitd_uid_t?) -> sourcekitd_uid_t? {
    return sourcekitd.api.variant_dictionary_get_uid(dict, key)
  }
  subscript(key: sourcekitd_uid_t?) -> SKResponseArray? {
    return SKResponseArray(sourcekitd.api.variant_dictionary_get_value(dict, key), response: resp)
  }
}

final class SKResponseArray {
  let array: sourcekitd_variant_t
  let resp: SKResponse
  var sourcekitd: SwiftSourceKitFramework { return resp.sourcekitd }

  init(_ array: sourcekitd_variant_t, response: SKResponse) {
    self.array = array
    self.resp = response
  }

  var count: Int { return sourcekitd.api.variant_array_get_count(array) }

  /// If the `applier` returns `false`, iteration terminates.
  @discardableResult
  func forEach(_ applier: (Int, SKResponseDictionary) -> Bool) -> Bool {
    for i in 0..<count {
      if !applier(i, SKResponseDictionary(sourcekitd.api.variant_array_get_value(array, i), response: resp)) {
        return false
      }
    }
    return true
  }
}

extension SKRequestDictionary: CustomStringConvertible {
  var description: String {
    let ptr = sourcekitd.api.request_description_copy(dict)!
    defer { free(ptr) }
    return String(cString: ptr)
  }
}

extension SKRequestArray: CustomStringConvertible {
  var description: String {
    let ptr = sourcekitd.api.request_description_copy(array)!
    defer { free(ptr) }
    return String(cString: ptr)
  }
}

extension SKResponse: CustomStringConvertible {
  var description: String {
    let ptr = sourcekitd.api.response_description_copy(response)!
    defer { free(ptr) }
    return String(cString: ptr)
  }
}

extension SKResponseDictionary: CustomStringConvertible {
  var description: String {
    let ptr = sourcekitd.api.variant_description_copy(dict)!
    defer { free(ptr) }
    return String(cString: ptr)
  }
}

extension SKResponseArray: CustomStringConvertible {
  var description: String {
    let ptr = sourcekitd.api.variant_description_copy(array)!
    defer { free(ptr) }
    return String(cString: ptr)
  }
}
