Generated Kotlin code with member `adapter` is hiding supertype member

## About:
If the proto definition contains field named `adapter` it will cause that the generated Kotlin code will contain conflicting member.

This is caused by the fact that `nameAllocatorStore` in `KotlinGenerator` is [set only with the constant variant (uppercase)](https://github.com/square/wire/blob/7aab9195bf1c898d355b117a9b5e3c954219789f/wire-library/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt#L257) of the member name but in the supertype `Message` it is defined lowercase.

## Example proto:
```proto
message Broken {

    // This will hide supertype member
    optional string adapter = 1;

    // This is changed to `unknownFields_`
    repeated string unknownFields = 2;
}
```

This issue is present in current master (3.2.0-SNAPSHOT).

diff --git a/wire-library/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt b/wire-library/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
index bf2aabc29..a170f509d 100644
--- a/wire-library/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
+++ b/wire-library/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
@@ -295,6 +295,7 @@ class KotlinGenerator private constructor(
           is MessageType -> {
             newName("unknownFields", "unknownFields")
             newName("ADAPTER", "ADAPTER")
+            newName("adapter", "adapter")
             newName("reader", "reader")
             newName("Builder", "Builder")
             newName("builder", "builder")
diff --git a/wire-library/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt b/wire-library/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt
index 366be2fd8..91e18784a 100644
--- a/wire-library/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt
+++ b/wire-library/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt
@@ -76,10 +76,12 @@ class KotlinGeneratorTest {
         |message Message {
         |  required float when = 1;
         |  required int32 ADAPTER = 2;
+        |  optional int64 adapter = 3;
         |}""".trimMargin())
     val code = repoBuilder.generateKotlin("Message")
     assertTrue(code.contains("val when_: Float"))
     assertTrue(code.contains("val ADAPTER_: Int"))
+    assertTrue(code.contains("val adapter_: Long?"))
     assertTrue(code.contains("ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.when_) +"))
     assertTrue(code.contains("ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.when_)"))
     assertTrue(code.contains("ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.when_)"))

Generated Kotlin code with member `adapter` is hiding supertype member
## About:
If the proto definition contains field named `adapter` it will cause that the generated Kotlin code will contain conflicting member.

This is caused by the fact that `nameAllocatorStore` in `KotlinGenerator` is [set only with the constant variant (uppercase)](https://github.com/square/wire/blob/7aab9195bf1c898d355b117a9b5e3c954219789f/wire-library/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt#L257) of the member name but in the supertype `Message` it is defined lowercase.

## Example proto:
```proto
message Broken {

    // This will hide supertype member
    optional string adapter = 1;

    // This is changed to `unknownFields_`
    repeated string unknownFields = 2;
}
```

This issue is present in current master (3.2.0-SNAPSHOT).

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.sample

import com.google.common.base.Stopwatch
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.TypeSpec
import com.squareup.wire.java.JavaGenerator
import com.squareup.wire.schema.Location
import com.squareup.wire.schema.PruningRules
import com.squareup.wire.schema.Schema
import com.squareup.wire.schema.SchemaLoader
import java.io.File
import java.io.IOException

/**
 * Reads in .proto files, links ’em together, prunes ’em, and emits both types and services. This
 * can be used to either complement or replace `WireCompiler`.
 */
class CodegenSample(
  private val log: Log,
  private val sources: Set<String>,
  private val protos: Set<String>,
  private val generatedSourceDirectory: String,
  private val pruningRules: PruningRules
) {
  @Throws(IOException::class)
  fun execute() {
    var schema = loadSchema()

    if (!pruningRules.isEmpty) {
      schema = retainRoots(schema)
    }

    val javaGenerator = JavaGenerator.get(schema)
    val serviceGenerator = ServiceGenerator(javaGenerator)
    for (protoFile in schema.protoFiles) {
      for (type in protoFile.types) {
        val stopwatch = Stopwatch.createStarted()
        val typeSpec = javaGenerator.generateType(type)
        val javaTypeName = javaGenerator.typeName(type.type) as ClassName
        writeJavaFile(javaTypeName, typeSpec, type.location, stopwatch)
      }
      for (service in protoFile.services) {
        val stopwatch = Stopwatch.createStarted()
        val javaTypeName = javaGenerator.typeName(service.type()) as ClassName
        val typeSpec = serviceGenerator.api(service)
        writeJavaFile(javaTypeName, typeSpec, service.location(), stopwatch)
      }
    }
  }

  @Throws(IOException::class)
  private fun loadSchema(): Schema {
    val stopwatch = Stopwatch.createStarted()
    val loader = SchemaLoader()
    for (source in sources) {
      loader.addSource(File(source))
    }
    for (proto in protos) {
      loader.addProto(proto)
    }
    val schema = loader.load()
    log.info("Loaded %s proto files in %s", schema.protoFiles.size, stopwatch)
    return schema
  }

  private fun retainRoots(schema: Schema): Schema {
    val stopwatch = Stopwatch.createStarted()
    val oldSize = countTypes(schema)

    val prunedSchema = schema.prune(pruningRules)
    val newSize = countTypes(prunedSchema)

    log.info("Pruned schema from %s types to %s types in %s", oldSize, newSize, stopwatch)

    return prunedSchema
  }

  @Throws(IOException::class)
  private fun writeJavaFile(
    javaTypeName: ClassName,
    typeSpec: TypeSpec,
    location: Location?,
    stopwatch: Stopwatch
  ) {
    val builder = JavaFile.builder(javaTypeName.packageName(), typeSpec)
          .addFileComment("Code generated by \$L, do not edit.", CodegenSample::class.java.name)
    if (location != null) {
      builder.addFileComment("\nSource file: \$L", location.path)
    }
    val javaFile = builder.build()
    try {
      javaFile.writeTo(File(generatedSourceDirectory))
    } catch (e: IOException) {
      throw IOException(
          "Failed to write ${javaFile.packageName}.${javaFile.typeSpec.name} to $generatedSourceDirectory", e
      )
    }
    log.info("Generated %s in %s", javaTypeName, stopwatch)
  }

  private fun countTypes(prunedSchema: Schema): Int {
    var result = 0
    for (protoFile in prunedSchema.protoFiles) {
      result += protoFile.types.size
    }
    return result
  }

  interface Log {
    fun info(
      format: String,
      vararg args: Any
    )
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.javapoet.JavaFile
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.wire.schema.ProtoType
import java.nio.file.Path

internal class ConsoleWireLogger : WireLogger {
  private var quiet: Boolean = false

  override fun setQuiet(quiet: Boolean) {
    this.quiet = quiet
  }

  override fun info(message: String) {
    if (!quiet) {
      println(message)
    }
  }

  override fun artifact(outputPath: Path, filePath: String) {
    if (quiet) {
      println(filePath)
    } else {
      println("Writing $filePath to $outputPath")
    }
  }

  override fun artifact(outputPath: Path, javaFile: JavaFile) {
    if (quiet) {
      println("${javaFile.packageName}.${javaFile.typeSpec.name}")
    } else {
      println("Writing ${javaFile.packageName}.${javaFile.typeSpec.name} to $outputPath")
    }
  }

  override fun artifact(outputPath: Path, kotlinFile: FileSpec) {
    val typeSpec = kotlinFile.members.first() as TypeSpec
    if (quiet) {
      println("${kotlinFile.packageName}.${typeSpec.name}")
    } else {
      println("Writing ${kotlinFile.packageName}.${typeSpec.name} to $outputPath")
    }
  }

  override fun artifactSkipped(type: ProtoType) {
    println("Skipping $type")
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.javapoet.JavaFile
import com.squareup.wire.java.JavaGenerator
import java.io.IOException
import java.nio.file.FileSystem
import java.util.concurrent.Callable
import java.util.concurrent.ConcurrentLinkedQueue

internal class JavaFileWriter(
  private val destination: String,
  private val javaGenerator: JavaGenerator,
  private val queue: ConcurrentLinkedQueue<PendingTypeFileSpec>,
  private val dryRun: Boolean,
  private val fs: FileSystem,
  private val log: WireLogger
) : Callable<Unit> {

  @Throws(IOException::class)
  override fun call() {
    while (true) {
      val type = queue.poll()?.type ?: return

      val typeSpec = javaGenerator.generateType(type)
      val javaTypeName = javaGenerator.generatedTypeName(type)
      val javaFile = JavaFile.builder(javaTypeName.packageName(), typeSpec)
          .addFileComment("\$L", WireCompiler.CODE_GENERATED_BY_WIRE)
          .apply {
            addFileComment("\nSource file: \$L", type.location.withPathOnly())
          }.build()

      val path = fs.getPath(destination)
      log.artifact(path, javaFile)
      if (dryRun) return

      try {
        javaFile.writeTo(path)
      } catch (e: IOException) {
        throw IOException(
            "Error emitting ${javaFile.packageName}.${javaFile.typeSpec.name} to $destination", e)
      }
    }
  }
}

/*
 * Copyright 2018 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.wire.kotlin.KotlinGenerator
import com.squareup.wire.schema.Location
import com.squareup.wire.schema.Service
import com.squareup.wire.schema.Type
import java.io.IOException
import java.nio.file.FileSystem
import java.util.concurrent.Callable
import java.util.concurrent.ConcurrentLinkedQueue

internal class KotlinFileWriter(
  private val destination: String,
  private val kotlinGenerator: KotlinGenerator,
  private val queue: ConcurrentLinkedQueue<PendingFileSpec>,
  private val fs: FileSystem,
  private val log: WireLogger,
  private val dryRun: Boolean
) : Callable<Unit> {

  @Throws(IOException::class)
  override fun call() {
    while (true) {
      val pendingFile = queue.poll() ?: return
      val kotlinFile = when (pendingFile) {
        is PendingTypeFileSpec -> generateFileForType(pendingFile.type)
        is PendingServiceFileSpec -> generateFileForService(pendingFile.service)
      }

      val path = fs.getPath(destination)
      log.artifact(path, kotlinFile)
      if (dryRun) return

      try {
        kotlinFile.writeTo(path)
      } catch (e: IOException) {
        val className = when (pendingFile) {
          is PendingTypeFileSpec ->
            kotlinGenerator.generatedTypeName(pendingFile.type).canonicalName
          is PendingServiceFileSpec ->
            pendingFile.service.type().toString()
        }
        throw IOException(
            "Error emitting ${kotlinFile.packageName}.$className to $destination",
            e)
      }
    }
  }

  private fun generateFileForType(type: Type): FileSpec {
    return generateFile(
        packageName = kotlinGenerator.generatedTypeName(type).packageName,
        typeSpec = kotlinGenerator.generateType(type),
        location = type.location
    )
  }

  private fun generateFileForService(service: Service): FileSpec {
    return generateFile(
        packageName = kotlinGenerator.generatedServiceName(service).packageName,
        typeSpec = kotlinGenerator.generateService(service),
        location = service.location()
    )
  }

  private fun generateFile(packageName: String, typeSpec: TypeSpec, location: Location?): FileSpec {
    return FileSpec.builder(packageName, typeSpec.name!!)
        .addComment(WireCompiler.CODE_GENERATED_BY_WIRE)
        .indent("  ")
        .apply {
          if (location != null) {
            addComment("\nSource file: %L", location.withPathOnly())
          }
        }
        .addType(typeSpec)
        .build()
  }
}

package com.squareup.wire

import com.squareup.wire.schema.Service
import com.squareup.wire.schema.Type

internal sealed class PendingFileSpec
internal data class PendingTypeFileSpec(val type: Type) : PendingFileSpec() {
  override fun toString() = type.type.toString()
}
internal data class PendingServiceFileSpec(val service: Service) : PendingFileSpec() {
  override fun toString() = service.type().toString()
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.java.JavaGenerator
import com.squareup.wire.java.ProfileLoader
import com.squareup.wire.kotlin.KotlinGenerator
import com.squareup.wire.schema.PruningRules
import com.squareup.wire.schema.SchemaLoader
import com.squareup.wire.schema.Service
import com.squareup.wire.schema.Type
import okio.buffer
import okio.source
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.nio.file.FileSystem
import java.nio.file.FileSystems
import java.util.ArrayList
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executors
import java.util.concurrent.Future

/**
 * Command line interface to the Wire Java generator.
 *
 * Usage
 * -----
 *
 * ```
 * java WireCompiler --proto_path=<path>
 *   [--java_out=<path>]
 *   [--kotlin_out=<path>]
 *   [--files=<protos.include>]
 *   [--includes=<message_name>[,<message_name>...]]
 *   [--excludes=<message_name>[,<message_name>...]]
 *   [--quiet]
 *   [--dry_run]
 *   [--android]
 *   [--android-annotations]
 *   [--compact]
 *   [file [file...]]
 * ```
 *
 * `--java_out` should provide the folder where the files generated by the Java code generator
 * should be placed. Similarly, `--kotlin_out` should provide the folder where the files generated
 * by the Kotlin code generator will be written. Only one of the two should be specified.
 *
 * If the `--includes` flag is present, its argument must be a comma-separated list of
 * fully-qualified message or enum names. The output will be limited to those messages and enums
 * that are (transitive) dependencies of the listed names. The `--excludes` flag excludes types, and
 * takes precedence over `--includes`.
 *
 * If the `--registry_class` flag is present, its argument must be a Java class name. A class with
 * the given name will be generated, containing a constant list of all extension classes generated
 * during the compile. This list is suitable for passing to Wire's constructor at runtime for
 * constructing its internal extension registry.
 *
 * If `--quiet` is specified, diagnostic messages to stdout are suppressed.
 *
 * The `--dry_run` flag causes the compile to just emit the names of the source files that would be
 * generated to stdout.
 *
 * The `--android` flag will cause all messages to implement the `Parcelable`
 * interface. This implies `--android-annotations` as well.
 *
 * The `--android-annotations` flag will add the `Nullable` annotation to optional fields.
 *
 * The `--compact` flag will emit code that uses reflection for reading, writing, and
 * toString methods which are normally implemented with code generation.
 */
class WireCompiler internal constructor(
  val fs: FileSystem,
  val log: WireLogger,
  val protoPaths: List<String>,
  val javaOut: String?,
  val kotlinOut: String?,
  val sourceFileNames: List<String>,
  val pruningRules: PruningRules,
  val dryRun: Boolean,
  val namedFilesOnly: Boolean,
  val emitAndroid: Boolean,
  val emitAndroidAnnotations: Boolean,
  val emitCompact: Boolean,
  val javaInterop: Boolean
) {

  @Throws(IOException::class)
  fun compile() {
    val schemaLoader = SchemaLoader()
    for (protoPath in protoPaths) {
      schemaLoader.addSource(fs.getPath(protoPath))
    }
    for (sourceFileName in sourceFileNames) {
      schemaLoader.addProto(sourceFileName)
    }
    var schema = schemaLoader.load()

    if (!pruningRules.isEmpty) {
      log.info("Analyzing dependencies of root types.")
      schema = schema.prune(pruningRules)
      for (rule in pruningRules.unusedRoots()) {
        log.info("Unused include: $rule")
      }
      for (rule in pruningRules.unusedPrunes()) {
        log.info("Unused exclude: $rule")
      }
    }

    /** Queue which can contain both [Type]s and [Service]s. */
    val queue = ConcurrentLinkedQueue<PendingFileSpec>()
    for (protoFile in schema.protoFiles) {
      // Check if we're skipping files not explicitly named.
      if (!sourceFileNames.isEmpty() && protoFile.location.path !in sourceFileNames) {
        if (namedFilesOnly || protoFile.location.path == DESCRIPTOR_PROTO) continue
      }
      if (protoFile.location.path == ANY_PROTO) continue
      queue.addAll(protoFile.types.map(::PendingTypeFileSpec))
      queue.addAll(protoFile.services.map(::PendingServiceFileSpec))
    }

    val executor = Executors.newCachedThreadPool()
    val futures = ArrayList<Future<Unit>>(MAX_WRITE_CONCURRENCY)

    when {
      javaOut != null -> {
        val profileName = if (emitAndroid) "android" else "java"
        val profile = ProfileLoader(profileName)
            .schema(schema)
            .load()

        val javaGenerator = JavaGenerator.get(schema)
            .withProfile(profile)
            .withAndroid(emitAndroid)
            .withAndroidAnnotations(emitAndroidAnnotations)
            .withCompact(emitCompact)

        // No services for Java.
        val types = ConcurrentLinkedQueue(queue.filterIsInstance<PendingTypeFileSpec>())
        for (i in 0 until MAX_WRITE_CONCURRENCY) {
          val task = JavaFileWriter(javaOut, javaGenerator, types, dryRun, fs, log)
          futures.add(executor.submit(task))
        }
      }

      kotlinOut != null -> {
        val kotlinGenerator = KotlinGenerator(schema, emitAndroid, javaInterop)

        for (i in 0 until MAX_WRITE_CONCURRENCY) {
          val task = KotlinFileWriter(kotlinOut, kotlinGenerator, queue, fs, log, dryRun)
          futures.add(executor.submit(task))
        }
      }

      else -> throw AssertionError()
    }

    executor.shutdown()

    try {
      for (future in futures) {
        future.get()
      }
    } catch (e: ExecutionException) {
      throw IOException(e.message, e)
    } catch (e: InterruptedException) {
      throw RuntimeException(e.message, e)
    }
  }

  companion object {
    const val CODE_GENERATED_BY_WIRE =
        "Code generated by Wire protocol buffer compiler, do not edit."

    private const val PROTO_PATH_FLAG = "--proto_path="
    private const val JAVA_OUT_FLAG = "--java_out="
    private const val KOTLIN_OUT_FLAG = "--kotlin_out="
    private const val FILES_FLAG = "--files="
    private const val INCLUDES_FLAG = "--includes="
    private const val EXCLUDES_FLAG = "--excludes="
    private const val QUIET_FLAG = "--quiet"
    private const val DRY_RUN_FLAG = "--dry_run"
    private const val NAMED_FILES_ONLY = "--named_files_only"
    private const val ANDROID = "--android"
    private const val ANDROID_ANNOTATIONS = "--android-annotations"
    private const val COMPACT = "--compact"
    private const val JAVA_INTEROP = "--java_interop"
    private const val MAX_WRITE_CONCURRENCY = 8
    private const val DESCRIPTOR_PROTO = "google/protobuf/descriptor.proto"
    private const val ANY_PROTO = "google/protobuf/any.proto"

    @Throws(IOException::class)
    @JvmStatic fun main(args: Array<String>) {
      try {
        val wireCompiler = forArgs(args = *args)
        wireCompiler.compile()
      } catch (e: WireException) {
        System.err.print("Fatal: ")
        e.printStackTrace(System.err)
        System.exit(1)
      }
    }

    @Throws(WireException::class)
    @JvmOverloads
    @JvmStatic
    fun forArgs(
      fileSystem: FileSystem = FileSystems.getDefault(),
      logger: WireLogger = ConsoleWireLogger(),
      vararg args: String
    ): WireCompiler {
      val sourceFileNames = mutableListOf<String>()
      val pruningRulesBuilder = PruningRules.Builder()
      val protoPaths = mutableListOf<String>()
      var javaOut: String? = null
      var kotlinOut: String? = null
      var quiet = false
      var dryRun = false
      var namedFilesOnly = false
      var emitAndroid = false
      var emitAndroidAnnotations = false
      var emitCompact = false
      var javaInterop = false

      for (arg in args) {
        when {
          arg.startsWith(PROTO_PATH_FLAG) -> {
            protoPaths.add(arg.substring(PROTO_PATH_FLAG.length))
          }

          arg.startsWith(JAVA_OUT_FLAG) -> {
            check(javaOut == null) { "java_out already set" }
            javaOut = arg.substring(JAVA_OUT_FLAG.length)
          }

          arg.startsWith(KOTLIN_OUT_FLAG) -> {
            check(kotlinOut == null) { "kotlin_out already set" }
            kotlinOut = arg.substring(KOTLIN_OUT_FLAG.length)
          }

          arg.startsWith(FILES_FLAG) -> {
            val files = File(arg.substring(FILES_FLAG.length))
            try {
              files.source().buffer().use { source ->
                while (true) {
                  val line = source.readUtf8Line() ?: break
                  sourceFileNames.add(line)
                }
              }
            } catch (ex: FileNotFoundException) {
              throw WireException("Error processing argument $arg", ex)
            }
          }

          arg.startsWith(INCLUDES_FLAG) -> {
            val includes = arg.substring(INCLUDES_FLAG.length)
            pruningRulesBuilder.addRoot(includes.split(Regex(",")))
          }

          arg.startsWith(EXCLUDES_FLAG) -> {
            val excludes = arg.substring(EXCLUDES_FLAG.length)
            pruningRulesBuilder.prune(excludes.split(Regex(",")))
          }

          arg == QUIET_FLAG -> quiet = true
          arg == DRY_RUN_FLAG -> dryRun = true
          arg == NAMED_FILES_ONLY -> namedFilesOnly = true
          arg == ANDROID -> emitAndroid = true
          arg == ANDROID_ANNOTATIONS -> emitAndroidAnnotations = true
          arg == COMPACT -> emitCompact = true
          arg == JAVA_INTEROP -> javaInterop = true
          arg.startsWith("--") -> throw IllegalArgumentException("Unknown argument '$arg'.")
          else -> sourceFileNames.add(arg)
        }
      }

      if ((javaOut != null) == (kotlinOut != null)) {
        throw WireException("Only one of $JAVA_OUT_FLAG or $KOTLIN_OUT_FLAG flag must be specified")
      }

      logger.setQuiet(quiet)

      return WireCompiler(fileSystem, logger, protoPaths, javaOut, kotlinOut, sourceFileNames,
          pruningRulesBuilder.build(), dryRun, namedFilesOnly, emitAndroid, emitAndroidAnnotations,
          emitCompact, javaInterop)
    }
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

internal class WireException : Exception {
  constructor(message: String) : super(message)

  constructor(message: String, throwable: Throwable) : super(message, throwable)
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.javapoet.JavaFile
import com.squareup.kotlinpoet.FileSpec
import com.squareup.wire.schema.ProtoType
import java.nio.file.Path

interface WireLogger {
  fun setQuiet(quiet: Boolean)
  fun artifact(outputPath: Path, filePath: String)
  fun artifact(outputPath: Path, javaFile: JavaFile)
  fun artifact(outputPath: Path, kotlinFile: FileSpec)
  fun artifactSkipped(type: ProtoType)
  fun info(message: String)
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.internal.Throws
import okio.IOException
import okio.Timeout

/**
 * A single call to a remote server. This call sends a single request value and receives a single
 * response value. A gRPC call cannot be executed twice.
 *
 * gRPC calls can be [suspending][execute], [blocking][executeBlocking], or
 * [asynchronous][enqueue]. Use whichever mechanism works at your call site: the bytes transmitted
 * on the network are the same.
 */
interface GrpcCall<S : Any, R : Any> {
  /** Configures how long the call can take to complete before it is automatically canceled. */
  val timeout: Timeout

  /**
   * Attempts to cancel the call. This function is safe to call concurrently with execution. When
   * canceled, execution fails with an immediate [IOException] rather than waiting to complete
   * normally.
   */
  fun cancel()

  /** True if [cancel] was called. */
  fun isCanceled(): Boolean

  /** Invokes the call immediately and suspends until its response is received. */
  @Throws(IOException::class)
  suspend fun execute(request: S): R

  /** Invokes the call immediately and blocks until its response is received. */
  @Throws(IOException::class)
  fun executeBlocking(request: S): R

  /**
   * Enqueues this call for asynchronous execution. The [callback] will be invoked on the client's
   * dispatcher thread when the call completes.
   */
  fun enqueue(request: S, callback: Callback<S, R>)

  /**
   * Returns true if [execute], [executeBlocking], or [enqueue] was called. It is an error to
   * execute or enqueue a call more than once.
   */
  fun isExecuted(): Boolean

  /**
   * Create a new, identical gRPC call to this one which can be enqueued or executed even if this
   * call has already been.
   */
  fun clone(): GrpcCall<S, R>

  interface Callback<S : Any, R : Any> {
    fun onFailure(call: GrpcCall<S, R>, exception: IOException)
    fun onSuccess(call: GrpcCall<S, R>, response: R)
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

expect class GrpcClient {
  fun <S : Any, R : Any> newCall(method: GrpcMethod<S, R>): GrpcCall<S, R>
  fun <S : Any, R : Any> newStreamingCall(method: GrpcMethod<S, R>): GrpcStreamingCall<S, R>
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

class GrpcMethod<S : Any, R : Any>(
  val path: String,
  val requestAdapter: ProtoAdapter<S>,
  val responseAdapter: ProtoAdapter<R>
)

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import okio.IOException
import okio.Timeout

/**
 * A single streaming call to a remote server. This class handles three streaming call types:
 *
 *  * Single request, streaming response. The send channel or message sink accept exactly one
 *    message. The receive channel or message source produce zero or more messages. The outbound
 *    request message is sent before any inbound response messages.
 *
 *  * Streaming request, single response. The send channel or message sink accept zero or more
 *    messages. The receive channel or message source produce exactly one message. All outbound
 *    request messages are sent before the inbound response message.
 *
 *  * Streaming request, streaming response. The send channel or message sink accept zero or more
 *    messages, and the receive channel or message source produce any number of messages. Unlike
 *    the above two types, you are free to interleave request and response messages.
 *
 * A gRPC call cannot be executed twice.
 *
 * gRPC calls can be [suspending][execute] or [blocking][executeBlocking]. Use whichever mechanism
 * works at your call site: the bytes transmitted on the network are the same.
 */
interface GrpcStreamingCall<S : Any, R : Any> {
  /**
   * Configures how long the call can take to complete before it is automatically canceled. The
   * timeout applies to the full set of messages transmitted. For long-running streams you must
   * configure a sufficiently long timeout.
   */
  val timeout: Timeout

  /**
   * Attempts to cancel the call. This function is safe to call concurrently with execution. When
   * canceled, execution fails with an immediate [IOException] rather than waiting to complete
   * normally.
   */
  fun cancel()

  /** True if [cancel] was called. */
  fun isCanceled(): Boolean

  /**
   * Enqueues this call for execution and returns channels to send and receive the call's messages.
   * This uses the [Dispatchers.IO] to transmit outbound messages.
   */
  fun execute(): Pair<SendChannel<S>, ReceiveChannel<R>>

  /**
   * Enqueues this call for execution and returns streams to send and receive the call's messages.
   * Reads and writes on the returned streams are blocking.
   */
  fun executeBlocking(): Pair<MessageSink<S>, MessageSource<R>>

  /**
   * Returns true if [execute] or [executeBlocking] was called. It is an error to execute a call
   * more than once.
   */
  fun isExecuted(): Boolean

  /**
   * Create a new, identical gRPC call to this one which can be enqueued or executed even if this
   * call has already been.
   */
  fun clone(): GrpcStreamingCall<S, R>
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */

package com.squareup.wire

import kotlinx.coroutines.ExperimentalCoroutinesApi

/**
 * Marker annotation for experimental Wire gRPC features.
 */
@ExperimentalCoroutinesApi
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class WireGrpcExperimental

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

actual class GrpcClient {
  actual fun <S : Any, R : Any> newCall(method: GrpcMethod<S, R>): GrpcCall<S, R> {
    throw UnsupportedOperationException("wire-grpc-client doesn't support JS yet.")
  }

  actual fun <S : Any, R : Any> newStreamingCall(
    method: GrpcMethod<S, R>
  ): GrpcStreamingCall<S, R> {
    throw UnsupportedOperationException("wire-grpc-client doesn't support JS yet.")
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.internal.RealGrpcCall
import com.squareup.wire.internal.RealGrpcStreamingCall
import okhttp3.Call
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody

actual class GrpcClient private constructor(
  internal val client: OkHttpClient,
  internal val baseUrl: HttpUrl
) {
  actual fun <S : Any, R : Any> newCall(method: GrpcMethod<S, R>): GrpcCall<S, R> {
    return RealGrpcCall(this, method)
  }

  actual fun <S : Any, R : Any> newStreamingCall(
    method: GrpcMethod<S, R>
  ): GrpcStreamingCall<S, R> {
    return RealGrpcStreamingCall(this, method)
  }

  internal fun newCall(path: String, requestBody: RequestBody): Call {
    return client.newCall(Request.Builder()
        .url(baseUrl.resolve(path)!!)
        .addHeader("te", "trailers")
        .addHeader("grpc-trace-bin", "")
        .addHeader("grpc-accept-encoding", "gzip")
        .addHeader("grpc-encoding", "gzip")
        .method("POST", requestBody)
        .build())
  }

  class Builder {
    private var client: OkHttpClient? = null
    private var baseUrl: HttpUrl? = null

    fun client(client: OkHttpClient): Builder {
      this.client = client
      return this
    }

    fun baseUrl(baseUrl: String): Builder {
      this.baseUrl = baseUrl.toHttpUrl()
      return this
    }

    fun baseUrl(url: HttpUrl): Builder {
      this.baseUrl = url
      return this
    }

    fun build(): GrpcClient {
      return GrpcClient(client = client!!, baseUrl = baseUrl!!)
    }
  }
}

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

actual class GrpcClient {
  actual fun <S : Any, R : Any> newCall(method: GrpcMethod<S, R>): GrpcCall<S, R> {
    throw UnsupportedOperationException("wire-grpc-client doesn't support Native yet.")
  }

  actual fun <S : Any, R : Any> newStreamingCall(
    method: GrpcMethod<S, R>
  ): GrpcStreamingCall<S, R> {
    throw UnsupportedOperationException("wire-grpc-client doesn't support Native yet.")
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import okio.ByteString
import okio.ByteString.Companion.decodeBase64
import java.io.IOException

/**
 * A [TypeAdapter] that may be used to serialize and deserialize [ByteString] values using the GSON
 * Json library. The byte data is serialized in Base64 format.
 */
internal class ByteStringTypeAdapter : TypeAdapter<ByteString>() {

  @Throws(IOException::class)
  override fun write(out: JsonWriter, value: ByteString?) {
    if (value == null) {
      out.nullValue()
    } else {
      out.value(value.base64())
    }
  }

  @Throws(IOException::class)
  override fun read(input: JsonReader): ByteString? {
    if (input.peek() == JsonToken.NULL) {
      input.nextNull()
      return null
    }
    return input.nextString().decodeBase64()
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import com.squareup.wire.WireField.Label
import com.squareup.wire.internal.FieldBinding
import com.squareup.wire.internal.RuntimeMessageAdapter
import java.io.IOException
import java.math.BigInteger

// 2^64, used to convert sint64 values >= 2^63 to unsigned decimal form
private val POWER_64 = BigInteger("18446744073709551616")

internal class MessageTypeAdapter<M : Message<M, B>, B : Message.Builder<M, B>>(
  private val gson: Gson,
  type: TypeToken<M>
) : TypeAdapter<M>() {
  private val messageAdapter: RuntimeMessageAdapter<M, B> =
      RuntimeMessageAdapter.create(type.rawType as Class<M>, "square.github.io/wire/unknown")
  private val fieldBindings: Map<String, FieldBinding<M, B>> =
      messageAdapter.fieldBindings.values.associateBy { it.name }

  @Throws(IOException::class)
  override fun write(out: JsonWriter, message: M?) {
    if (message == null) {
      out.nullValue()
      return
    }

    out.beginObject()
    for (tagBinding in messageAdapter.fieldBindings.values) {
      val value = tagBinding[message] ?: continue
      out.name(tagBinding.name)
      emitJson(out, value, tagBinding.singleAdapter(), tagBinding.label)
    }
    out.endObject()
  }

  private fun emitJson(out: JsonWriter, value: Any, adapter: ProtoAdapter<*>, label: Label) {
    if (adapter === ProtoAdapter.UINT64) {
      if (label.isRepeated) {
        val longs = value as List<Long>
        out.beginArray()
        for (i in 0 until longs.size) {
          emitUint64(longs[i], out)
        }
        out.endArray()
      } else {
        emitUint64(value as Long, out)
      }
    } else {
      gson.toJson(value, value.javaClass, out)
    }
  }

  private fun emitUint64(value: Long, out: JsonWriter) {
    if (value < 0) {
      val unsigned = POWER_64.add(BigInteger.valueOf(value))
      out.value(unsigned)
    } else {
      out.value(value)
    }
  }

  @Throws(IOException::class)
  override fun read(input: JsonReader): M? {
    if (input.peek() == JsonToken.NULL) {
      input.nextNull()
      return null
    }

    val elementAdapter = gson.getAdapter(JsonElement::class.java)
    val builder = messageAdapter.newBuilder()

    input.beginObject()
    while (input.peek() != JsonToken.END_OBJECT) {
      val name = input.nextName()

      val fieldBinding = fieldBindings[name]
      if (fieldBinding == null) {
        input.skipValue()
      } else {
        val element = elementAdapter.read(input)
        val value = parseValue(fieldBinding, element)
        fieldBinding[builder] = value
      }
    }

    input.endObject()
    return builder.build()
  }

  private fun parseValue(fieldBinding: FieldBinding<*, *>, element: JsonElement): Any? {
    if (fieldBinding.label.isRepeated) {
      if (element.isJsonNull) {
        return emptyList<Any>()
      }
      val itemType = fieldBinding.singleAdapter().type!!.javaObjectType
      val adapter = gson.getAdapter(itemType)
      return element.asJsonArray.map(adapter::fromJsonTree)
    }

    if (fieldBinding.isMap) {
      if (element.isJsonNull) {
        return emptyMap<Any, Any>()
      }

      val keyType = fieldBinding.keyAdapter().type!!.javaObjectType
      val valueType = fieldBinding.singleAdapter().type!!.javaObjectType
      val valueAdapter = gson.getAdapter(valueType)

      val jsonObject = element.asJsonObject
      return jsonObject.entrySet().associateBy(
          { gson.fromJson(it.key, keyType) },
          { valueAdapter.fromJsonTree(it.value) }
      )
    }

    val elementType = fieldBinding.singleAdapter().type!!.javaObjectType
    return gson.fromJson(element, elementType)
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import okio.ByteString

/**
 * A [TypeAdapterFactory] that allows Wire messages to be serialized and deserialized
 * using the GSON Json library. To create a [Gson] instance that works with Wire,
 * use the [com.google.gson.GsonBuilder] interface:
 *
 * ```
 * Gson gson = new GsonBuilder()
 *     .registerTypeAdapterFactory(new WireTypeAdapterFactory())
 *     .create();
 * ```
 *
 * The resulting [Gson] instance will be able to serialize and deserialize any Wire
 * [Message] type, including extensions and unknown field values. The JSON encoding is
 * intended to be compatible with the
 * [protobuf-java-format](https://code.google.com/p/protobuf-java-format/)
 * library. Note that version 1.2 of that API has a
 * [bug](https://code.google.com/p/protobuf-java-format/issues/detail?id=47)
 * in the way it serializes unknown fields, so we use our own approach for this case.
 */
class WireTypeAdapterFactory : TypeAdapterFactory {
  override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
    return when {
      type.rawType == ByteString::class.java -> ByteStringTypeAdapter() as TypeAdapter<T>
      Message::class.java.isAssignableFrom(type.rawType) ->
        MessageTypeAdapter<Nothing, Nothing>(gson, type as TypeToken<Nothing>) as TypeAdapter<T>
      else -> null
    }
  }
}

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import java.io.IOException

internal class AnyMessageJsonAdapter(
  private val moshi: Moshi,
  private val typeUrlToAdapter: Map<String, ProtoAdapter<*>>
) : JsonAdapter<AnyMessage>() {

  @Throws(IOException::class)
  override fun toJson(writer: JsonWriter, value: AnyMessage?) {
    if (value == null) {
      writer.nullValue()
      return
    }
    writer.beginObject()

    writer.name("@type")
    writer.value(value.typeUrl)

    val protoAdapter = typeUrlToAdapter[value.typeUrl]
        ?: throw JsonDataException("Cannot find type for url: ${value.typeUrl} in ${writer.path}")

    val delegate = moshi.adapter(protoAdapter.type!!.java) as JsonAdapter<Message<*, *>>

    val flattenToken = writer.beginFlatten()
    delegate.toJson(writer, value.unpack(protoAdapter) as Message<*, *>)
    writer.endFlatten(flattenToken)
    writer.endObject()
  }

  @Throws(IOException::class)
  override fun fromJson(reader: JsonReader): AnyMessage? {
    if (reader.peek() == JsonReader.Token.NULL) {
      reader.nextNull<Any>()
      return null
    }

    val typeUrl = reader.peekJson().use { it.readStringNamed("@type") }
        ?: throw JsonDataException("expected @type in ${reader.path}")

    val protoAdapter = typeUrlToAdapter[typeUrl]
        ?: throw JsonDataException("Cannot resolve type: $typeUrl in ${reader.path}")

    val delegate = moshi.adapter(protoAdapter.type!!.java) as JsonAdapter<Message<*, *>>

    val value = delegate.fromJson(reader)

    return AnyMessage.pack(value!!)
  }

  /** Returns the string named [name] of an object, or null if no such property exists. */
  private fun JsonReader.readStringNamed(name: String): String? {
    beginObject()
    while (hasNext()) {
      if (nextName() == name) {
        return nextString()
      } else {
        skipValue()
      }
    }
    return null // No such field on 'reader'.
  }
}

/*
 * Copyright 2018 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.wire.internal.RuntimeMessageAdapter
import java.io.IOException
import java.lang.reflect.Type

internal class MessageJsonAdapter<M : Message<M, B>, B : Message.Builder<M, B>>(
  moshi: Moshi,
  type: Type
) : JsonAdapter<M>() {
  private val messageAdapter = RuntimeMessageAdapter.create(type as Class<M>, "square.github.io/wire/unknown")
  private val fieldBindings = messageAdapter.fieldBindings.values.toTypedArray()
  private val options = JsonReader.Options.of(*fieldBindings.map { it.name }.toTypedArray())

  private val jsonAdapters = fieldBindings.map { fieldBinding ->
    var fieldType: Type = fieldBinding.singleAdapter().type?.javaObjectType as Type
    if (fieldBinding.isMap) {
      val keyType = fieldBinding.keyAdapter().type?.javaObjectType
      fieldType = Types.newParameterizedType(Map::class.java, keyType, fieldType)
    } else if (fieldBinding.label.isRepeated) {
      fieldType = Types.newParameterizedType(List::class.java, fieldType)
    }
    var qualifiers: Array<Class<out Annotation>> = emptyArray()
    if (fieldBinding.singleAdapter() === ProtoAdapter.UINT64) {
      qualifiers = arrayOf(Uint64::class.java)
    }
    return@map moshi.adapter<Any>(fieldType, *qualifiers)
  }

  @Throws(IOException::class)
  override fun toJson(out: JsonWriter, message: M?) {
    if (message == null) {
      out.nullValue()
      return
    }
    out.beginObject()
    fieldBindings.forEachIndexed { index, fieldBinding ->
      out.name(fieldBinding.name)
      val value = fieldBinding[message]
      jsonAdapters[index]?.toJson(out, value)
    }
    out.endObject()
  }

  @Throws(IOException::class)
  override fun fromJson(input: JsonReader): M? {
    if (input.peek() == JsonReader.Token.NULL) {
      input.nextNull<Any>()
      return null
    }
    val builder = messageAdapter.newBuilder()
    input.beginObject()
    while (input.hasNext()) {
      val index = input.selectName(options)
      if (index == -1) {
        input.skipName()
        input.skipValue()
        continue
      }
      val fieldBinding = fieldBindings[index]
      if (fieldBinding == null) {
        input.skipValue()
        continue
      }
      val value = jsonAdapters[index]?.fromJson(input) ?: continue

      // If the value was explicitly null we ignore it rather than forcing null into the field.
      // Otherwise malformed JSON that sets a list to null will create a malformed message, and
      // we'd rather just ignore that problem.
      fieldBinding[builder] = value
    }
    input.endObject()
    return builder.build()
  }
}

package com.squareup.wire

import com.squareup.moshi.JsonQualifier

/**
 * Annotates longs that should be encoded as unsigned. For example, -1L is encoded as
 * 18446744073709551615.
 */
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
internal annotation class Uint64

/*
 * Copyright 2018 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import okio.ByteString
import okio.ByteString.Companion.decodeBase64
import java.io.IOException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.math.BigInteger
import java.util.ArrayList

/**
 * A [JsonAdapter.Factory] that allows Wire messages to be serialized and deserialized using the
 * Moshi Json library.
 *
 * ```
 * Moshi moshi = new Moshi.Builder()
 *     .add(new WireJsonAdapterFactory())
 *     .build();
 * ```
 *
 * The resulting [Moshi] instance will be able to serialize and deserialize Wire [Message] types,
 * including extensions. It ignores unknown field values. The JSON encoding is intended to be
 * compatible with the [protobuf-java-format](https://code.google.com/p/protobuf-java-format/)
 * library.
 */
class WireJsonAdapterFactory private constructor(
  private val typeUrlToAdapter: Map<String, ProtoAdapter<*>>
) : JsonAdapter.Factory {
  constructor() : this(mapOf())

  /**
   * Returns a new WireJsonAdapterFactory that can encode the messages for [adapters] if they're
   * used with [AnyMessage].
   */
  fun plus(adapters: List<ProtoAdapter<*>>): WireJsonAdapterFactory {
    val newMap = typeUrlToAdapter.toMutableMap()
    for (adapter in adapters) {
      val key = adapter.typeUrl ?: throw IllegalArgumentException(
          "recompile ${adapter.type} to use it with WireJsonAdapterFactory")
      newMap[key] = adapter
    }
    return WireJsonAdapterFactory(newMap)
  }

  /**
   * Returns a new WireJsonAdapterFactory that can encode the messages for [adapter] if they're
   * used with [AnyMessage].
   */
  fun plus(adapter: ProtoAdapter<*>): WireJsonAdapterFactory {
    return plus(listOf(adapter))
  }

  override fun create(
    type: Type,
    annotations: Set<Annotation>,
    moshi: Moshi
  ): JsonAdapter<*>? {
    if ((type === Long::class.javaObjectType || type === Long::class.javaPrimitiveType) &&
        Types.nextAnnotations(annotations, Uint64::class.java) != null) {
      return UINT64_JSON_ADAPTER
    }
    val rawType = Types.getRawType(type)
    if (rawType == List::class.java &&
        (type as ParameterizedType).actualTypeArguments[0] === Long::class.javaObjectType &&
        Types.nextAnnotations(annotations, Uint64::class.java) != null) {
      return LIST_OF_UINT64_JSON_ADAPTER
    }
    if (annotations.isNotEmpty()) {
      return null
    }
    if (rawType == ByteString::class.java) {
      return BYTE_STRING_JSON_ADAPTER
    }
    if (rawType == AnyMessage::class.java) {
      return AnyMessageJsonAdapter(moshi, typeUrlToAdapter)
    }
    return if (Message::class.java.isAssignableFrom(rawType)) {
      MessageJsonAdapter<Nothing, Nothing>(moshi, type)
    } else {
      null
    }
  }

  companion object {
    internal val BYTE_STRING_JSON_ADAPTER = object : JsonAdapter<ByteString>() {
      @Throws(IOException::class)
      override fun toJson(out: JsonWriter, byteString: ByteString?) {
        out.value(byteString?.base64())
      }

      @Throws(IOException::class)
      override fun fromJson(input: JsonReader): ByteString? {
        return input.nextString().decodeBase64()
      }
    }.nullSafe()

    /**
     * Wire uses the signed long type to store unsigned longs. Sigh. But when we encode as JSON we
     * need to emit an unsigned value.
     */
    internal val UINT64_JSON_ADAPTER = object : JsonAdapter<Long>() {
      // 2^64, used to convert sint64 values >= 2^63 to unsigned decimal form
      private val power64 = BigInteger("18446744073709551616")
      private val maxLong = BigInteger.valueOf(java.lang.Long.MAX_VALUE)

      @Throws(IOException::class)
      override fun fromJson(reader: JsonReader): Long? {
        val bigInteger = BigInteger(reader.nextString())
        return if (bigInteger.compareTo(maxLong) > 0)
          bigInteger.subtract(power64).toLong()
        else
          bigInteger.toLong()
      }

      @Throws(IOException::class)
      override fun toJson(writer: JsonWriter, value: Long?) {
        if (value!! < 0) {
          val unsigned = power64.add(BigInteger.valueOf(value))
          writer.value(unsigned)
        } else {
          writer.value(value)
        }
      }
    }.nullSafe()

    /**
     * Tragically Moshi doesn't know enough to follow a `@Uint64 List<Long>` really wants to be
     * treated as a `List<@Uint64 Long>` and so we have to do it manually.
     *
     * TODO delete when Moshi can handle that; see
     * [moshi/issues/666](https://github.com/square/moshi/issues/666)
     */
    internal val LIST_OF_UINT64_JSON_ADAPTER = object : JsonAdapter<List<Long>>() {
      @Throws(IOException::class)
      override fun fromJson(reader: JsonReader): List<Long>? {
        val result = ArrayList<Long>()
        reader.beginArray()
        while (reader.hasNext()) {
          result.add(UINT64_JSON_ADAPTER.fromJson(reader)!!)
        }
        reader.endArray()
        return result
      }

      @Throws(IOException::class)
      override fun toJson(writer: JsonWriter, value: List<Long>?) {
        writer.beginArray()
        for (v in value!!) {
          UINT64_JSON_ADAPTER.toJson(writer, v)
        }
        writer.endArray()
      }
    }.nullSafe()
  }
}

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import okio.ByteString
import kotlin.jvm.JvmField

/**
 * TODO(oldergod): documentation
 * TODO(oldergod): JSON
 */
class AnyMessage(
  val typeUrl: String,
  val value: ByteString = ByteString.EMPTY
) : Message<AnyMessage, Nothing>(ADAPTER, ByteString.EMPTY) {

  fun <T> unpack(adapter: ProtoAdapter<T>): T {
    check(typeUrl == adapter.typeUrl) {
      "type mismatch: $typeUrl != ${adapter.typeUrl}"
    }
    return adapter.decode(value)
  }

  fun <T> unpackOrNull(adapter: ProtoAdapter<T>): T? {
    return if (typeUrl == adapter.typeUrl) adapter.decode(value) else null
  }

  @Deprecated(
      message = "Shouldn't be used in Kotlin",
      level = DeprecationLevel.HIDDEN
  )
  override fun newBuilder(): Nothing = throw AssertionError()

  override fun equals(other: Any?): Boolean {
    if (other === this) return true
    if (other !is AnyMessage) return false
    return typeUrl == other.typeUrl && value == other.value
  }

  override fun hashCode(): Int {
    var result = super.hashCode
    if (result == 0) {
      result = result * 37 + typeUrl.hashCode()
      result = result * 37 + value.hashCode()
      super.hashCode = result
    }
    return result
  }

  override fun toString() = "Any{type_url=$typeUrl, value=$value}"

  fun copy(
    typeUrl: String = this.typeUrl,
    value: ByteString = this.value
  ) = AnyMessage(typeUrl, value)

  companion object {
    fun pack(message: Message<*, *>): AnyMessage {
      val typeUrl = message.adapter.typeUrl
          ?: error("recompile ${message::class} to use it with AnyMessage")
      return AnyMessage(typeUrl, message.encodeByteString())
    }

    @JvmField
    val ADAPTER: ProtoAdapter<AnyMessage> = object : ProtoAdapter<AnyMessage>(
        FieldEncoding.LENGTH_DELIMITED,
        AnyMessage::class,
        "type.googleapis.com/google.protobuf.Any"
    ) {
      override fun encodedSize(value: AnyMessage): Int =
          STRING.encodedSizeWithTag(1, value.typeUrl) +
              BYTES.encodedSizeWithTag(2, value.value)

      override fun encode(writer: ProtoWriter, value: AnyMessage) {
        STRING.encodeWithTag(writer, 1, value.typeUrl)
        BYTES.encodeWithTag(writer, 2, value.value)
      }

      override fun decode(reader: ProtoReader): AnyMessage {
        var typeUrl = "square.github.io/wire/unknown" // TODO: what does protoc do?
        var value = ByteString.EMPTY
        reader.forEachTag { tag ->
          when (tag) {
            1 -> typeUrl = STRING.decode(reader)
            2 -> value = BYTES.decode(reader)
            else -> reader.readUnknownField(tag)
          }
        }
        return AnyMessage(typeUrl = typeUrl, value = value)
      }

      // TODO: this is a hazard.
      override fun redact(value: AnyMessage) =
          AnyMessage("square.github.io/wire/redacted", ByteString.EMPTY)
    }
  }
}

/*
 * Copyright 2016 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.internal.Throws
import okio.IOException
import kotlin.reflect.KClass

/**
 * An abstract [ProtoAdapter] that converts values of an enum to and from integers.
 */
expect abstract class EnumAdapter<E : WireEnum> protected constructor(
  type: KClass<E>
) : ProtoAdapter<E> {
  override fun encodedSize(value: E): Int

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: E)

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): E

  override fun redact(value: E): E

  /**
   * Converts an integer to an enum.
   * Returns null if there is no corresponding enum.
   */
  protected abstract fun fromValue(value: Int): E?
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E : WireEnum> commonEncodedSize(value: E): Int {
  return ProtoWriter.varint32Size(value.value)
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E : WireEnum> commonEncode(writer: ProtoWriter, value: E) {
  writer.writeVarint32(value.value)
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E : WireEnum> EnumAdapter<E>.commonDecode(
  reader: ProtoReader,
  fromValue: (Int) -> E?
): E {
  val value = reader.readVarint32()
  return fromValue(value) ?: throw ProtoAdapter.EnumConstantNotFoundException(value, type)
}

@Suppress("NOTHING_TO_INLINE", "UNUSED_PARAMETER")
internal inline fun <E : WireEnum> commonRedact(value: E): E {
  throw UnsupportedOperationException()
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.internal.ProtocolException
import com.squareup.wire.internal.Throws
import okio.IOException
import kotlin.jvm.JvmStatic

enum class FieldEncoding(internal val value: Int) {
  VARINT(0), FIXED64(1), LENGTH_DELIMITED(2), FIXED32(5);

  /**
   * Returns a Wire adapter that reads this field encoding without interpretation. For example,
   * messages are returned as byte strings and enums are returned as integers.
   */
  fun rawProtoAdapter(): ProtoAdapter<*> = when (this) {
    VARINT -> ProtoAdapter.UINT64
    FIXED32 -> ProtoAdapter.FIXED32
    FIXED64 -> ProtoAdapter.FIXED64
    LENGTH_DELIMITED -> ProtoAdapter.BYTES
  }

  companion object {
    @JvmStatic
    @Throws(IOException::class)
    internal operator fun get(value: Int): FieldEncoding = when (value) {
      0 -> VARINT
      1 -> FIXED64
      2 -> LENGTH_DELIMITED
      5 -> FIXED32
      else -> throw ProtocolException("Unexpected FieldEncoding: $value")
    }
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import okio.BufferedSink
import okio.ByteString

/** A protocol buffer message. */
expect abstract class Message<M : Message<M, B>, B : Message.Builder<M, B>> protected constructor(
  adapter: ProtoAdapter<M>,
  unknownFields: ByteString
) {
  /** If non-zero, the hash code of this message. Accessed by generated code. */
  protected var hashCode: Int

  /**
   * Returns a byte string containing the proto encoding of this message's unknown fields. Returns
   * an empty byte string if this message has no unknown fields.
   */
  val unknownFields: ByteString

  /**
   * Returns a new builder initialized with the data in this message.
   */
  // TODO(egorand): Remove from common and not generate in Kotlin.
  abstract fun newBuilder(): B

  /** The [ProtoAdapter] for encoding and decoding messages of this type. */
  val adapter: ProtoAdapter<M>

  /** Encode this message and write it to `stream`. */
  fun encode(sink: BufferedSink)

  /** Encode this message as a `byte[]`. */
  fun encode(): ByteArray

  /** Encode this message as a `ByteString`. */
  fun encodeByteString(): ByteString

  /**
   * Superclass for protocol buffer message builders.
   */
  abstract class Builder<M : Message<M, B>, B : Builder<M, B>>
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.internal.Throws
import okio.IOException

/**
 * A writable stream of messages.
 *
 * Typical implementations will immediately encode messages and enqueue them for transmission, such
 * as for client-to-server or server-to-client networking. But this interface is not limited to 1-1
 * networking use cases and implementations may persist, broadcast, validate, or take any other
 * action with the messages.
 *
 * There is no flushing mechanism. Messages are flushed one-by-one as they are written. This
 * minimizes latency at a potential cost of throughput.
 *
 * On its own this offers no guarantees that messages are delivered. For example, a message may
 * accepted by [write] could be lost due to a network partition or crash. It is the caller's
 * responsibility to confirm delivery and to retransmit as necessary.
 *
 * It is possible for a writer to saturate the transmission channel, such as when a writer writes
 * faster than the corresponding reader can read. In such cases calls to [write] will block until
 * there is capacity in the outbound channel. You may use this as a basic backpressure mechanism.
 * You should ensure that such backpressure propagates to the originator of outbound messages.
 *
 * Instances of this interface are not safe for concurrent use.
 */
expect interface MessageSink<in T : Any> {
  /**
   * Encode [message] to bytes and enqueue the bytes for delivery, waiting if necessary until the
   * delivery channel has capacity for the encoded message.
   *
   * @throws IllegalStateException if this stream is closed.
   */
  @Throws(IOException::class)
  fun write(message: T)

  /**
   * Truncate this stream abnormally. This attempts to signal to readers of this data that it is
   * incomplete. Note that unlike some cancel methods this is not safe for concurrent use.
   *
   * A stream must be closed after it is canceled.
   *
   * @throws IllegalStateException if this stream is closed.
   */
  @Throws(IOException::class)
  fun cancel()

  /**
   * Terminate the stream and release its resources. If this has not been canceled this signals a
   * normal completion of the stream.
   */
  @Throws(IOException::class)
  fun close()
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.internal.Throws
import okio.IOException

/**
 * A readable stream of messages.
 *
 * Typical implementations will receive messages recently transmitted from a peer, such as for
 * server-to-client or client-to-server networking. But this implementation is not limited to such
 * networking use cases and implementations may load messages from local storage or generate
 * messages on demand.
 *
 * Calls to [read] will block until a message becomes available. There is no mechanism to limit how
 * long a specific [read] will wait, though implementations may be configured to fail if they
 * consider a source to be unhealthy.
 *
 * Readers should take care to keep up with the stream of messages. A reader that takes an excessive
 * amount of time to process a message may cause their writer to back up and suffer queueing.
 *
 * Instances of this interface are not safe for concurrent use.
 */
expect interface MessageSource<out T : Any> {
  /**
   * Read the next length-prefixed message on the stream and return it. Returns null if there are
   * no further messages on this stream.
   *
   * @throws IOException if the next message cannot be read, or if the stream was abnormally
   *     terminated by its producer.
   */
  @Throws(IOException::class)
  fun read(): T?

  @Throws(IOException::class)
  fun close()
}

/*
 * Copyright 2015 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.ProtoWriter.Companion.decodeZigZag32
import com.squareup.wire.ProtoWriter.Companion.decodeZigZag64
import com.squareup.wire.ProtoWriter.Companion.encodeZigZag32
import com.squareup.wire.ProtoWriter.Companion.encodeZigZag64
import com.squareup.wire.ProtoWriter.Companion.int32Size
import com.squareup.wire.ProtoWriter.Companion.varint32Size
import com.squareup.wire.ProtoWriter.Companion.varint64Size
import com.squareup.wire.internal.Throws
import okio.Buffer
import okio.BufferedSink
import okio.BufferedSource
import okio.ByteString
import okio.IOException
import okio.utf8Size
import kotlin.jvm.JvmField
import kotlin.jvm.JvmStatic
import kotlin.reflect.KClass

expect abstract class ProtoAdapter<E>(
  fieldEncoding: FieldEncoding,
  type: KClass<*>?,
  typeUrl: String?
) {
  internal val fieldEncoding: FieldEncoding
  val type: KClass<*>?

  /**
   * Identifies this type for inclusion in a `google.protobuf.Any`. This is a string like
   * "type.googleapis.com/packagename.messagename" or null if this type is either not a message
   * (such as scalars and enums), or was code-generated before Wire 3.2 which introduced support for
   * type URLS.
   */
  val typeUrl: String?

  internal val packedAdapter: ProtoAdapter<List<E>>?
  internal val repeatedAdapter: ProtoAdapter<List<E>>?

  /** Returns the redacted form of `value`. */
  abstract fun redact(value: E): E

  /**
   * The size of the non-null data `value`. This does not include the size required for a
   * length-delimited prefix (should the type require one).
   */
  abstract fun encodedSize(value: E): Int

  /**
   * The size of `tag` and `value` in the wire format. This size includes the tag, type,
   * length-delimited prefix (should the type require one), and value. Returns 0 if `value` is
   * null.
   */
  open fun encodedSizeWithTag(tag: Int, value: E?): Int

  /** Write non-null `value` to `writer`. */
  @Throws(IOException::class)
  abstract fun encode(writer: ProtoWriter, value: E)

  /** Write `tag` and `value` to `writer`. If value is null this does nothing. */
  @Throws(IOException::class)
  open fun encodeWithTag(writer: ProtoWriter, tag: Int, value: E?)

  /** Encode `value` and write it to `stream`. */
  @Throws(IOException::class)
  fun encode(sink: BufferedSink, value: E)

  /** Encode `value` as a `byte[]`. */
  fun encode(value: E): ByteArray

  /** Encode `value` as a [ByteString]. */
  fun encodeByteString(value: E): ByteString

  /** Read a non-null value from `reader`. */
  @Throws(IOException::class)
  abstract fun decode(reader: ProtoReader): E

  /** Read an encoded message from `bytes`. */
  @Throws(IOException::class)
  fun decode(bytes: ByteArray): E

  /** Read an encoded message from `bytes`. */
  @Throws(IOException::class)
  fun decode(bytes: ByteString): E

  /** Read an encoded message from `source`. */
  @Throws(IOException::class)
  fun decode(source: BufferedSource): E

  /** Returns a human-readable version of the given `value`. */
  open fun toString(value: E): String

  internal fun withLabel(label: WireField.Label): ProtoAdapter<*>

  /** Returns an adapter for `E` but as a packed, repeated value. */
  fun asPacked(): ProtoAdapter<List<E>>

  /**
   * Returns an adapter for `E` but as a repeated value.
   *
   * Note: Repeated items are not required to be encoded sequentially. Thus, when decoding using
   * the returned adapter, only single-element lists will be returned and it is the caller's
   * responsibility to merge them into the final list.
   */
  fun asRepeated(): ProtoAdapter<List<E>>

  class EnumConstantNotFoundException(
    value: Int,
    type: KClass<*>?
  ) : IllegalArgumentException {
    @JvmField val value: Int
  }

  companion object {
    /**
     * Creates a new proto adapter for a map using `keyAdapter` and `valueAdapter`.
     *
     * Note: Map entries are not required to be encoded sequentially. Thus, when decoding using
     * the returned adapter, only single-element maps will be returned and it is the caller's
     * responsibility to merge them into the final map.
     */
    @JvmStatic fun <K, V> newMapAdapter(
      keyAdapter: ProtoAdapter<K>,
      valueAdapter: ProtoAdapter<V>
    ): ProtoAdapter<Map<K, V>>

    @JvmField val BOOL: ProtoAdapter<Boolean>
    @JvmField val INT32: ProtoAdapter<Int>
    @JvmField val UINT32: ProtoAdapter<Int>
    @JvmField val SINT32: ProtoAdapter<Int>
    @JvmField val FIXED32: ProtoAdapter<Int>
    @JvmField val SFIXED32: ProtoAdapter<Int>
    @JvmField val INT64: ProtoAdapter<Long>
    /**
     * Like INT64, but negative longs are interpreted as large positive values, and encoded that way
     * in JSON.
     */
    @JvmField val UINT64: ProtoAdapter<Long>
    @JvmField val SINT64: ProtoAdapter<Long>
    @JvmField val FIXED64: ProtoAdapter<Long>
    @JvmField val SFIXED64: ProtoAdapter<Long>
    @JvmField val FLOAT: ProtoAdapter<Float>
    @JvmField val DOUBLE: ProtoAdapter<Double>
    @JvmField val BYTES: ProtoAdapter<ByteString>
    @JvmField val STRING: ProtoAdapter<String>
  }
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonEncodedSizeWithTag(tag: Int, value: E?): Int {
  if (value == null) return 0
  var size = encodedSize(value)
  if (fieldEncoding == FieldEncoding.LENGTH_DELIMITED) {
    size += varint32Size(size)
  }
  return size + ProtoWriter.tagSize(tag)
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonEncodeWithTag(
  writer: ProtoWriter,
  tag: Int,
  value: E?
) {
  if (value == null) return
  writer.writeTag(tag, fieldEncoding)
  if (fieldEncoding == FieldEncoding.LENGTH_DELIMITED) {
    writer.writeVarint32(encodedSize(value))
  }
  encode(writer, value)
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonEncode(sink: BufferedSink, value: E) {
  encode(ProtoWriter(sink), value)
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonEncode(value: E): ByteArray {
  val buffer = Buffer()
  encode(buffer, value)
  return buffer.readByteArray()
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonEncodeByteString(value: E): ByteString {
  val buffer = Buffer()
  encode(buffer, value)
  return buffer.readByteString()
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonDecode(bytes: ByteArray): E {
  return decode(Buffer().write(bytes))
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonDecode(bytes: ByteString): E {
  return decode(Buffer().write(bytes))
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonDecode(source: BufferedSource): E {
  return decode(ProtoReader(source))
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> commonToString(value: E): String = value.toString()

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonWithLabel(label: WireField.Label): ProtoAdapter<*> {
  if (label.isRepeated) {
    return if (label.isPacked) asPacked() else asRepeated()
  }
  return this
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonCreatePacked(): ProtoAdapter<List<E>> {
  require(fieldEncoding != FieldEncoding.LENGTH_DELIMITED) {
    "Unable to pack a length-delimited type."
  }
  return PackedProtoAdapter(originalAdapter = this)
}

internal class PackedProtoAdapter<E>(
  private val originalAdapter: ProtoAdapter<E>
) : ProtoAdapter<List<E>>(FieldEncoding.LENGTH_DELIMITED, List::class, null) {
  @Throws(IOException::class)
  override fun encodeWithTag(writer: ProtoWriter, tag: Int, value: List<E>?) {
    if (value != null && value.isNotEmpty()) {
      super.encodeWithTag(writer, tag, value)
    }
  }

  override fun encodedSize(value: List<E>): Int {
    var size = 0
    for (i in 0 until value.size) {
      size += originalAdapter.encodedSize(value[i])
    }
    return size
  }

  override fun encodedSizeWithTag(tag: Int, value: List<E>?): Int {
    return if (value == null || value.isEmpty()) 0 else super.encodedSizeWithTag(tag, value)
  }

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: List<E>) {
    for (i in 0 until value.size) {
      originalAdapter.encode(writer, value[i])
    }
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): List<E> = listOf(originalAdapter.decode(reader))

  override fun redact(value: List<E>): List<E> = emptyList()
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun <E> ProtoAdapter<E>.commonCreateRepeated(): ProtoAdapter<List<E>> {
  return RepeatedProtoAdapter(originalAdapter = this)
}

internal class RepeatedProtoAdapter<E>(
  private val originalAdapter: ProtoAdapter<E>
) : ProtoAdapter<List<E>>(originalAdapter.fieldEncoding, List::class, null) {
  override fun encodedSize(value: List<E>): Int {
    throw UnsupportedOperationException("Repeated values can only be sized with a tag.")
  }

  override fun encodedSizeWithTag(tag: Int, value: List<E>?): Int {
    if (value == null) return 0
    var size = 0
    for (i in 0 until value.size) {
      size += originalAdapter.encodedSizeWithTag(tag, value[i])
    }
    return size
  }

  override fun encode(writer: ProtoWriter, value: List<E>) {
    throw UnsupportedOperationException("Repeated values can only be encoded with a tag.")
  }

  @Throws(IOException::class)
  override fun encodeWithTag(writer: ProtoWriter, tag: Int, value: List<E>?) {
    if (value == null) return
    for (i in 0 until value.size) {
      originalAdapter.encodeWithTag(writer, tag, value[i])
    }
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): List<E> = listOf(originalAdapter.decode(reader))

  override fun redact(value: List<E>): List<E> = emptyList()
}

internal class MapProtoAdapter<K, V> internal constructor(
  keyAdapter: ProtoAdapter<K>,
  valueAdapter: ProtoAdapter<V>
) : ProtoAdapter<Map<K, V>>(FieldEncoding.LENGTH_DELIMITED, Map::class, null) {
  private val entryAdapter = MapEntryProtoAdapter(keyAdapter, valueAdapter)

  override fun encodedSize(value: Map<K, V>): Int {
    throw UnsupportedOperationException("Repeated values can only be sized with a tag.")
  }

  override fun encodedSizeWithTag(tag: Int, value: Map<K, V>?): Int {
    if (value == null) return 0
    var size = 0
    for (entry in value.entries) {
      size += entryAdapter.encodedSizeWithTag(tag, entry)
    }
    return size
  }

  override fun encode(writer: ProtoWriter, value: Map<K, V>) {
    throw UnsupportedOperationException("Repeated values can only be encoded with a tag.")
  }

  @Throws(IOException::class)
  override fun encodeWithTag(writer: ProtoWriter, tag: Int, value: Map<K, V>?) {
    if (value == null) return
    for (entry in value.entries) {
      entryAdapter.encodeWithTag(writer, tag, entry)
    }
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Map<K, V> {
    var key: K? = null
    var value: V? = null

    val token = reader.beginMessage()
    while (true) {
      val tag = reader.nextTag()
      if (tag == -1) break
      when (tag) {
        1 -> key = entryAdapter.keyAdapter.decode(reader)
        2 -> value = entryAdapter.valueAdapter.decode(reader)
        // Ignore unknown tags in map entries.
      }
    }
    reader.endMessageAndGetUnknownFields(token)

    check(key != null) { "Map entry with null key" }
    check(value != null) { "Map entry with null value" }
    return mapOf(key to value)
  }

  override fun redact(value: Map<K, V>): Map<K, V> = emptyMap()
}

private class MapEntryProtoAdapter<K, V> internal constructor(
  internal val keyAdapter: ProtoAdapter<K>,
  internal val valueAdapter: ProtoAdapter<V>
) : ProtoAdapter<Map.Entry<K, V>>(FieldEncoding.LENGTH_DELIMITED, Map.Entry::class, null) {

  override fun encodedSize(value: Map.Entry<K, V>): Int {
    return keyAdapter.encodedSizeWithTag(1, value.key) +
        valueAdapter.encodedSizeWithTag(2, value.value)
  }

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Map.Entry<K, V>) {
    keyAdapter.encodeWithTag(writer, 1, value.key)
    valueAdapter.encodeWithTag(writer, 2, value.value)
  }

  override fun decode(reader: ProtoReader): Map.Entry<K, V> {
    throw UnsupportedOperationException()
  }

  override fun redact(value: Map.Entry<K, V>): Map.Entry<K, V> {
    throw UnsupportedOperationException()
  }
}

private const val FIXED_BOOL_SIZE = 1
private const val FIXED_32_SIZE = 4
private const val FIXED_64_SIZE = 8

@Suppress("NOTHING_TO_INLINE")
internal inline fun <K, V> commonNewMapAdapter(
  keyAdapter: ProtoAdapter<K>,
  valueAdapter: ProtoAdapter<V>
): ProtoAdapter<Map<K, V>> {
  return MapProtoAdapter(keyAdapter, valueAdapter)
}

internal fun commonBool(): ProtoAdapter<Boolean> = object : ProtoAdapter<Boolean>(
    FieldEncoding.VARINT,
    Boolean::class,
    null
) {
  override fun encodedSize(value: Boolean): Int = FIXED_BOOL_SIZE

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Boolean) {
    writer.writeVarint32(if (value) 1 else 0)
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Boolean = when (val value = reader.readVarint32()) {
    0 -> false
    1 -> true
    else -> throw IOException("Invalid boolean value 0x" + value.toString(16).padStart(2, '0'))
  }

  override fun redact(value: Boolean): Boolean = throw UnsupportedOperationException()
}

internal fun commonInt32(): ProtoAdapter<Int> = object : ProtoAdapter<Int>(
    FieldEncoding.VARINT,
    Int::class,
    null
) {
  override fun encodedSize(value: Int): Int = int32Size(value)

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Int) {
    writer.writeSignedVarint32(value)
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Int = reader.readVarint32()

  override fun redact(value: Int): Int = throw UnsupportedOperationException()
}

internal fun commonUint32(): ProtoAdapter<Int> = object : ProtoAdapter<Int>(
    FieldEncoding.VARINT,
    Int::class,
    null
) {
  override fun encodedSize(value: Int): Int = varint32Size(value)

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Int) {
    writer.writeVarint32(value)
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Int = reader.readVarint32()

  override fun redact(value: Int): Int = throw UnsupportedOperationException()
}

internal fun commonSint32(): ProtoAdapter<Int> = object : ProtoAdapter<Int>(
    FieldEncoding.VARINT,
    Int::class,
    null
) {
  override fun encodedSize(value: Int): Int = varint32Size(encodeZigZag32(value))

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Int) {
    writer.writeVarint32(encodeZigZag32(value))
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Int = decodeZigZag32(reader.readVarint32())

  override fun redact(value: Int): Int = throw UnsupportedOperationException()
}

internal fun commonFixed32(): ProtoAdapter<Int> = object : ProtoAdapter<Int>(
    FieldEncoding.FIXED32,
    Int::class,
    null
) {
  override fun encodedSize(value: Int): Int = FIXED_32_SIZE

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Int) {
    writer.writeFixed32(value)
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Int = reader.readFixed32()

  override fun redact(value: Int): Int = throw UnsupportedOperationException()
}

internal fun commonSfixed32() = commonFixed32()
internal fun commonInt64(): ProtoAdapter<Long> = object : ProtoAdapter<Long>(
    FieldEncoding.VARINT,
    Long::class,
    null
) {
  override fun encodedSize(value: Long): Int = varint64Size(value)

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Long) {
    writer.writeVarint64(value)
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Long = reader.readVarint64()

  override fun redact(value: Long): Long = throw UnsupportedOperationException()
}

/**
 * Like INT64, but negative longs are interpreted as large positive values, and encoded that way
 * in JSON.
 */
internal fun commonUint64(): ProtoAdapter<Long> = object : ProtoAdapter<Long>(
    FieldEncoding.VARINT,
    Long::class,
    null
) {
  override fun encodedSize(value: Long): Int = varint64Size(value)

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Long) {
    writer.writeVarint64(value)
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Long = reader.readVarint64()

  override fun redact(value: Long): Long = throw UnsupportedOperationException()
}

internal fun commonSint64(): ProtoAdapter<Long> = object : ProtoAdapter<Long>(
    FieldEncoding.VARINT,
    Long::class,
    null
) {
  override fun encodedSize(value: Long): Int = varint64Size(encodeZigZag64(value))

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Long) {
    writer.writeVarint64(encodeZigZag64(value))
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Long = decodeZigZag64(reader.readVarint64())

  override fun redact(value: Long): Long = throw UnsupportedOperationException()
}

internal fun commonFixed64(): ProtoAdapter<Long> = object : ProtoAdapter<Long>(
    FieldEncoding.FIXED64,
    Long::class,
    null
) {
  override fun encodedSize(value: Long): Int = FIXED_64_SIZE

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Long) {
    writer.writeFixed64(value)
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Long = reader.readFixed64()

  override fun redact(value: Long): Long = throw UnsupportedOperationException()
}

internal fun commonSfixed64() = commonFixed64()
internal fun commonFloat(): ProtoAdapter<Float> = object : ProtoAdapter<Float>(
    FieldEncoding.FIXED32,
    Float::class,
    null
) {
  override fun encodedSize(value: Float): Int = FIXED_32_SIZE

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Float) {
    writer.writeFixed32(value.toBits())
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Float {
    return Float.fromBits(reader.readFixed32())
  }

  override fun redact(value: Float): Float = throw UnsupportedOperationException()
}

internal fun commonDouble(): ProtoAdapter<Double> = object : ProtoAdapter<Double>(
    FieldEncoding.FIXED64,
    Double::class,
    null
) {
  override fun encodedSize(value: Double): Int = FIXED_64_SIZE

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: Double) {
    writer.writeFixed64(value.toBits())
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): Double {
    return Double.fromBits(reader.readFixed64())
  }

  override fun redact(value: Double): Double = throw UnsupportedOperationException()
}

internal fun commonString(): ProtoAdapter<String> = object : ProtoAdapter<String>(
    FieldEncoding.LENGTH_DELIMITED,
    String::class,
    null
) {
  override fun encodedSize(value: String): Int = value.utf8Size().toInt()

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: String) {
    writer.writeString(value)
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): String = reader.readString()

  override fun redact(value: String): String = throw UnsupportedOperationException()
}

internal fun commonBytes(): ProtoAdapter<ByteString> = object : ProtoAdapter<ByteString>(
    FieldEncoding.LENGTH_DELIMITED,
    ByteString::class,
    null
) {
  override fun encodedSize(value: ByteString): Int = value.size

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: ByteString) {
    writer.writeBytes(value)
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): ByteString = reader.readBytes()

  override fun redact(value: ByteString): ByteString = throw UnsupportedOperationException()
}

// This class is derived from the CodedInputByteBuffer class in Google's "Nano" Protocol Buffer
// implementation. The original copyright notice, list of conditions, and disclaimer for those
// classes is as follows:

// Protocol Buffers - Google's data interchange format
// Copyright 2013 Google Inc.  All rights reserved.
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.squareup.wire

import com.squareup.wire.internal.ProtocolException
import com.squareup.wire.internal.Throws
import com.squareup.wire.internal.and
import com.squareup.wire.internal.shl
import okio.Buffer
import okio.BufferedSource
import okio.ByteString
import okio.EOFException
import okio.IOException
import kotlin.jvm.JvmName

/**
 * Reads and decodes protocol message fields.
 */
class ProtoReader(private val source: BufferedSource) {
  /** The current position in the input source, starting at 0 and increasing monotonically. */
  private var pos: Long = 0
  /** The absolute position of the end of the current message. */
  private var limit = Long.MAX_VALUE
  /** The current number of levels of message nesting. */
  private var recursionDepth = 0
  /** How to interpret the next read call. */
  private var state = STATE_LENGTH_DELIMITED
  /** The most recently read tag. Used to make packed values look like regular values. */
  private var tag = -1
  /** Limit once we complete the current length-delimited value. */
  private var pushedLimit: Long = -1
  /** The encoding of the next value to be read. */
  private var nextFieldEncoding: FieldEncoding? = null
  /** Pooled buffers for unknown fields, indexed by [recursionDepth]. */
  private val bufferStack = mutableListOf<Buffer>()

  /**
   * Begin a nested message. A call to this method will restrict the reader so that [nextTag]
   * returns -1 when the message is complete. An accompanying call to [endMessage] must then occur
   * with the opaque token returned from this method.
   */
  @Throws(IOException::class)
  fun beginMessage(): Long {
    check(state == STATE_LENGTH_DELIMITED) { "Unexpected call to beginMessage()" }
    if (++recursionDepth > RECURSION_LIMIT) {
      throw IOException("Wire recursion limit exceeded")
    }
    // Allocate a buffer to store unknown fields encountered at this recursion level.
    if (recursionDepth > bufferStack.size) bufferStack += Buffer()
    // Give the pushed limit to the caller to hold. The value is returned in endMessage() where we
    // resume using it as our limit.
    val token = pushedLimit
    pushedLimit = -1L
    state = STATE_TAG
    return token
  }

  /**
   * End a length-delimited nested message. Calls to this method must be symmetric with calls to
   * [beginMessage].
   *
   * @param token value returned from the corresponding call to [beginMessage].
   */
  @Throws(IOException::class)
  fun endMessageAndGetUnknownFields(token: Long): ByteString {
    check(state == STATE_TAG) { "Unexpected call to endMessage()" }
    check(--recursionDepth >= 0 && pushedLimit == -1L) { "No corresponding call to beginMessage()" }
    if (pos != limit && recursionDepth != 0) {
      throw IOException("Expected to end at $limit but was $pos")
    }
    limit = token
    val unknownFieldsBuffer = bufferStack[recursionDepth]
    return if (unknownFieldsBuffer.size > 0L) {
      unknownFieldsBuffer.readByteString()
    } else {
      ByteString.EMPTY
    }
  }

  /**
   * End a length-delimited nested message. Calls to this method must be symmetric with calls to
   * [beginMessage].
   *
   * @param token value returned from the corresponding call to [beginMessage].
   */
  @Throws(IOException::class)
  @Deprecated(
      level = DeprecationLevel.WARNING,
      message = "prefer endMessageAndGetUnknownFields()",
      replaceWith = ReplaceWith("endMessageAndGetUnknownFields(token)")
  )
  fun endMessage(token: Long) {
    endMessageAndGetUnknownFields(token)
  }

  /**
   * Reads and returns the next tag of the message, or -1 if there are no further tags. Use
   * [peekFieldEncoding] after calling this method to query its encoding. This silently skips
   * groups.
   */
  @Throws(IOException::class)
  fun nextTag(): Int {
    if (state == STATE_PACKED_TAG) {
      state = STATE_LENGTH_DELIMITED
      return tag
    } else if (state != STATE_TAG) {
      throw IllegalStateException("Unexpected call to nextTag()")
    }

    loop@ while (pos < limit && !source.exhausted()) {
      val tagAndFieldEncoding = internalReadVarint32()
      if (tagAndFieldEncoding == 0) throw ProtocolException("Unexpected tag 0")

      tag = tagAndFieldEncoding shr TAG_FIELD_ENCODING_BITS
      when (val groupOrFieldEncoding = tagAndFieldEncoding and FIELD_ENCODING_MASK) {
        STATE_START_GROUP -> {
          skipGroup(tag)
          continue@loop
        }

        STATE_END_GROUP -> throw ProtocolException("Unexpected end group")

        STATE_LENGTH_DELIMITED -> {
          nextFieldEncoding = FieldEncoding.LENGTH_DELIMITED
          state = STATE_LENGTH_DELIMITED
          val length = internalReadVarint32()
          if (length < 0) throw ProtocolException("Negative length: $length")
          if (pushedLimit != -1L) throw IllegalStateException()
          // Push the current limit, and set a new limit to the length of this value.
          pushedLimit = limit
          limit = pos + length
          if (limit > pushedLimit) throw EOFException()
          return tag
        }

        STATE_VARINT -> {
          nextFieldEncoding = FieldEncoding.VARINT
          state = STATE_VARINT
          return tag
        }

        STATE_FIXED64 -> {
          nextFieldEncoding = FieldEncoding.FIXED64
          state = STATE_FIXED64
          return tag
        }

        STATE_FIXED32 -> {
          nextFieldEncoding = FieldEncoding.FIXED32
          state = STATE_FIXED32
          return tag
        }

        else -> throw ProtocolException("Unexpected field encoding: $groupOrFieldEncoding")
      }
    }
    return -1
  }

  /**
   * Returns the encoding of the next field value. [nextTag] must be called before this method.
   */
  fun peekFieldEncoding(): FieldEncoding? = nextFieldEncoding

  /**
   * Skips the current field's value. This is only safe to call immediately following a call to
   * [nextTag].
   */
  @Throws(IOException::class)
  fun skip() {
    when (state) {
      STATE_LENGTH_DELIMITED -> {
        val byteCount = beforeLengthDelimitedScalar()
        source.skip(byteCount)
      }
      STATE_VARINT -> readVarint64()
      STATE_FIXED64 -> readFixed64()
      STATE_FIXED32 -> readFixed32()
      else -> throw IllegalStateException("Unexpected call to skip()")
    }
  }

  /** Skips a section of the input delimited by START_GROUP/END_GROUP type markers. */
  private fun skipGroup(expectedEndTag: Int) {
    while (pos < limit && !source.exhausted()) {
      val tagAndFieldEncoding = internalReadVarint32()
      if (tagAndFieldEncoding == 0) throw ProtocolException("Unexpected tag 0")
      val tag = tagAndFieldEncoding shr TAG_FIELD_ENCODING_BITS
      when (val groupOrFieldEncoding = tagAndFieldEncoding and FIELD_ENCODING_MASK) {
        STATE_START_GROUP -> skipGroup(tag) // Nested group.
        STATE_END_GROUP -> {
          if (tag == expectedEndTag) return  // Success!
          throw ProtocolException("Unexpected end group")
        }
        STATE_LENGTH_DELIMITED -> {
          val length = internalReadVarint32()
          pos += length.toLong()
          source.skip(length.toLong())
        }
        STATE_VARINT -> {
          state = STATE_VARINT
          readVarint64()
        }
        STATE_FIXED64 -> {
          state = STATE_FIXED64
          readFixed64()
        }
        STATE_FIXED32 -> {
          state = STATE_FIXED32
          readFixed32()
        }
        else -> throw ProtocolException("Unexpected field encoding: $groupOrFieldEncoding")
      }
    }
    throw EOFException()
  }

  /**
   * Reads a `bytes` field value from the stream. The length is read from the stream prior to the
   * actual data.
   */
  @Throws(IOException::class)
  fun readBytes(): ByteString {
    val byteCount = beforeLengthDelimitedScalar()
    source.require(byteCount) // Throws EOFException if insufficient bytes are available.
    return source.readByteString(byteCount)
  }

  /** Reads a `string` field value from the stream. */
  @Throws(IOException::class)
  fun readString(): String {
    val byteCount = beforeLengthDelimitedScalar()
    source.require(byteCount) // Throws EOFException if insufficient bytes are available.
    return source.readUtf8(byteCount)
  }

  /**
   * Reads a raw varint from the stream. If larger than 32 bits, discard the upper bits.
   */
  @Throws(IOException::class)
  fun readVarint32(): Int {
    if (state != STATE_VARINT && state != STATE_LENGTH_DELIMITED) {
      throw ProtocolException("Expected VARINT or LENGTH_DELIMITED but was $state")
    }
    val result = internalReadVarint32()
    afterPackableScalar(STATE_VARINT)
    return result
  }

  private fun internalReadVarint32(): Int {
    source.require(1) // Throws EOFException if insufficient bytes are available.
    pos++
    var tmp = source.readByte()
    if (tmp >= 0) {
      return tmp.toInt()
    }
    var result = tmp and 0x7f
    source.require(1) // Throws EOFException if insufficient bytes are available.
    pos++
    tmp = source.readByte()
    if (tmp >= 0) {
      result = result or (tmp shl 7)
    } else {
      result = result or (tmp and 0x7f shl 7)
      source.require(1) // Throws EOFException if insufficient bytes are available.
      pos++
      tmp = source.readByte()
      if (tmp >= 0) {
        result = result or (tmp shl 14)
      } else {
        result = result or (tmp and 0x7f shl 14)
        source.require(1) // Throws EOFException if insufficient bytes are available.
        pos++
        tmp = source.readByte()
        if (tmp >= 0) {
          result = result or (tmp shl 21)
        } else {
          result = result or (tmp and 0x7f shl 21)
          source.require(1) // Throws EOFException if insufficient bytes are available.
          pos++
          tmp = source.readByte()
          result = result or (tmp shl 28)
          if (tmp < 0) {
            // Discard upper 32 bits.
            for (i in 0..4) {
              source.require(1) // Throws EOFException if insufficient bytes are available.
              pos++
              if (source.readByte() >= 0) {
                return result
              }
            }
            throw ProtocolException("Malformed VARINT")
          }
        }
      }
    }
    return result
  }

  /** Reads a raw varint up to 64 bits in length from the stream.  */
  @Throws(IOException::class)
  fun readVarint64(): Long {
    if (state != STATE_VARINT && state != STATE_LENGTH_DELIMITED) {
      throw ProtocolException("Expected VARINT or LENGTH_DELIMITED but was $state")
    }
    var shift = 0
    var result: Long = 0
    while (shift < 64) {
      source.require(1) // Throws EOFException if insufficient bytes are available.
      pos++
      val b = source.readByte()
      result = result or ((b and 0x7F).toLong() shl shift)
      if (b and 0x80 == 0) {
        afterPackableScalar(STATE_VARINT)
        return result
      }
      shift += 7
    }
    throw ProtocolException("WireInput encountered a malformed varint")
  }

  /** Reads a 32-bit little-endian integer from the stream.  */
  @Throws(IOException::class)
  fun readFixed32(): Int {
    if (state != STATE_FIXED32 && state != STATE_LENGTH_DELIMITED) {
      throw ProtocolException("Expected FIXED32 or LENGTH_DELIMITED but was $state")
    }
    source.require(4) // Throws EOFException if insufficient bytes are available.
    pos += 4
    val result = source.readIntLe()
    afterPackableScalar(STATE_FIXED32)
    return result
  }

  /** Reads a 64-bit little-endian integer from the stream.  */
  @Throws(IOException::class)
  fun readFixed64(): Long {
    if (state != STATE_FIXED64 && state != STATE_LENGTH_DELIMITED) {
      throw ProtocolException("Expected FIXED64 or LENGTH_DELIMITED but was $state")
    }
    source.require(8) // Throws EOFException if insufficient bytes are available.
    pos += 8
    val result = source.readLongLe()
    afterPackableScalar(STATE_FIXED64)
    return result
  }

  @Throws(IOException::class)
  private fun afterPackableScalar(fieldEncoding: Int) {
    if (state == fieldEncoding) {
      state = STATE_TAG
    } else {
      when {
        pos > limit -> throw IOException("Expected to end at $limit but was $pos")
        pos == limit -> {
          // We've completed a sequence of packed values. Pop the limit.
          limit = pushedLimit
          pushedLimit = -1
          state = STATE_TAG
        }
        else -> state = STATE_PACKED_TAG
      }
    }
  }

  @Throws(IOException::class)
  private fun beforeLengthDelimitedScalar(): Long {
    if (state != STATE_LENGTH_DELIMITED) {
      throw ProtocolException("Expected LENGTH_DELIMITED but was $state")
    }
    val byteCount = limit - pos
    source.require(byteCount) // Throws EOFException if insufficient bytes are available.
    state = STATE_TAG
    // We've completed a length-delimited scalar. Pop the limit.
    pos = limit
    limit = pushedLimit
    pushedLimit = -1
    return byteCount
  }

  /** Reads each tag, handles it, and returns a byte string with the unknown fields. */
  @JvmName("-forEachTag") // hide from Java
  inline fun forEachTag(tagHandler: (Int) -> Any): ByteString {
    val token = beginMessage()
    while (true) {
      val tag = nextTag()
      if (tag == -1) break
      tagHandler(tag)
    }
    return endMessageAndGetUnknownFields(token)
  }

  /**
   * Read an unknown field and store temporarily. Once the entire message is read, call
   * [endMessageAndGetUnknownFields] to retrieve unknown fields.
   */
  fun readUnknownField(tag: Int) {
    val fieldEncoding = peekFieldEncoding()
    val protoAdapter = fieldEncoding!!.rawProtoAdapter()
    val value = protoAdapter.decode(this)
    addUnknownField(tag, fieldEncoding, value)
  }

  /**
   * Store an already read field temporarily. Once the entire message is read, call
   * [endMessageAndGetUnknownFields] to retrieve unknown fields.
   */
  fun addUnknownField(
    tag: Int,
    fieldEncoding: FieldEncoding,
    value: Any?
  ) {
    val unknownFieldsWriter = ProtoWriter(bufferStack[recursionDepth - 1])
    val protoAdapter = fieldEncoding.rawProtoAdapter()
    @Suppress("UNCHECKED_CAST") // We encode and decode the same types.
    (protoAdapter as ProtoAdapter<Any>).encodeWithTag(unknownFieldsWriter, tag, value)
  }

  companion object {
    /** The standard number of levels of message nesting to allow. */
    private const val RECURSION_LIMIT = 65

    private const val FIELD_ENCODING_MASK = 0x7
    internal const val TAG_FIELD_ENCODING_BITS = 3

    /** Read states. These constants correspond to field encodings where both exist. */
    private const val STATE_VARINT = 0
    private const val STATE_FIXED64 = 1
    private const val STATE_LENGTH_DELIMITED = 2
    private const val STATE_START_GROUP = 3
    private const val STATE_END_GROUP = 4
    private const val STATE_FIXED32 = 5
    private const val STATE_TAG = 6 // Note: not a field encoding.
    private const val STATE_PACKED_TAG = 7 // Note: not a field encoding.
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

// This class is derived from the CodedOutputByteBuffer, and WireFormatNano classes in Google's
// "Nano" Protocol Buffer implementation. The original copyright notice, list of conditions, and
// disclaimer for those classes is as follows:

// Protocol Buffers - Google's data interchange format
// Copyright 2013 Google Inc.  All rights reserved.
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import com.squareup.wire.internal.Throws
import okio.BufferedSink
import okio.ByteString
import okio.IOException

/**
 * Utilities for encoding and writing protocol message fields.
 */
class ProtoWriter(private val sink: BufferedSink) {

  @Throws(IOException::class)
  fun writeBytes(value: ByteString) {
    sink.write(value)
  }

  @Throws(IOException::class)
  fun writeString(value: String) {
    sink.writeUtf8(value)
  }

  /** Encode and write a tag.  */
  @Throws(IOException::class)
  fun writeTag(fieldNumber: Int, fieldEncoding: FieldEncoding) {
    writeVarint32(makeTag(fieldNumber, fieldEncoding))
  }

  /** Write an `int32` field to the stream.  */
  @Throws(IOException::class)
  internal fun writeSignedVarint32(value: Int) {
    if (value >= 0) {
      writeVarint32(value)
    } else {
      // Must sign-extend.
      writeVarint64(value.toLong())
    }
  }

  /**
   * Encode and write a varint. `value` is treated as unsigned, so it won't be sign-extended
   * if negative.
   */
  @Throws(IOException::class)
  fun writeVarint32(value: Int) {
    var value = value
    while (value and 0x7f.inv() != 0) {
      sink.writeByte((value and 0x7f) or 0x80)
      value = value ushr 7
    }
    sink.writeByte(value)
  }

  /** Encode and write a varint.  */
  @Throws(IOException::class)
  fun writeVarint64(value: Long) {
    var value = value
    while (value and 0x7fL.inv() != 0L) {
      sink.writeByte((value.toInt() and 0x7f) or 0x80)
      value = value ushr 7
    }
    sink.writeByte(value.toInt())
  }

  /** Write a little-endian 32-bit integer.  */
  @Throws(IOException::class)
  fun writeFixed32(value: Int) {
    sink.writeIntLe(value)
  }

  /** Write a little-endian 64-bit integer.  */
  @Throws(IOException::class)
  fun writeFixed64(value: Long) {
    sink.writeLongLe(value)
  }

  companion object {

    /** Makes a tag value given a field number and wire type. */
    private fun makeTag(fieldNumber: Int, fieldEncoding: FieldEncoding): Int {
      return (fieldNumber shl ProtoReader.TAG_FIELD_ENCODING_BITS) or fieldEncoding.value
    }

    /** Compute the number of bytes that would be needed to encode a tag. */
    internal fun tagSize(tag: Int): Int = varint32Size(makeTag(tag, FieldEncoding.VARINT))

    /**
     * Computes the number of bytes that would be needed to encode a signed variable-length integer
     * of up to 32 bits.
     */
    internal fun int32Size(value: Int): Int {
      return if (value >= 0) {
        varint32Size(value)
      } else {
        // Must sign-extend.
        10
      }
    }

    /**
     * Compute the number of bytes that would be needed to encode a varint. `value` is treated
     * as unsigned, so it won't be sign-extended if negative.
     */
    internal fun varint32Size(value: Int): Int {
      if (value and (-0x1 shl 7) == 0) return 1
      if (value and (-0x1 shl 14) == 0) return 2
      if (value and (-0x1 shl 21) == 0) return 3
      return if (value and (-0x1 shl 28) == 0) 4 else 5
    }

    /** Compute the number of bytes that would be needed to encode a varint. */
    internal fun varint64Size(value: Long): Int {
      if (value and (-0x1L shl 7) == 0L) return 1
      if (value and (-0x1L shl 14) == 0L) return 2
      if (value and (-0x1L shl 21) == 0L) return 3
      if (value and (-0x1L shl 28) == 0L) return 4
      if (value and (-0x1L shl 35) == 0L) return 5
      if (value and (-0x1L shl 42) == 0L) return 6
      if (value and (-0x1L shl 49) == 0L) return 7
      if (value and (-0x1L shl 56) == 0L) return 8
      return if (value and (-0x1L shl 63) == 0L) 9 else 10
    }

    /**
     * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers into values that can be
     * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
     * to be varint encoded, thus always taking 10 bytes on the wire.)
     *
     * @param n A signed 32-bit integer.
     * @return An unsigned 32-bit integer, stored in a signed int because Java has no explicit
     * unsigned support.
     */
    internal fun encodeZigZag32(n: Int): Int {
      // Note: the right-shift must be arithmetic
      return (n shl 1) xor (n shr 31)
    }

    /**
     * Decodes a ZigZag-encoded 32-bit value. ZigZag encodes signed integers into values that can be
     * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
     * to be varint encoded, thus always taking 10 bytes on the wire.)
     *
     * @param n An unsigned 32-bit integer, stored in a signed int because Java has no explicit
     * unsigned support.
     * @return A signed 32-bit integer.
     */
    internal fun decodeZigZag32(n: Int): Int = (n.ushr(1)) xor -(n and 1)

    /**
     * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be
     * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
     * to be varint encoded, thus always taking 10 bytes on the wire.)
     *
     * @param n A signed 64-bit integer.
     * @return An unsigned 64-bit integer, stored in a signed int because Java has no explicit
     * unsigned support.
     */
    internal fun encodeZigZag64(n: Long): Long {
      // Note:  the right-shift must be arithmetic
      return (n shl 1) xor (n shr 63)
    }

    /**
     * Decodes a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be
     * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
     * to be varint encoded, thus always taking 10 bytes on the wire.)
     *
     * @param n An unsigned 64-bit integer, stored in a signed int because Java has no explicit
     * unsigned support.
     * @return A signed 64-bit integer.
     */
    internal fun decodeZigZag64(n: Long): Long = (n.ushr(1)) xor -(n and 1)
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

interface Service

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

/**
 * Interface for generated [Enum] values to help serialization and deserialization.
 */
interface WireEnum {
  /**
   * The tag value of an enum constant.
   */
  val value: Int
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import kotlin.jvm.JvmName

/**
 * Annotates generated [Message] fields with metadata for serialization and deserialization.
 */
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class WireField(
  /** The tag number used to store the field's value. */
  val tag: Int,
  /**
   * Reference to the static field that holds a [ProtoAdapter] that can encode and decode this
   * field's keys. This only applies to maps. See [adapter] for a description of the value format.
   */
  val keyAdapter: String = "",
  /**
   * Reference to the static field that holds a [ProtoAdapter] that can encode and decode this
   * field's values. The reference is a string like `com.squareup.wire.protos.person.Person#ADAPTER`
   * and contains a fully-qualified class name followed by a hash symbol and a field name.
   */
  val adapter: String,
  /**
   * The field's protocol buffer label, one of [Label.OPTIONAL], [Label.REQUIRED], [Label.REPEATED],
   * or [Label.PACKED]. Defaults to [Label.OPTIONAL].
   */
  val label: Label = Label.OPTIONAL,
  /**
   * Redacted fields are omitted from toString() to protect sensitive data. Defaults to false.
   */
  val redacted: Boolean = false
) {

  /** A protocol buffer label.  */
  enum class Label {
    REQUIRED,
    OPTIONAL,
    REPEATED,
    ONE_OF,
    /** Implies [REPEATED]. */
    PACKED;

    val isRepeated: Boolean
      @JvmName("isRepeated") get() = this == REPEATED || this == PACKED

    val isPacked: Boolean
      @JvmName("isPacked") get() = this == PACKED

    val isOneOf: Boolean
      @JvmName("isOneOf") get() = this == ONE_OF
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

/**
 * For gRPC actions the path is formatted as `/<service name>/<method name>`. The path of the proto
 * service below is `/squareup.helloworld.Greeter/SayHello`.
 *
 * ```
 * package squareup.helloworld;
 *
 * service Greeter {
 *   rpc SayHello (HelloRequest) returns (HelloReply) {}
 * }
 * ```
 */
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class WireRpc(
  val path: String,
  val requestAdapter: String,
  val responseAdapter: String
)

/*
 * Copyright 2016 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import kotlin.reflect.KClass

/**
 * An abstract [ProtoAdapter] that converts values of an enum to and from integers.
 */
actual abstract class EnumAdapter<E : WireEnum> protected actual constructor(
  type: KClass<E>
) : ProtoAdapter<E>(FieldEncoding.VARINT, type, null) {
  actual override fun encodedSize(value: E): Int = commonEncodedSize(value)

  actual override fun encode(writer: ProtoWriter, value: E) {
    commonEncode(writer, value)
  }

  actual override fun decode(reader: ProtoReader): E = commonDecode(reader, this::fromValue)

  actual override fun redact(value: E): E = commonRedact(value)

  /**
   * Converts an integer to an enum.
   * Returns null if there is no corresponding enum.
   */
  protected actual abstract fun fromValue(value: Int): E?
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import okio.BufferedSink
import okio.ByteString

/** A protocol buffer message. */
actual abstract class Message<M : Message<M, B>, B : Message.Builder<M, B>>
protected actual constructor(
  /** The [ProtoAdapter] for encoding and decoding messages of this type. */
  actual val adapter: ProtoAdapter<M>,
  /**
   * Returns a byte string containing the proto encoding of this message's unknown fields. Returns
   * an empty byte string if this message has no unknown fields.
   */
  actual val unknownFields: ByteString
) {
  /** If non-zero, the hash code of this message. Accessed by generated code. */
  @JsName("cachedHashCode") protected actual var hashCode = 0

  /**
   * Returns a new builder initialized with the data in this message.
   */
  actual abstract fun newBuilder(): B

  /** Encode this message and write it to `stream`. */
  actual fun encode(sink: BufferedSink) {
    adapter.encode(sink, this as M)
  }

  /** Encode this message as a `byte[]`. */
  actual fun encode(): ByteArray = adapter.encode(this as M)

  /** Encode this message as a `ByteString`. */
  actual fun encodeByteString(): ByteString {
    return adapter.encodeByteString(this as M)
  }

  /**
   * Superclass for protocol buffer message builders.
   */
  actual abstract class Builder<M : Message<M, B>, B : Builder<M, B>>
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

actual interface MessageSink<in T : Any> {
  actual fun write(message: T)

  actual fun cancel()

  actual fun close()
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

actual interface MessageSource<out T : Any> {
  actual fun read(): T?

  actual fun close()
}

/*
 * Copyright 2015 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import okio.BufferedSink
import okio.BufferedSource
import okio.ByteString
import kotlin.reflect.KClass

actual abstract class ProtoAdapter<E> actual constructor(
  internal actual val fieldEncoding: FieldEncoding,
  actual val type: KClass<*>?,
  actual val typeUrl: String?
) {
  internal actual val packedAdapter: ProtoAdapter<List<E>>? = when {
    this is PackedProtoAdapter<*> || this is RepeatedProtoAdapter<*> -> null
    fieldEncoding == FieldEncoding.LENGTH_DELIMITED -> null
    else -> commonCreatePacked()
  }
  internal actual val repeatedAdapter: ProtoAdapter<List<E>>? = when {
    this is RepeatedProtoAdapter<*> || this is PackedProtoAdapter<*> -> null
    else -> commonCreateRepeated()
  }

  /** Returns the redacted form of `value`. */
  actual abstract fun redact(value: E): E

  /**
   * The size of the non-null data `value`. This does not include the size required for a
   * length-delimited prefix (should the type require one).
   */
  actual abstract fun encodedSize(value: E): Int

  /**
   * The size of `tag` and `value` in the wire format. This size includes the tag, type,
   * length-delimited prefix (should the type require one), and value. Returns 0 if `value` is
   * null.
   */
  actual open fun encodedSizeWithTag(tag: Int, value: E?): Int {
    return commonEncodedSizeWithTag(tag, value)
  }

  /** Write non-null `value` to `writer`. */
  actual abstract fun encode(writer: ProtoWriter, value: E)

  /** Write `tag` and `value` to `writer`. If value is null this does nothing. */
  actual open fun encodeWithTag(writer: ProtoWriter, tag: Int, value: E?) {
    commonEncodeWithTag(writer, tag, value)
  }

  /** Encode `value` and write it to `stream`. */
  actual fun encode(sink: BufferedSink, value: E) {
    commonEncode(sink, value)
  }

  /** Encode `value` as a `byte[]`. */
  actual fun encode(value: E): ByteArray {
    return commonEncode(value)
  }

  /** Encode `value` as a [ByteString]. */
  actual fun encodeByteString(value: E): ByteString {
    return commonEncodeByteString(value)
  }

  /** Read a non-null value from `reader`. */
  actual abstract fun decode(reader: ProtoReader): E

  /** Read an encoded message from `bytes`. */
  actual fun decode(bytes: ByteArray): E {
    return commonDecode(bytes)
  }

  /** Read an encoded message from `bytes`. */
  actual fun decode(bytes: ByteString): E {
    return commonDecode(bytes)
  }

  /** Read an encoded message from `source`. */
  actual fun decode(source: BufferedSource): E {
    return commonDecode(source)
  }

  /** Returns a human-readable version of the given `value`. */
  actual open fun toString(value: E): String {
    return commonToString(value)
  }

  internal actual fun withLabel(label: WireField.Label): ProtoAdapter<*> {
    return commonWithLabel(label)
  }

  /** Returns an adapter for `E` but as a packed, repeated value. */
  actual fun asPacked(): ProtoAdapter<List<E>> {
    require(fieldEncoding != FieldEncoding.LENGTH_DELIMITED) {
      "Unable to pack a length-delimited type."
    }
    return packedAdapter ?: throw UnsupportedOperationException(
        "Can't create a packed adapter from a packed or repeated adapter.")
  }

  /**
   * Returns an adapter for `E` but as a repeated value.
   *
   * Note: Repeated items are not required to be encoded sequentially. Thus, when decoding using
   * the returned adapter, only single-element lists will be returned and it is the caller's
   * responsibility to merge them into the final list.
   */
  actual fun asRepeated(): ProtoAdapter<List<E>> {
    return repeatedAdapter ?: throw UnsupportedOperationException(
        "Can't create a repeated adapter from a repeated or packed adapter.")
  }

  actual class EnumConstantNotFoundException actual constructor(
    actual val value: Int,
    type: KClass<*>?
  ) : IllegalArgumentException("Unknown enum tag $value for ${type?.simpleName}")

  actual companion object {
    /**
     * Creates a new proto adapter for a map using `keyAdapter` and `valueAdapter`.
     *
     * Note: Map entries are not required to be encoded sequentially. Thus, when decoding using
     * the returned adapter, only single-element maps will be returned and it is the caller's
     * responsibility to merge them into the final map.
     */
    actual fun <K, V> newMapAdapter(
      keyAdapter: ProtoAdapter<K>,
      valueAdapter: ProtoAdapter<V>
    ): ProtoAdapter<Map<K, V>> {
      return commonNewMapAdapter(keyAdapter, valueAdapter)
    }

    actual val BOOL: ProtoAdapter<Boolean> = commonBool()
    actual val INT32: ProtoAdapter<Int> = commonInt32()
    actual val UINT32: ProtoAdapter<Int> = commonUint32()
    actual val SINT32: ProtoAdapter<Int> = commonSint32()
    actual val FIXED32: ProtoAdapter<Int> = commonFixed32()
    actual val SFIXED32: ProtoAdapter<Int> = commonSfixed32()
    actual val INT64: ProtoAdapter<Long> = commonInt64()
    /**
     * Like INT64, but negative longs are interpreted as large positive values, and encoded that way
     * in JSON.
     */
    actual val UINT64: ProtoAdapter<Long> = commonUint64()
    actual val SINT64: ProtoAdapter<Long> = commonSint64()
    actual val FIXED64: ProtoAdapter<Long> = commonFixed64()
    actual val SFIXED64: ProtoAdapter<Long> = commonSfixed64()
    actual val FLOAT: ProtoAdapter<Float> = commonFloat()
    actual val DOUBLE: ProtoAdapter<Double> = commonDouble()
    actual val BYTES: ProtoAdapter<ByteString> = commonBytes()
    actual val STRING: ProtoAdapter<String> = commonString()
  }
}

/*
 * Copyright 2016 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import android.os.Parcel
import android.os.Parcelable
import okio.ByteString
import java.lang.reflect.Array.newInstance

/** An Android-specific [Message] which adds support for [Parcelable]. */
abstract class AndroidMessage<M : Message<M, B>, B : Message.Builder<M, B>> protected constructor(
  adapter: ProtoAdapter<M>,
  unknownFields: ByteString
) : Message<M, B>(adapter, unknownFields), Parcelable {

  override fun writeToParcel(dest: Parcel, flags: Int) {
    dest.writeByteArray(encode())
  }

  override fun describeContents() = 0

  private class ProtoAdapterCreator<M> internal constructor(
    private val adapter: ProtoAdapter<M>
  ) : Parcelable.Creator<M> {
    override fun createFromParcel(input: Parcel): M = adapter.decode(input.createByteArray())

    override fun newArray(size: Int): Array<M> =
        newInstance(adapter.type?.javaObjectType, size) as Array<M>
  }

  companion object {
    /** Creates a new [Parcelable.Creator] using `adapter` for serialization. */
    @JvmStatic fun <E> newCreator(adapter: ProtoAdapter<E>): Parcelable.Creator<E> {
      return ProtoAdapterCreator(adapter)
    }
  }
}

/*
 * Copyright 2016 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import java.io.IOException
import kotlin.reflect.KClass

/**
 * An abstract [ProtoAdapter] that converts values of an enum to and from integers.
 */
actual abstract class EnumAdapter<E : WireEnum> protected actual constructor(
  type: KClass<E>
) : ProtoAdapter<E>(FieldEncoding.VARINT, type, null) {
  constructor(type: Class<E>) : this(type.kotlin)

  actual override fun encodedSize(value: E): Int = commonEncodedSize(value)

  @Throws(IOException::class)
  actual override fun encode(writer: ProtoWriter, value: E) {
    commonEncode(writer, value)
  }

  @Throws(IOException::class)
  actual override fun decode(reader: ProtoReader): E = commonDecode(reader, this::fromValue)

  actual override fun redact(value: E): E = commonRedact(value)

  /**
   * Converts an integer to an enum.
   * Returns null if there is no corresponding enum.
   */
  protected actual abstract fun fromValue(value: Int): E?
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import okio.Buffer
import okio.BufferedSink
import okio.ByteString
import java.io.IOException
import java.io.ObjectStreamException
import java.io.OutputStream
import java.io.Serializable

/** A protocol buffer message. */
actual abstract class Message<M : Message<M, B>, B : Message.Builder<M, B>>
protected actual constructor(
  /** The [ProtoAdapter] for encoding and decoding messages of this type. */
  @field:Transient @get:JvmName("adapter") actual val adapter: ProtoAdapter<M>,
  unknownFields: ByteString
) : Serializable {
  /**
   * Returns a byte string containing the proto encoding of this message's unknown fields. Returns
   * an empty byte string if this message has no unknown fields.
   */
  @field:Transient @get:JvmName("unknownFields")
  actual val unknownFields: ByteString = unknownFields
    get() {
      // Some naughty libraries construct Messages by reflection which causes this non-null field
      // to have a null value. We defend against this with an otherwise-redundant null check.
      @Suppress("SENSELESS_COMPARISON")
      if (field == null) return ByteString.EMPTY
      return field
    }

  /** If not `0` then the serialized size of this message. */
  @Transient internal var cachedSerializedSize = 0

  /** If non-zero, the hash code of this message. Accessed by generated code. */
  @Transient @JvmField protected actual var hashCode = 0

  /**
   * Returns a new builder initialized with the data in this message.
   */
  actual abstract fun newBuilder(): B

  /** Returns this message with any unknown fields removed. */
  fun withoutUnknownFields(): M = newBuilder().clearUnknownFields().build()

  override fun toString(): String = adapter.toString(this as M)

  @Throws(ObjectStreamException::class)
  protected fun writeReplace(): Any = MessageSerializedForm(encode(), javaClass as Class<M>)

  /** Encode this message and write it to `stream`. */
  @Throws(IOException::class)
  actual fun encode(sink: BufferedSink) {
    adapter.encode(sink, this as M)
  }

  /** Encode this message as a `byte[]`.  */
  actual fun encode(): ByteArray = adapter.encode(this as M)

  /** Encode this message as a `ByteString`. */
  actual fun encodeByteString(): ByteString {
    return adapter.encodeByteString(this as M)
  }

  /** Encode this message and write it to `stream`.  */
  @Throws(IOException::class)
  fun encode(stream: OutputStream) {
    adapter.encode(stream, this as M)
  }

  /**
   * Superclass for protocol buffer message builders.
   */
  actual abstract class Builder<M : Message<M, B>, B : Builder<M, B>> protected constructor() {
    /**
     * Caches unknown fields as a [ByteString] when [buildUnknownFields] is called.
     * When the caller adds an additional unknown field after that, it will be written to the new
     * [unknownFieldsBuffer] to ensure that all unknown fields are retained between calls to
     * [buildUnknownFields].
     */
    @Transient internal var unknownFieldsByteString = ByteString.EMPTY
    /**
     * [Buffer] of the message's unknown fields that is lazily instantiated between calls to
     * [buildUnknownFields]. It's automatically cleared in [buildUnknownFields], and can also be
     * manually cleared by calling [clearUnknownFields].
     */
    @Transient internal var unknownFieldsBuffer: Buffer? = null
    @Transient internal var unknownFieldsWriter: ProtoWriter? = null

    fun addUnknownFields(unknownFields: ByteString): Builder<M, B> = apply {
      if (unknownFields.size > 0) {
        prepareForNewUnknownFields()
        unknownFieldsWriter!!.writeBytes(unknownFields)
      }
    }

    fun addUnknownField(
      tag: Int,
      fieldEncoding: FieldEncoding,
      value: Any?
    ): Builder<M, B> = apply {
      prepareForNewUnknownFields()
      val protoAdapter = fieldEncoding.rawProtoAdapter() as ProtoAdapter<Any>
      protoAdapter.encodeWithTag(unknownFieldsWriter!!, tag, value)
    }

    fun clearUnknownFields(): Builder<M, B> = apply {
      unknownFieldsByteString = ByteString.EMPTY
      if (unknownFieldsBuffer != null) {
        unknownFieldsBuffer!!.clear()
        unknownFieldsBuffer = null
      }
      unknownFieldsWriter = null
    }

    /**
     * Returns a byte string with this message's unknown fields. Returns an empty byte string if
     * this message has no unknown fields.
     */
    fun buildUnknownFields(): ByteString {
      if (unknownFieldsBuffer != null) {
        // Reads and caches the unknown fields from the buffer.
        unknownFieldsByteString = unknownFieldsBuffer!!.readByteString()
        unknownFieldsBuffer = null
        unknownFieldsWriter = null
      }
      return unknownFieldsByteString
    }

    /** Returns an immutable [Message] based on the fields that set in this builder. */
    abstract fun build(): M

    private fun prepareForNewUnknownFields() {
      if (unknownFieldsBuffer == null) {
        unknownFieldsBuffer = Buffer()
        unknownFieldsWriter = ProtoWriter(unknownFieldsBuffer!!)
        // Writes the cached unknown fields to the buffer.
        unknownFieldsWriter!!.writeBytes(unknownFieldsByteString)
        unknownFieldsByteString = ByteString.EMPTY
      }
    }
  }

  companion object {
    private const val serialVersionUID = 0L
  }
}

/*
 * Copyright 2015 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import java.io.IOException
import java.io.ObjectStreamException
import java.io.Serializable
import java.io.StreamCorruptedException

internal class MessageSerializedForm<M : Message<M, B>, B : Message.Builder<M, B>>(
  private val bytes: ByteArray,
  private val messageClass: Class<M>
) : Serializable {

  @Throws(ObjectStreamException::class)
  fun readResolve(): Any {
    val adapter = ProtoAdapter.get(messageClass)
    try {
      // Extensions will be decoded as unknown values.
      return adapter.decode(bytes)
    } catch (e: IOException) {
      throw StreamCorruptedException(e.message)
    }
  }

  companion object {
    private const val serialVersionUID = 0L
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import okio.IOException
import java.io.Closeable

actual interface MessageSink<in T : Any> : Closeable {
  @Throws(IOException::class)
  actual fun write(message: T)

  @Throws(IOException::class)
  actual fun cancel()
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import okio.IOException
import java.io.Closeable

actual interface MessageSource<out T : Any> : Closeable {
  @Throws(IOException::class)
  actual fun read(): T?
}

/*
 * Copyright 2015 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.internal.RuntimeMessageAdapter
import okio.BufferedSink
import okio.BufferedSource
import okio.ByteString
import okio.buffer
import okio.sink
import okio.source
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import kotlin.reflect.KClass

actual abstract class ProtoAdapter<E> actual constructor(
  internal actual val fieldEncoding: FieldEncoding,
  actual val type: KClass<*>?,
  actual val typeUrl: String?
) {
  internal actual val packedAdapter: ProtoAdapter<List<E>>? = when {
    this is PackedProtoAdapter<*> || this is RepeatedProtoAdapter<*> -> null
    fieldEncoding == FieldEncoding.LENGTH_DELIMITED -> null
    else -> commonCreatePacked()
  }
  internal actual val repeatedAdapter: ProtoAdapter<List<E>>? = when {
    this is RepeatedProtoAdapter<*> || this is PackedProtoAdapter<*> -> null
    else -> commonCreateRepeated()
  }

  // Obsolete; for Java classes generated before typeUrl was added.
  constructor(fieldEncoding: FieldEncoding, type: Class<*>) : this(fieldEncoding, type.kotlin)

  // Obsolete; for Kotlin classes generated before typeUrl was added.
  constructor(fieldEncoding: FieldEncoding, type: KClass<*>?) : this(fieldEncoding, type, null)

  constructor(fieldEncoding: FieldEncoding, type: Class<*>, typeUrl: String?) :
      this(fieldEncoding, type.kotlin, typeUrl)

  actual abstract fun redact(value: E): E

  actual abstract fun encodedSize(value: E): Int

  actual open fun encodedSizeWithTag(tag: Int, value: E?): Int {
    return commonEncodedSizeWithTag(tag, value)
  }

  @Throws(IOException::class)
  actual abstract fun encode(writer: ProtoWriter, value: E)

  @Throws(IOException::class)
  actual open fun encodeWithTag(writer: ProtoWriter, tag: Int, value: E?) {
    commonEncodeWithTag(writer, tag, value)
  }

  @Throws(IOException::class)
  actual fun encode(sink: BufferedSink, value: E) {
    commonEncode(sink, value)
  }

  actual fun encode(value: E): ByteArray {
    return commonEncode(value)
  }

  actual fun encodeByteString(value: E): ByteString {
    return commonEncodeByteString(value)
  }

  @Throws(IOException::class)
  fun encode(stream: OutputStream, value: E) {
    val buffer = stream.sink().buffer()
    encode(buffer, value)
    buffer.emit()
  }

  @Throws(IOException::class)
  actual abstract fun decode(reader: ProtoReader): E

  @Throws(IOException::class)
  actual fun decode(bytes: ByteArray): E {
    return commonDecode(bytes)
  }

  @Throws(IOException::class)
  actual fun decode(bytes: ByteString): E {
    return commonDecode(bytes)
  }

  @Throws(IOException::class)
  actual fun decode(source: BufferedSource): E {
    return commonDecode(source)
  }

  @Throws(IOException::class)
  fun decode(stream: InputStream): E = decode(stream.source().buffer())

  actual open fun toString(value: E): String {
    return commonToString(value)
  }

  internal actual fun withLabel(label: WireField.Label): ProtoAdapter<*> {
    return commonWithLabel(label)
  }

  actual fun asPacked(): ProtoAdapter<List<E>> {
    require(fieldEncoding != FieldEncoding.LENGTH_DELIMITED) {
      "Unable to pack a length-delimited type."
    }
    return packedAdapter ?: throw UnsupportedOperationException(
        "Can't create a packed adapter from a packed or repeated adapter.")
  }

  actual fun asRepeated(): ProtoAdapter<List<E>> {
    return repeatedAdapter ?: throw UnsupportedOperationException(
        "Can't create a repeated adapter from a repeated or packed adapter.")
  }

  actual class EnumConstantNotFoundException actual constructor(
    @JvmField actual val value: Int,
    type: KClass<*>?
  ) : IllegalArgumentException("Unknown enum tag $value for ${type?.java?.name}") {
    constructor(value: Int, type: Class<*>): this(value, type.kotlin)
  }

  actual companion object {
    @JvmStatic actual fun <K, V> newMapAdapter(
      keyAdapter: ProtoAdapter<K>,
      valueAdapter: ProtoAdapter<V>
    ): ProtoAdapter<Map<K, V>> {
      return commonNewMapAdapter(keyAdapter, valueAdapter)
    }

    /** Creates a new proto adapter for `type`. */
    @JvmStatic fun <M : Message<M, B>, B : Message.Builder<M, B>> newMessageAdapter(
      type: Class<M>
    ): ProtoAdapter<M> {
      return RuntimeMessageAdapter.create(type, null)
    }

    /** Creates a new proto adapter for `type`. */
    @JvmStatic fun <M : Message<M, B>, B : Message.Builder<M, B>> newMessageAdapter(
      type: Class<M>,
      typeUrl: String
    ): ProtoAdapter<M> {
      return RuntimeMessageAdapter.create(type, typeUrl)
    }

    /** Creates a new proto adapter for `type`. */
    @JvmStatic fun <E : WireEnum> newEnumAdapter(type: Class<E>): EnumAdapter<E> {
      return RuntimeEnumAdapter(type)
    }

    /** Returns the adapter for the type of `Message`. */
    @JvmStatic fun <M : Message<*, *>> get(message: M): ProtoAdapter<M> {
      return get(message.javaClass)
    }

    /** Returns the adapter for `type`. */
    @JvmStatic fun <M> get(type: Class<M>): ProtoAdapter<M> {
      try {
        return type.getField("ADAPTER").get(null) as ProtoAdapter<M>
      } catch (e: IllegalAccessException) {
        throw IllegalArgumentException("failed to access ${type.name}#ADAPTER", e)
      } catch (e: NoSuchFieldException) {
        throw IllegalArgumentException("failed to access ${type.name}#ADAPTER", e)
      }
    }

    /**
     * Returns the adapter for a given `adapterString`. `adapterString` is specified on a proto
     * message field's [WireField] annotation in the form
     * `com.squareup.wire.protos.person.Person#ADAPTER`.
     */
    @JvmStatic fun get(adapterString: String): ProtoAdapter<*> {
      try {
        val hash = adapterString.indexOf('#')
        val className = adapterString.substring(0, hash)
        val fieldName = adapterString.substring(hash + 1)
        return Class.forName(className).getField(fieldName).get(null) as ProtoAdapter<Any>
      } catch (e: IllegalAccessException) {
        throw IllegalArgumentException("failed to access $adapterString", e)
      } catch (e: NoSuchFieldException) {
        throw IllegalArgumentException("failed to access $adapterString", e)
      } catch (e: ClassNotFoundException) {
        throw IllegalArgumentException("failed to access $adapterString", e)
      }
    }

    @JvmField actual val BOOL: ProtoAdapter<Boolean> = commonBool()
    @JvmField actual val INT32: ProtoAdapter<Int> = commonInt32()
    @JvmField actual val UINT32: ProtoAdapter<Int> = commonUint32()
    @JvmField actual val SINT32: ProtoAdapter<Int> = commonSint32()
    @JvmField actual val FIXED32: ProtoAdapter<Int> = commonFixed32()
    @JvmField actual val SFIXED32: ProtoAdapter<Int> = commonSfixed32()
    @JvmField actual val INT64: ProtoAdapter<Long> = commonInt64()
    @JvmField actual val UINT64: ProtoAdapter<Long> = commonUint64()
    @JvmField actual val SINT64: ProtoAdapter<Long> = commonSint64()
    @JvmField actual val FIXED64: ProtoAdapter<Long> = commonFixed64()
    @JvmField actual val SFIXED64: ProtoAdapter<Long> = commonSfixed64()
    @JvmField actual val FLOAT: ProtoAdapter<Float> = commonFloat()
    @JvmField actual val DOUBLE: ProtoAdapter<Double> = commonDouble()
    @JvmField actual val BYTES: ProtoAdapter<ByteString> = commonBytes()
    @JvmField actual val STRING: ProtoAdapter<String> = commonString()
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import java.lang.reflect.Method

/**
 * Converts values of an enum to and from integers using reflection.
 */
internal class RuntimeEnumAdapter<E : WireEnum>(
  private val javaType: Class<E>
) : EnumAdapter<E>(javaType.kotlin) {
  private var fromValueMethod: Method? = null // Lazy to avoid reflection during class loading.

  private fun getFromValueMethod(): Method {
    return fromValueMethod ?: javaType.getMethod("fromValue", Int::class.javaPrimitiveType).also {
      fromValueMethod = it
    }
  }

  override fun fromValue(value: Int): E? = getFromValueMethod().invoke(null, value) as E

  override fun equals(other: Any?) = other is RuntimeEnumAdapter<*> && other.type == type

  override fun hashCode() = type.hashCode()
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
@file:JvmName("Wire")

package com.squareup.wire

import kotlin.jvm.JvmName

/**
 * Returns `value` if it is not null; `defaultValue` otherwise. This is used to conveniently return
 * a default value when a value is null. For example,
 *
 * ```
 * MyProto myProto = ...
 * MyField field = Wire.get(myProto.f, MyProto.f_default);
 * ```
 *
 * will attempt to retrieve the value of the field 'f' defined by MyProto. If the field is null
 * (i.e., unset), `get` will return its second argument, which in this case is the default value for
 * the field 'f'.
 */
fun <T> get(value: T?, defaultValue: T): T = value ?: defaultValue

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import okio.BufferedSink
import okio.ByteString

/** A protocol buffer message. */
actual abstract class Message<M : Message<M, B>, B : Message.Builder<M, B>>
protected actual constructor(
  /** The [ProtoAdapter] for encoding and decoding messages of this type. */
  actual val adapter: ProtoAdapter<M>,
  /**
   * Returns a byte string containing the proto encoding of this message's unknown fields. Returns
   * an empty byte string if this message has no unknown fields.
   */
  actual val unknownFields: ByteString
) {
  /** If non-zero, the hash code of this message. Accessed by generated code. */
  @Suppress("SetterBackingFieldAssignment")
  protected actual var hashCode = 0
    set(value) {
      // Do nothing to avoid IllegalImmutabilityException.
    }

  /**
   * Returns a new builder initialized with the data in this message.
   */
  actual abstract fun newBuilder(): B

  /** Encode this message and write it to `stream`. */
  actual fun encode(sink: BufferedSink) {
    adapter.encode(sink, this as M)
  }

  /** Encode this message as a `byte[]`. */
  actual fun encode(): ByteArray = adapter.encode(this as M)

  /** Encode this message as a `ByteString`. */
  actual fun encodeByteString(): ByteString {
    return adapter.encodeByteString(this as M)
  }

  /**
   * Superclass for protocol buffer message builders.
   */
  actual abstract class Builder<M : Message<M, B>, B : Builder<M, B>>
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.internal.Throws
import okio.IOException

actual interface MessageSink<in T : Any> {
  @Throws(IOException::class)
  actual fun write(message: T)

  @Throws(IOException::class)
  actual fun cancel()

  @Throws(IOException::class)
  actual fun close()
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire

import com.squareup.wire.internal.Throws
import okio.IOException

actual interface MessageSource<out T : Any> {
  @Throws(IOException::class)
  actual fun read(): T?

  @Throws(IOException::class)
  actual fun close()
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.whiteboard

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.os.Bundle
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.toColorInt
import com.squareup.wire.whiteboard.WhiteboardCommand.AddPoint
import com.squareup.wire.whiteboard.WhiteboardCommand.ClearBoard
import com.squareup.wire.whiteboard.WhiteboardUpdate.InitialiseBoard
import com.squareup.wire.whiteboard.WhiteboardUpdate.UpdatePoints
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
import kotlin.math.roundToInt

class MainActivity : AppCompatActivity(), OnBoardEventListener {
  private lateinit var sendCommandChannel: SendChannel<WhiteboardCommand>
  private lateinit var receiveUpdateChannel: ReceiveChannel<WhiteboardUpdate>

  private lateinit var whiteboardView: WhiteboardView
  private lateinit var clearButton: Button
  private val uiScope = CoroutineScope(Dispatchers.Main)

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_activity)
    whiteboardView = findViewById(R.id.whiteboard)
    whiteboardView.onBoardEventListener = this
    clearButton = findViewById(R.id.clear)

    uiScope.launch {
      withContext(Dispatchers.IO) {
        GrpcClientProvider.grpcClient.create(WhiteboardClient::class)
            .Whiteboard()
            .execute()
            .let { (sendChannel, receiveChannel) ->
              sendCommandChannel = sendChannel
              receiveUpdateChannel = receiveChannel
            }
      }
      try {
        for (update in receiveUpdateChannel) {
          when {
            update.initialise_board != null -> initialiseBoard(update.initialise_board!!)
            update.update_points != null -> updatePoints(update.update_points!!)
          }
        }
      } catch (e: IOException) {
        e.printStackTrace()
        AlertDialog.Builder(this@MainActivity)
            .setTitle("Ouch!")
            .setMessage("IoException:\n${e.localizedMessage}")
            .setPositiveButton("That hurts.") { _, _ ->
              this@MainActivity.finish()
            }
            .setIcon(android.R.drawable.ic_dialog_alert)
            .setCancelable(false)
            .show()
      }
    }

    clearButton.setOnClickListener { onClear() }
  }

  override fun onPoint(point: Point) {
    tryAndCatch {
      sendCommandChannel.offer(WhiteboardCommand(add_point = AddPoint(point)))
    }
  }

  override fun onClear() {
    tryAndCatch {
      sendCommandChannel.offer(WhiteboardCommand(clear_board = ClearBoard()))
    }
  }

  private fun initialiseBoard(initialiseBoard: InitialiseBoard) {
    whiteboardView.points = emptyList()
    whiteboardView.abstractWidth = initialiseBoard.width
    whiteboardView.abstractHeight = initialiseBoard.height
    whiteboardView.color = initialiseBoard.color
    whiteboardView.invalidate()
  }

  private fun updatePoints(updatePoints: UpdatePoints) {
    whiteboardView.points = updatePoints.points
    whiteboardView.invalidate()
  }

  class WhiteboardView(
    context: Context,
    attrs: AttributeSet?
  ) : View(context, attrs) {
    var onBoardEventListener: OnBoardEventListener? = null

    internal var points: List<Point> = emptyList()
    internal var abstractWidth = 1
    internal var abstractHeight = 1
    internal var color: Int = 0xff333333.toInt()

    private val realWidth: Int
      get() = width / abstractWidth
    private val realHeight: Int
      get() = height / abstractHeight

    override fun draw(canvas: Canvas) {
      super.draw(canvas)

      if (points.isEmpty()) {
        canvas.showEmptyView()
      }

      points.forEach { point ->
        val (realX, realY) = convertToReal(point.x, point.y)
        canvas.drawRect(
            realX - realWidth / 2f,
            realY - realHeight / 2f,
            realX + realWidth / 2f - 1,
            realY + realHeight / 2f - 1,
            Paint().apply { color = point.color }
        )
      }
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
      when (event.action) {
        MotionEvent.ACTION_DOWN,
        MotionEvent.ACTION_MOVE -> {
          val (abstractX, abstractY) = convertToAbstract(event.x, event.y)
          onBoardEventListener?.onPoint(Point(abstractX, abstractY, color))
        }
        else -> return false
      }

      return true
    }

    private fun convertToReal(
      x: Int,
      y: Int
    ) = Pair(
        x * width / abstractWidth,
        y * height / abstractHeight
    )

    private fun convertToAbstract(
      x: Float,
      y: Float
    ) = Pair(
        (x / width * abstractWidth).roundToInt(),
        (y / height * abstractHeight).roundToInt()
    )

    private fun Canvas.showEmptyView() {
      val centerX = width / 2
      val centerY = height / 2
      val text = "WIRE ♡ gRPC"
      val paint = Paint().apply {
        color = "#f46e38".toColorInt()
        textSize = 90f
      }

      drawText(
          text,
          (centerX - (paint.measureText(text) / 2).toInt()).toFloat(),
          (centerY - (paint.descent() + paint.ascent()) / 2).toInt().toFloat(),
          paint
      )
    }
  }

  private fun tryAndCatch(block: () -> Unit) {
    try {
      block()
    } catch (e: IOException) {
      e.printStackTrace()
      AlertDialog.Builder(this@MainActivity)
          .setTitle("Ouch!")
          .setMessage("IoException:\n${e.localizedMessage}")
          .setPositiveButton("That hurts.") { _, _ ->
            this@MainActivity.finish()
          }
          .setIcon(android.R.drawable.ic_dialog_alert)
          .setCancelable(false)
          .show()
    }
  }
}

interface OnBoardEventListener {
  fun onPoint(point: Point)
  fun onClear()
}

// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: com/squareup/wire/whiteboard/whiteboard.proto
package com.squareup.wire.whiteboard

import com.squareup.wire.GrpcStreamingCall
import com.squareup.wire.Service
import com.squareup.wire.WireRpc

interface WhiteboardClient : Service {
  @WireRpc(
    path = "/com.squareup.wire.whiteboard.Whiteboard/Whiteboard",
    requestAdapter = "com.squareup.wire.whiteboard.WhiteboardCommand#ADAPTER",
    responseAdapter = "com.squareup.wire.whiteboard.WhiteboardUpdate#ADAPTER"
  )
  fun Whiteboard(): GrpcStreamingCall<WhiteboardCommand, WhiteboardUpdate>
}

// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: com/squareup/wire/whiteboard/whiteboard.proto
package com.squareup.wire.whiteboard

import com.squareup.wire.FieldEncoding
import com.squareup.wire.Message
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.ProtoReader
import com.squareup.wire.ProtoWriter
import com.squareup.wire.WireField
import com.squareup.wire.internal.missingRequiredFields
import kotlin.Any
import kotlin.AssertionError
import kotlin.Boolean
import kotlin.Deprecated
import kotlin.DeprecationLevel
import kotlin.Int
import kotlin.Nothing
import kotlin.String
import kotlin.jvm.JvmField
import okio.ByteString

class Point(
  @field:WireField(
    tag = 1,
    adapter = "com.squareup.wire.ProtoAdapter#INT32",
    label = WireField.Label.REQUIRED
  )
  val x: Int,
  @field:WireField(
    tag = 2,
    adapter = "com.squareup.wire.ProtoAdapter#INT32",
    label = WireField.Label.REQUIRED
  )
  val y: Int,
  @field:WireField(
    tag = 3,
    adapter = "com.squareup.wire.ProtoAdapter#INT32",
    label = WireField.Label.REQUIRED
  )
  val color: Int,
  unknownFields: ByteString = ByteString.EMPTY
) : Message<Point, Nothing>(ADAPTER, unknownFields) {
  @Deprecated(
    message = "Shouldn't be used in Kotlin",
    level = DeprecationLevel.HIDDEN
  )
  override fun newBuilder(): Nothing = throw AssertionError()

  override fun equals(other: Any?): Boolean {
    if (other === this) return true
    if (other !is Point) return false
    return unknownFields == other.unknownFields
        && x == other.x
        && y == other.y
        && color == other.color
  }

  override fun hashCode(): Int {
    var result = super.hashCode
    if (result == 0) {
      result = unknownFields.hashCode()
      result = result * 37 + x.hashCode()
      result = result * 37 + y.hashCode()
      result = result * 37 + color.hashCode()
      super.hashCode = result
    }
    return result
  }

  override fun toString(): String {
    val result = mutableListOf<String>()
    result += """x=$x"""
    result += """y=$y"""
    result += """color=$color"""
    return result.joinToString(prefix = "Point{", separator = ", ", postfix = "}")
  }

  fun copy(
    x: Int = this.x,
    y: Int = this.y,
    color: Int = this.color,
    unknownFields: ByteString = this.unknownFields
  ): Point = Point(x, y, color, unknownFields)

  companion object {
    @JvmField
    val ADAPTER: ProtoAdapter<Point> = object : ProtoAdapter<Point>(
      FieldEncoding.LENGTH_DELIMITED,
      Point::class
    ) {
      override fun encodedSize(value: Point): Int =
        ProtoAdapter.INT32.encodedSizeWithTag(1, value.x) +
        ProtoAdapter.INT32.encodedSizeWithTag(2, value.y) +
        ProtoAdapter.INT32.encodedSizeWithTag(3, value.color) +
        value.unknownFields.size

      override fun encode(writer: ProtoWriter, value: Point) {
        ProtoAdapter.INT32.encodeWithTag(writer, 1, value.x)
        ProtoAdapter.INT32.encodeWithTag(writer, 2, value.y)
        ProtoAdapter.INT32.encodeWithTag(writer, 3, value.color)
        writer.writeBytes(value.unknownFields)
      }

      override fun decode(reader: ProtoReader): Point {
        var x: Int? = null
        var y: Int? = null
        var color: Int? = null
        val unknownFields = reader.forEachTag { tag ->
          when (tag) {
            1 -> x = ProtoAdapter.INT32.decode(reader)
            2 -> y = ProtoAdapter.INT32.decode(reader)
            3 -> color = ProtoAdapter.INT32.decode(reader)
            else -> reader.readUnknownField(tag)
          }
        }
        return Point(
          x = x ?: throw missingRequiredFields(x, "x"),
          y = y ?: throw missingRequiredFields(y, "y"),
          color = color ?: throw missingRequiredFields(color, "color"),
          unknownFields = unknownFields
        )
      }

      override fun redact(value: Point): Point = value.copy(
        unknownFields = ByteString.EMPTY
      )
    }
  }
}

// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: com/squareup/wire/whiteboard/whiteboard.proto
package com.squareup.wire.whiteboard

import com.squareup.wire.FieldEncoding
import com.squareup.wire.Message
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.ProtoReader
import com.squareup.wire.ProtoWriter
import com.squareup.wire.WireField
import com.squareup.wire.internal.countNonNull
import com.squareup.wire.internal.missingRequiredFields
import kotlin.Any
import kotlin.AssertionError
import kotlin.Boolean
import kotlin.Deprecated
import kotlin.DeprecationLevel
import kotlin.Int
import kotlin.Nothing
import kotlin.String
import kotlin.hashCode
import kotlin.jvm.JvmField
import okio.ByteString

class WhiteboardCommand(
  @field:WireField(
    tag = 1,
    adapter = "com.squareup.wire.whiteboard.WhiteboardCommand${'$'}AddPoint#ADAPTER"
  )
  val add_point: AddPoint? = null,
  @field:WireField(
    tag = 2,
    adapter = "com.squareup.wire.whiteboard.WhiteboardCommand${'$'}ClearBoard#ADAPTER"
  )
  val clear_board: ClearBoard? = null,
  unknownFields: ByteString = ByteString.EMPTY
) : Message<WhiteboardCommand, Nothing>(ADAPTER, unknownFields) {
  init {
    require(countNonNull(add_point, clear_board) <= 1) {
      "At most one of add_point, clear_board may be non-null"
    }
  }

  @Deprecated(
    message = "Shouldn't be used in Kotlin",
    level = DeprecationLevel.HIDDEN
  )
  override fun newBuilder(): Nothing = throw AssertionError()

  override fun equals(other: Any?): Boolean {
    if (other === this) return true
    if (other !is WhiteboardCommand) return false
    return unknownFields == other.unknownFields
        && add_point == other.add_point
        && clear_board == other.clear_board
  }

  override fun hashCode(): Int {
    var result = super.hashCode
    if (result == 0) {
      result = unknownFields.hashCode()
      result = result * 37 + add_point.hashCode()
      result = result * 37 + clear_board.hashCode()
      super.hashCode = result
    }
    return result
  }

  override fun toString(): String {
    val result = mutableListOf<String>()
    if (add_point != null) result += """add_point=$add_point"""
    if (clear_board != null) result += """clear_board=$clear_board"""
    return result.joinToString(prefix = "WhiteboardCommand{", separator = ", ", postfix = "}")
  }

  fun copy(
    add_point: AddPoint? = this.add_point,
    clear_board: ClearBoard? = this.clear_board,
    unknownFields: ByteString = this.unknownFields
  ): WhiteboardCommand = WhiteboardCommand(add_point, clear_board, unknownFields)

  companion object {
    @JvmField
    val ADAPTER: ProtoAdapter<WhiteboardCommand> = object : ProtoAdapter<WhiteboardCommand>(
      FieldEncoding.LENGTH_DELIMITED,
      WhiteboardCommand::class
    ) {
      override fun encodedSize(value: WhiteboardCommand): Int =
        AddPoint.ADAPTER.encodedSizeWithTag(1, value.add_point) +
        ClearBoard.ADAPTER.encodedSizeWithTag(2, value.clear_board) +
        value.unknownFields.size

      override fun encode(writer: ProtoWriter, value: WhiteboardCommand) {
        AddPoint.ADAPTER.encodeWithTag(writer, 1, value.add_point)
        ClearBoard.ADAPTER.encodeWithTag(writer, 2, value.clear_board)
        writer.writeBytes(value.unknownFields)
      }

      override fun decode(reader: ProtoReader): WhiteboardCommand {
        var add_point: AddPoint? = null
        var clear_board: ClearBoard? = null
        val unknownFields = reader.forEachTag { tag ->
          when (tag) {
            1 -> add_point = AddPoint.ADAPTER.decode(reader)
            2 -> clear_board = ClearBoard.ADAPTER.decode(reader)
            else -> reader.readUnknownField(tag)
          }
        }
        return WhiteboardCommand(
          add_point = add_point,
          clear_board = clear_board,
          unknownFields = unknownFields
        )
      }

      override fun redact(value: WhiteboardCommand): WhiteboardCommand = value.copy(
        add_point = value.add_point?.let(AddPoint.ADAPTER::redact),
        clear_board = value.clear_board?.let(ClearBoard.ADAPTER::redact),
        unknownFields = ByteString.EMPTY
      )
    }
  }

  class AddPoint(
    @field:WireField(
      tag = 1,
      adapter = "com.squareup.wire.whiteboard.Point#ADAPTER",
      label = WireField.Label.REQUIRED
    )
    val point: Point,
    unknownFields: ByteString = ByteString.EMPTY
  ) : Message<AddPoint, Nothing>(ADAPTER, unknownFields) {
    @Deprecated(
      message = "Shouldn't be used in Kotlin",
      level = DeprecationLevel.HIDDEN
    )
    override fun newBuilder(): Nothing = throw AssertionError()

    override fun equals(other: Any?): Boolean {
      if (other === this) return true
      if (other !is AddPoint) return false
      return unknownFields == other.unknownFields
          && point == other.point
    }

    override fun hashCode(): Int {
      var result = super.hashCode
      if (result == 0) {
        result = unknownFields.hashCode()
        result = result * 37 + point.hashCode()
        super.hashCode = result
      }
      return result
    }

    override fun toString(): String {
      val result = mutableListOf<String>()
      result += """point=$point"""
      return result.joinToString(prefix = "AddPoint{", separator = ", ", postfix = "}")
    }

    fun copy(point: Point = this.point, unknownFields: ByteString = this.unknownFields): AddPoint =
        AddPoint(point, unknownFields)

    companion object {
      @JvmField
      val ADAPTER: ProtoAdapter<AddPoint> = object : ProtoAdapter<AddPoint>(
        FieldEncoding.LENGTH_DELIMITED,
        AddPoint::class
      ) {
        override fun encodedSize(value: AddPoint): Int =
          Point.ADAPTER.encodedSizeWithTag(1, value.point) +
          value.unknownFields.size

        override fun encode(writer: ProtoWriter, value: AddPoint) {
          Point.ADAPTER.encodeWithTag(writer, 1, value.point)
          writer.writeBytes(value.unknownFields)
        }

        override fun decode(reader: ProtoReader): AddPoint {
          var point: Point? = null
          val unknownFields = reader.forEachTag { tag ->
            when (tag) {
              1 -> point = Point.ADAPTER.decode(reader)
              else -> reader.readUnknownField(tag)
            }
          }
          return AddPoint(
            point = point ?: throw missingRequiredFields(point, "point"),
            unknownFields = unknownFields
          )
        }

        override fun redact(value: AddPoint): AddPoint = value.copy(
          point = Point.ADAPTER.redact(value.point),
          unknownFields = ByteString.EMPTY
        )
      }
    }
  }

  class ClearBoard(
    unknownFields: ByteString = ByteString.EMPTY
  ) : Message<ClearBoard, Nothing>(ADAPTER, unknownFields) {
    @Deprecated(
      message = "Shouldn't be used in Kotlin",
      level = DeprecationLevel.HIDDEN
    )
    override fun newBuilder(): Nothing = throw AssertionError()

    override fun equals(other: Any?): Boolean {
      if (other === this) return true
      if (other !is ClearBoard) return false
      return unknownFields == other.unknownFields
    }

    override fun hashCode(): Int = unknownFields.hashCode()

    override fun toString(): String = "ClearBoard{}"

    fun copy(unknownFields: ByteString = this.unknownFields): ClearBoard = ClearBoard(unknownFields)

    companion object {
      @JvmField
      val ADAPTER: ProtoAdapter<ClearBoard> = object : ProtoAdapter<ClearBoard>(
        FieldEncoding.LENGTH_DELIMITED,
        ClearBoard::class
      ) {
        override fun encodedSize(value: ClearBoard): Int =
          value.unknownFields.size

        override fun encode(writer: ProtoWriter, value: ClearBoard) {
          writer.writeBytes(value.unknownFields)
        }

        override fun decode(reader: ProtoReader): ClearBoard {
          val unknownFields = reader.forEachTag(reader::readUnknownField)
          return ClearBoard(
            unknownFields = unknownFields
          )
        }

        override fun redact(value: ClearBoard): ClearBoard = value.copy(
          unknownFields = ByteString.EMPTY
        )
      }
    }
  }
}

// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: com/squareup/wire/whiteboard/whiteboard.proto
package com.squareup.wire.whiteboard

import com.squareup.wire.FieldEncoding
import com.squareup.wire.Message
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.ProtoReader
import com.squareup.wire.ProtoWriter
import com.squareup.wire.WireField
import com.squareup.wire.internal.countNonNull
import com.squareup.wire.internal.missingRequiredFields
import com.squareup.wire.internal.redactElements
import kotlin.Any
import kotlin.AssertionError
import kotlin.Boolean
import kotlin.Deprecated
import kotlin.DeprecationLevel
import kotlin.Int
import kotlin.Nothing
import kotlin.String
import kotlin.collections.List
import kotlin.hashCode
import kotlin.jvm.JvmField
import okio.ByteString

class WhiteboardUpdate(
  @field:WireField(
    tag = 1,
    adapter = "com.squareup.wire.whiteboard.WhiteboardUpdate${'$'}InitialiseBoard#ADAPTER"
  )
  val initialise_board: InitialiseBoard? = null,
  @field:WireField(
    tag = 2,
    adapter = "com.squareup.wire.whiteboard.WhiteboardUpdate${'$'}UpdatePoints#ADAPTER"
  )
  val update_points: UpdatePoints? = null,
  unknownFields: ByteString = ByteString.EMPTY
) : Message<WhiteboardUpdate, Nothing>(ADAPTER, unknownFields) {
  init {
    require(countNonNull(initialise_board, update_points) <= 1) {
      "At most one of initialise_board, update_points may be non-null"
    }
  }

  @Deprecated(
    message = "Shouldn't be used in Kotlin",
    level = DeprecationLevel.HIDDEN
  )
  override fun newBuilder(): Nothing = throw AssertionError()

  override fun equals(other: Any?): Boolean {
    if (other === this) return true
    if (other !is WhiteboardUpdate) return false
    return unknownFields == other.unknownFields
        && initialise_board == other.initialise_board
        && update_points == other.update_points
  }

  override fun hashCode(): Int {
    var result = super.hashCode
    if (result == 0) {
      result = unknownFields.hashCode()
      result = result * 37 + initialise_board.hashCode()
      result = result * 37 + update_points.hashCode()
      super.hashCode = result
    }
    return result
  }

  override fun toString(): String {
    val result = mutableListOf<String>()
    if (initialise_board != null) result += """initialise_board=$initialise_board"""
    if (update_points != null) result += """update_points=$update_points"""
    return result.joinToString(prefix = "WhiteboardUpdate{", separator = ", ", postfix = "}")
  }

  fun copy(
    initialise_board: InitialiseBoard? = this.initialise_board,
    update_points: UpdatePoints? = this.update_points,
    unknownFields: ByteString = this.unknownFields
  ): WhiteboardUpdate = WhiteboardUpdate(initialise_board, update_points, unknownFields)

  companion object {
    @JvmField
    val ADAPTER: ProtoAdapter<WhiteboardUpdate> = object : ProtoAdapter<WhiteboardUpdate>(
      FieldEncoding.LENGTH_DELIMITED,
      WhiteboardUpdate::class
    ) {
      override fun encodedSize(value: WhiteboardUpdate): Int =
        InitialiseBoard.ADAPTER.encodedSizeWithTag(1, value.initialise_board) +
        UpdatePoints.ADAPTER.encodedSizeWithTag(2, value.update_points) +
        value.unknownFields.size

      override fun encode(writer: ProtoWriter, value: WhiteboardUpdate) {
        InitialiseBoard.ADAPTER.encodeWithTag(writer, 1, value.initialise_board)
        UpdatePoints.ADAPTER.encodeWithTag(writer, 2, value.update_points)
        writer.writeBytes(value.unknownFields)
      }

      override fun decode(reader: ProtoReader): WhiteboardUpdate {
        var initialise_board: InitialiseBoard? = null
        var update_points: UpdatePoints? = null
        val unknownFields = reader.forEachTag { tag ->
          when (tag) {
            1 -> initialise_board = InitialiseBoard.ADAPTER.decode(reader)
            2 -> update_points = UpdatePoints.ADAPTER.decode(reader)
            else -> reader.readUnknownField(tag)
          }
        }
        return WhiteboardUpdate(
          initialise_board = initialise_board,
          update_points = update_points,
          unknownFields = unknownFields
        )
      }

      override fun redact(value: WhiteboardUpdate): WhiteboardUpdate = value.copy(
        initialise_board = value.initialise_board?.let(InitialiseBoard.ADAPTER::redact),
        update_points = value.update_points?.let(UpdatePoints.ADAPTER::redact),
        unknownFields = ByteString.EMPTY
      )
    }
  }

  class InitialiseBoard(
    @field:WireField(
      tag = 1,
      adapter = "com.squareup.wire.ProtoAdapter#INT32",
      label = WireField.Label.REQUIRED
    )
    val width: Int,
    @field:WireField(
      tag = 2,
      adapter = "com.squareup.wire.ProtoAdapter#INT32",
      label = WireField.Label.REQUIRED
    )
    val height: Int,
    @field:WireField(
      tag = 3,
      adapter = "com.squareup.wire.ProtoAdapter#INT32",
      label = WireField.Label.REQUIRED
    )
    val color: Int,
    unknownFields: ByteString = ByteString.EMPTY
  ) : Message<InitialiseBoard, Nothing>(ADAPTER, unknownFields) {
    @Deprecated(
      message = "Shouldn't be used in Kotlin",
      level = DeprecationLevel.HIDDEN
    )
    override fun newBuilder(): Nothing = throw AssertionError()

    override fun equals(other: Any?): Boolean {
      if (other === this) return true
      if (other !is InitialiseBoard) return false
      return unknownFields == other.unknownFields
          && width == other.width
          && height == other.height
          && color == other.color
    }

    override fun hashCode(): Int {
      var result = super.hashCode
      if (result == 0) {
        result = unknownFields.hashCode()
        result = result * 37 + width.hashCode()
        result = result * 37 + height.hashCode()
        result = result * 37 + color.hashCode()
        super.hashCode = result
      }
      return result
    }

    override fun toString(): String {
      val result = mutableListOf<String>()
      result += """width=$width"""
      result += """height=$height"""
      result += """color=$color"""
      return result.joinToString(prefix = "InitialiseBoard{", separator = ", ", postfix = "}")
    }

    fun copy(
      width: Int = this.width,
      height: Int = this.height,
      color: Int = this.color,
      unknownFields: ByteString = this.unknownFields
    ): InitialiseBoard = InitialiseBoard(width, height, color, unknownFields)

    companion object {
      @JvmField
      val ADAPTER: ProtoAdapter<InitialiseBoard> = object : ProtoAdapter<InitialiseBoard>(
        FieldEncoding.LENGTH_DELIMITED,
        InitialiseBoard::class
      ) {
        override fun encodedSize(value: InitialiseBoard): Int =
          ProtoAdapter.INT32.encodedSizeWithTag(1, value.width) +
          ProtoAdapter.INT32.encodedSizeWithTag(2, value.height) +
          ProtoAdapter.INT32.encodedSizeWithTag(3, value.color) +
          value.unknownFields.size

        override fun encode(writer: ProtoWriter, value: InitialiseBoard) {
          ProtoAdapter.INT32.encodeWithTag(writer, 1, value.width)
          ProtoAdapter.INT32.encodeWithTag(writer, 2, value.height)
          ProtoAdapter.INT32.encodeWithTag(writer, 3, value.color)
          writer.writeBytes(value.unknownFields)
        }

        override fun decode(reader: ProtoReader): InitialiseBoard {
          var width: Int? = null
          var height: Int? = null
          var color: Int? = null
          val unknownFields = reader.forEachTag { tag ->
            when (tag) {
              1 -> width = ProtoAdapter.INT32.decode(reader)
              2 -> height = ProtoAdapter.INT32.decode(reader)
              3 -> color = ProtoAdapter.INT32.decode(reader)
              else -> reader.readUnknownField(tag)
            }
          }
          return InitialiseBoard(
            width = width ?: throw missingRequiredFields(width, "width"),
            height = height ?: throw missingRequiredFields(height, "height"),
            color = color ?: throw missingRequiredFields(color, "color"),
            unknownFields = unknownFields
          )
        }

        override fun redact(value: InitialiseBoard): InitialiseBoard = value.copy(
          unknownFields = ByteString.EMPTY
        )
      }
    }
  }

  class UpdatePoints(
    @field:WireField(
      tag = 1,
      adapter = "com.squareup.wire.whiteboard.Point#ADAPTER",
      label = WireField.Label.REPEATED
    )
    val points: List<Point> = emptyList(),
    unknownFields: ByteString = ByteString.EMPTY
  ) : Message<UpdatePoints, Nothing>(ADAPTER, unknownFields) {
    @Deprecated(
      message = "Shouldn't be used in Kotlin",
      level = DeprecationLevel.HIDDEN
    )
    override fun newBuilder(): Nothing = throw AssertionError()

    override fun equals(other: Any?): Boolean {
      if (other === this) return true
      if (other !is UpdatePoints) return false
      return unknownFields == other.unknownFields
          && points == other.points
    }

    override fun hashCode(): Int {
      var result = super.hashCode
      if (result == 0) {
        result = unknownFields.hashCode()
        result = result * 37 + points.hashCode()
        super.hashCode = result
      }
      return result
    }

    override fun toString(): String {
      val result = mutableListOf<String>()
      if (points.isNotEmpty()) result += """points=$points"""
      return result.joinToString(prefix = "UpdatePoints{", separator = ", ", postfix = "}")
    }

    fun copy(points: List<Point> = this.points, unknownFields: ByteString = this.unknownFields):
        UpdatePoints = UpdatePoints(points, unknownFields)

    companion object {
      @JvmField
      val ADAPTER: ProtoAdapter<UpdatePoints> = object : ProtoAdapter<UpdatePoints>(
        FieldEncoding.LENGTH_DELIMITED,
        UpdatePoints::class
      ) {
        override fun encodedSize(value: UpdatePoints): Int =
          Point.ADAPTER.asRepeated().encodedSizeWithTag(1, value.points) +
          value.unknownFields.size

        override fun encode(writer: ProtoWriter, value: UpdatePoints) {
          Point.ADAPTER.asRepeated().encodeWithTag(writer, 1, value.points)
          writer.writeBytes(value.unknownFields)
        }

        override fun decode(reader: ProtoReader): UpdatePoints {
          val points = mutableListOf<Point>()
          val unknownFields = reader.forEachTag { tag ->
            when (tag) {
              1 -> points.add(Point.ADAPTER.decode(reader))
              else -> reader.readUnknownField(tag)
            }
          }
          return UpdatePoints(
            points = points,
            unknownFields = unknownFields
          )
        }

        override fun redact(value: UpdatePoints): UpdatePoints = value.copy(
          points = value.points.redactElements(Point.ADAPTER),
          unknownFields = ByteString.EMPTY
        )
      }
    }
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.whiteboard

import com.squareup.wire.MessageSource

fun <T : Any> MessageSource<T>.consumeEachAndClose(block: (T) -> Unit) {
  use {
    while (true) {
      val message = read() ?: return
      block(message)
    }
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.whiteboard

import misk.MiskApplication
import misk.MiskRealServiceModule
import misk.config.ConfigModule
import misk.config.MiskConfig
import misk.environment.Environment
import misk.environment.EnvironmentModule
import misk.web.MiskWebModule

fun main(args: Array<String>) {
  val environment = Environment.fromEnvironmentVariable()
  val config = MiskConfig.load<WhiteboardConfig>("whiteboard", environment)

  MiskApplication(
      MiskRealServiceModule(),
      MiskWebModule(config.web),
      WhiteboardGrpcModule(),
      ConfigModule.create("whiteboard", config),
      EnvironmentModule(environment)
  ).run(args)
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.whiteboard

import misk.config.Config
import misk.web.WebConfig

data class WhiteboardConfig(
  val web: WebConfig
) : Config

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.whiteboard

import com.squareup.wire.MessageSink
import com.squareup.wire.MessageSource
import com.squareup.wire.whiteboard.WhiteboardUpdate.InitialiseBoard
import com.squareup.wire.whiteboard.WhiteboardUpdate.UpdatePoints
import misk.web.actions.WebAction
import javax.inject.Singleton

@Singleton
class WhiteboardGrpcAction : WebAction, WhiteboardWhiteboardBlockingServer {
  val clients = mutableListOf<Client>()
  val points = mutableListOf<Point>()

  override fun Whiteboard(
    request: MessageSource<WhiteboardCommand>,
    response: MessageSink<WhiteboardUpdate>
  ) {
    val client = Client(request, response)
    clients += client
    client.process()
  }

  inner class Client(
    private val commands: MessageSource<WhiteboardCommand>,
    private val updates: MessageSink<WhiteboardUpdate>
  ) {
    fun process() {
      updates.use {
        val initialiseBoard = InitialiseBoard(
            BOARD_WIDTH,
            BOARD_HEIGHT,
            COLORS[clients.indexOf(this) % COLORS.size]
        )
        updates.write(WhiteboardUpdate(initialise_board = initialiseBoard))
        updates.write(WhiteboardUpdate(update_points = UpdatePoints(points)))

        commands.consumeEachAndClose { command ->
          when {
            command.add_point != null -> points += command.add_point!!.point
            command.clear_board != null -> points.clear()
            else -> throw IllegalArgumentException("Unexpected command $command")
          }

          for (client in clients) {
            synchronized(WhiteboardGrpcAction::class.java) {
              client.updates.write(WhiteboardUpdate(update_points = UpdatePoints(points)))
            }
          }
        }
      }
    }
  }

  companion object {
    private const val BOARD_WIDTH = 60
    private const val BOARD_HEIGHT = 80

    private val COLORS = arrayOf(
        0xff00c8fa.toInt(), // Blue
        0xfff46e38.toInt(), // Red
        0xff0bb634.toInt(), // Green
        0xff333333.toInt() // Black
    )
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.whiteboard

import misk.inject.KAbstractModule
import misk.web.WebActionModule

class WhiteboardGrpcModule : KAbstractModule() {
  override fun configure() {
    install(WebActionModule.create<WhiteboardGrpcAction>())
  }
}

// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: com/squareup/wire/whiteboard/whiteboard.proto
package com.squareup.wire.whiteboard

import com.squareup.wire.MessageSink
import com.squareup.wire.MessageSource
import com.squareup.wire.Service
import com.squareup.wire.WireRpc

interface WhiteboardWhiteboardBlockingServer : Service {
  @WireRpc(
    path = "/com.squareup.wire.whiteboard.Whiteboard/Whiteboard",
    requestAdapter = "com.squareup.wire.whiteboard.WhiteboardCommand#ADAPTER",
    responseAdapter = "com.squareup.wire.whiteboard.WhiteboardUpdate#ADAPTER"
  )
  fun Whiteboard(request: MessageSource<WhiteboardCommand>, response: MessageSink<WhiteboardUpdate>)
}

/*
 * Copyright 2018 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.google.common.io.Closer
import com.squareup.wire.java.Profile
import com.squareup.wire.java.internal.ProfileFileElement
import com.squareup.wire.schema.internal.parser.ProtoFileElement
import java.io.Closeable
import java.io.IOException
import java.nio.file.FileSystem
import java.util.ArrayDeque
import java.util.TreeSet

/**
 * Load proto files and their transitive dependencies and parse them. Keep track of which files were
 * loaded from where so that we can use that information later when deciding what to generate.
 */
class NewSchemaLoader(
  private val fs: FileSystem
) : Closeable, Loader, NewProfileLoader {
  private val closer = Closer.create()

  /** Errors accumulated by this load. */
  private val errors = mutableListOf<String>()

  /** Paths accumulated that we failed to load. */
  private val missingImports = TreeSet<String>()

  /** Source path roots that need to be closed */
  private var sourcePathRoots: List<Root>? = null

  /** Proto path roots that need to be closed */
  private var protoPathRoots: List<Root>? = null

  /** Subset of the schema that was loaded from the source path. */
  lateinit var sourcePathFiles: List<ProtoFile>
    private set

  /** Keys are a [Location.base]; values are the roots that those locations loaded from. */
  private val baseToRoots = mutableMapOf<String, List<Root>>()

  /** Initialize the [WireRun.sourcePath] and [WireRun.protoPath] from which files are loaded. */
  fun initRoots(
    sourcePath: List<Location>,
    protoPath: List<Location> = listOf()
  ) {
    check(sourcePathRoots == null && protoPathRoots == null)
    sourcePathRoots = allRoots(closer, sourcePath)
    protoPathRoots = allRoots(closer, protoPath)
  }

  @Throws(IOException::class)
  fun loadSchema(): Schema {
    sourcePathFiles = loadSourcePathFiles()
    val result = try {
      Schema.fromFiles(sourcePathFiles, this)
    } catch (e: Exception) {
      // TODO(jwilson): collect a single set of errors in loader and linker.
      reportLoadingErrors()
      throw e
    }
    reportLoadingErrors()
    return result
  }

  /** Returns the files in the source path. */
  @Throws(IOException::class)
  internal fun loadSourcePathFiles(): List<ProtoFile> {
    check(sourcePathRoots != null && protoPathRoots != null) {
      "call initRoots() before calling loadSourcePathFiles()"
    }

    val result = mutableListOf<ProtoFile>()
    for (sourceRoot in sourcePathRoots!!) {
      for (locationAndPath in sourceRoot.allProtoFiles()) {
        result += load(locationAndPath)
      }
    }

    if (result.isEmpty()) {
      errors += "no sources"
    }

    checkForErrors()

    return result
  }

  override fun load(path: String): ProtoFile {
    // Traverse roots in search of the one that has this path.
    var loadFrom: ProtoFilePath? = null
    for (protoPathRoot in protoPathRoots!!) {
      val locationAndPath: ProtoFilePath = protoPathRoot.resolve(path) ?: continue
      if (loadFrom != null) {
        errors += "$path is ambiguous:\n  $locationAndPath\n  $loadFrom"
        continue
      }
      loadFrom = locationAndPath
    }

    if (loadFrom != null) {
      return load(loadFrom)
    }

    if (path == CoreLoader.ANY_PROTO ||
        path == CoreLoader.DESCRIPTOR_PROTO ||
        path == CoreLoader.WIRE_EXTENSIONS_PROTO) {
      return CoreLoader.load(path)
    }

    missingImports += path
    return ProtoFile.get(ProtoFileElement.empty(path))
  }

  private fun load(protoFilePath: ProtoFilePath): ProtoFile {
    val protoFile = protoFilePath.parse()
    val importPath = protoFile.importPath(protoFilePath.location)

    // If the .proto was specified as a full path without a separate base directory that it's
    // relative to, confirm that the import path and file system path agree.
    if (protoFilePath.location.base.isEmpty()
        && protoFilePath.location.path != importPath
        && !protoFilePath.location.path.endsWith("/$importPath")) {
      errors += "expected ${protoFilePath.location.path} to have a path ending with $importPath"
    }

    return protoFile
  }

  /** Convert `pathStrings` into roots that can be searched. */
  private fun allRoots(closer: Closer, locations: List<Location>): List<Root> {
    val result = mutableListOf<Root>()
    for (location in locations) {
      try {
        result += location.roots(fs, closer, baseToRoots)
      } catch (e: IllegalArgumentException) {
        errors += e.message!!
      }
    }
    return result
  }

  internal fun reportLoadingErrors() {
    if (missingImports.isNotEmpty()) {
      errors += """
          |unable to resolve ${missingImports.size} imports:
          |  ${missingImports.joinToString(separator = "\n  ")}
          |searching ${protoPathRoots!!.size} proto paths:
          |  ${protoPathRoots!!.joinToString(separator = "\n  ")}
          """.trimMargin()
    }
    checkForErrors()
  }

  private fun checkForErrors() {
    require(errors.isEmpty()) { errors.joinToString(separator = "\n") }
  }

  override fun close() {
    return closer.close()
  }

  override fun loadProfile(name: String, schema: Schema): Profile {
    val allLocations = schema.protoFiles.map { it.location }
    val locationsToCheck = locationsToCheck(name, allLocations)

    val profileElements = mutableListOf<ProfileFileElement>()
    for (location in locationsToCheck) {
      val roots = baseToRoots[location.base] ?: continue
      for (root in roots) {
        val resolved = root.resolve(location.path) ?: continue
        profileElements += resolved.parseProfile()
      }
    }

    val profile = Profile(profileElements)
    validate(schema, profileElements)
    return profile
  }

  /** Confirms that `protoFiles` link correctly against `schema`.  */
  private fun validate(schema: Schema, profileFiles: List<ProfileFileElement>) {
    for (profileFile in profileFiles) {
      for (typeConfig in profileFile.typeConfigs) {
        val type = importedType(ProtoType.get(typeConfig.type)) ?: continue

        val resolvedType = schema.getType(type)
        if (resolvedType == null) {
          // This type is either absent from .proto files, or merely not loaded because our schema
          // is incomplete. Unfortunately we can't tell the difference! Assume that this type is
          // just absent from the schema-as-loaded and therefore irrelevant to the current project.
          // Ignore it!
          //
          // (A fancier implementation would load the schema and profile in one step and they would
          // be mutually complete. We aren't bothering with this correctness at this phase.)
          continue
        }

        val requiredImport = resolvedType.location.path
        if (!profileFile.imports.contains(requiredImport)) {
          errors.add(String.format("%s needs to import %s (%s)",
              typeConfig.location.path, requiredImport, typeConfig.location))
        }
      }
    }

    checkForErrors()
  }

  /** Returns the type to import for `type`.  */
  private fun importedType(type: ProtoType): ProtoType? {
    var type = type
    // Map key type is always scalar.
    if (type.isMap) type = type.valueType!!
    return if (type.isScalar) null else type
  }

  /**
   * Returns a list of locations to check for profile files. This is the profile file name (like
   * "java.wire") in the same directory, and in all parent directories up to the base.
   */
  internal fun locationsToCheck(name: String, input: List<Location>): Set<Location> {
    val queue = ArrayDeque<Location>(input)

    val result = mutableSetOf<Location>()
    while (true) {
      val protoLocation = queue.poll() ?: break
      val lastSlash = protoLocation.path.lastIndexOf("/")
      val parentPath = protoLocation.path.substring(0, lastSlash + 1)
      val profileLocation = protoLocation.copy(path = "$parentPath$name.wire")

      if (!result.add(profileLocation)) continue // Already added.
      if (!parentPath.isNotEmpty()) continue // No more parents to enqueue.
      queue += protoLocation.copy(path = parentPath.dropLast(1)) // Drop trailing '/'.
    }
    return result
  }
}

internal fun ProtoFile.importPath(location: Location): String {
  return when {
    location.base.isEmpty() -> canonicalImportPath(location)
    else -> location.path
  }
}

internal fun ProtoFile.canonicalImportPath(location: Location): String {
  val filename = location.path.substringAfterLast('/')
  return packageName!!.replace('.', '/') + "/" + filename
}

/*
 * Copyright 2018 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.google.common.io.Closer
import com.squareup.wire.java.internal.ProfileFileElement
import com.squareup.wire.java.internal.ProfileParser
import com.squareup.wire.schema.internal.parser.ProtoParser
import okio.buffer
import okio.source
import java.io.IOException
import java.nio.file.FileSystem
import java.nio.file.FileSystems
import java.nio.file.FileVisitOption
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.ProviderNotFoundException
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes

internal sealed class Root {
  abstract val base: String?

  /** Returns all proto files within this root. */
  abstract fun allProtoFiles(): Set<ProtoFilePath>

  /** Returns the proto file if it's in this root, or null if it isn't. */
  abstract fun resolve(import: String): ProtoFilePath?
}

/**
 * Returns this location's roots.
 *
 * @param baseToRoots cached roots to avoid opening the same .zip multiple times.
 */
// TODO(jwilson): rework this API to combine baseToRoots and Closer.
internal fun Location.roots(
  fs: FileSystem,
  closer: Closer,
  baseToRoots: MutableMap<String, List<Root>> = mutableMapOf()
): List<Root> {
  if (base.isNotEmpty()) {
    val roots = baseToRoots.computeIfAbsent(base) {
      Location.get(it).roots(fs, closer)
    }
    for (root in roots) {
      val resolved = root.resolve(path) ?: continue
      return listOf(resolved)
    }
    throw IllegalArgumentException("unable to resolve $this")
  } else {
    val path = fs.getPath(path)
    return baseToRoots.computeIfAbsent(this.path) {
      path.roots(closer, this)
    }
  }
}

/** Returns this path's roots. */
private fun Path.roots(closer: Closer, location: Location): List<Root> {
  return when {
    Files.isDirectory(this) -> {
      check(location.base.isEmpty())
      listOf(DirectoryRoot(location.path, this))
    }

    endsWithDotProto() -> listOf(ProtoFilePath(location, this))

    // Handle a .zip or .jar file by adding all .proto files within.
    else -> {
      check(location.base.isEmpty())
      try {
        val sourceFs = FileSystems.newFileSystem(this, javaClass.classLoader)
        closer.register(sourceFs)
        sourceFs.rootDirectories.map { DirectoryRoot(location.path, it) }
      } catch (e: ProviderNotFoundException) {
        throw IllegalArgumentException(
            "expected a directory, archive (.zip / .jar / etc.), or .proto: $this")
      }
    }
  }
}

/**
 * A logical location (the base location and path to the file), plus the physical path to load.
 * These will be different if the file is loaded from a .zip archive.
 */
internal class ProtoFilePath(
  val location: Location,
  val path: Path
) : Root() {
  override val base: String?
    get() = null

  override fun allProtoFiles() = setOf(this)

  override fun resolve(import: String): ProtoFilePath? {
    if (import == location.path) return this
    return null
  }

  /**
   * Returns the parsed proto file and the path that should be used to import it.
   *
   * This is a path like `squareup/dinosaurs/Dinosaur.proto` for a file based on its package name
   * (like `squareup.dinosaurs`) and its file name (like `Dinosaur.proto`).
   */
  fun parse(): ProtoFile {
    try {
      path.source().buffer().use { source ->
        val data = source.readUtf8()
        val element = ProtoParser.parse(location, data)
        return ProtoFile.get(element)
      }
    } catch (e: IOException) {
      throw IOException("Failed to load $path", e)
    }
  }

  fun parseProfile(): ProfileFileElement {
    try {
      path.source().buffer().use { source ->
        val data = source.readUtf8()
        val element = ProfileParser(location, data).read()
        return element
      }
    } catch (e: IOException) {
      throw IOException("Failed to load $path", e)
    }
  }

  override fun toString(): String = location.toString()
}

internal class DirectoryRoot(
  /** The location of either a directory or .zip file. */
  override val base: String,

  /** The root to search. If this is a .zip file this is within its internal file system. */
  val rootDirectory: Path
) : Root() {
  override fun allProtoFiles(): Set<ProtoFilePath> {
    val result = mutableSetOf<ProtoFilePath>()
    Files.walkFileTree(rootDirectory, setOf(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
        object : SimpleFileVisitor<Path>() {
          override fun visitFile(descendant: Path, attrs: BasicFileAttributes): FileVisitResult {
            if (descendant.endsWithDotProto()) {
              val location = Location.get(base, "${rootDirectory.relativize(descendant)}")
              result.add(ProtoFilePath(location, descendant))
            }
            return FileVisitResult.CONTINUE
          }
        })
    return result
  }

  override fun resolve(import: String): ProtoFilePath? {
    val resolved = rootDirectory.resolve(import)
    if (!Files.exists(resolved)) return null
    return ProtoFilePath(Location.get(base, import), resolved)
  }

  override fun toString(): String = base
}

private fun Path.endsWithDotProto() = fileName.toString().endsWith(".proto")

/*
 * Copyright 2018 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.javapoet.JavaFile
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.wire.WireCompiler
import com.squareup.wire.WireLogger
import com.squareup.wire.java.JavaGenerator
import com.squareup.wire.java.Profile
import com.squareup.wire.kotlin.KotlinGenerator
import com.squareup.wire.kotlin.RpcCallStyle
import com.squareup.wire.kotlin.RpcRole
import okio.buffer
import okio.sink
import java.io.IOException
import java.io.Serializable
import java.nio.file.FileSystem
import java.nio.file.Files

sealed class Target : Serializable {
  /**
   * Proto types to include generated sources for. Types listed here will be generated for this
   * target and not for subsequent targets in the task.
   *
   * This list should contain package names (suffixed with `.*`) and type names only. It should
   * not contain member names.
   */
  abstract val includes: List<String>

  /**
   * Proto types to excluded generated sources for. Types listed here will not be generated for this
   * target.
   *
   * This list should contain package names (suffixed with `.*`) and type names only. It should
   * not contain member names.
   */
  abstract val excludes: List<String>

  /**
   * True if types emitted for this target should not also be emitted for other targets. Use this
   * to cause multiple outputs to be emitted for the same input type.
   */
  abstract val exclusive: Boolean

  internal abstract fun newHandler(
    schema: Schema,
    fs: FileSystem,
    logger: WireLogger,
    newProfileLoader: NewProfileLoader
  ): SchemaHandler

  interface SchemaHandler {
    fun handle(type: Type)
    fun handle(service: Service)
    /**
     * This will handle all [Type]s and [Service]s of the `protoFile` in respect to the emitting
     * rules. If exclusive, the handled [Type]s and [Service]s should be added to the consumed set.
     * Consumed types and services themselves are to be omitted by this handler.
     */
    fun handle(
      protoFile: ProtoFile,
      emittingRules: EmittingRules,
      claimedDefinitions: ClaimedDefinitions,
      isExclusive: Boolean
    ) {
      protoFile.types
          .filter { it !in claimedDefinitions && emittingRules.includes(it.type) }
          .forEach { type ->
            handle(type)
            // We don't let other targets handle this one.
            if (isExclusive) claimedDefinitions.claim(type)
          }

      protoFile.services
          .filter { it !in claimedDefinitions && emittingRules.includes(it.type()) }
          .forEach { service ->
            handle(service)
            // We don't let other targets handle this one.
            if (isExclusive) claimedDefinitions.claim(service)
          }
    }
  }
}

/** Generate `.java` sources. */
data class JavaTarget(
  override val includes: List<String> = listOf("*"),
  override val excludes: List<String> = listOf(),

  override val exclusive: Boolean = true,

  val outDirectory: String,

  /** True for emitted types to implement `android.os.Parcelable`. */
  val android: Boolean = false,

  /** True to enable the `androidx.annotation.Nullable` annotation where applicable. */
  val androidAnnotations: Boolean = false,

  /**
   * True to emit code that uses reflection for reading, writing, and toString methods which are
   * normally implemented with generated code.
   */
  val compact: Boolean = false
) : Target() {
  override fun newHandler(
    schema: Schema,
    fs: FileSystem,
    logger: WireLogger,
    newProfileLoader: NewProfileLoader
  ): SchemaHandler {
    val profileName = if (android) "android" else "java"
    val profile = newProfileLoader.loadProfile(profileName, schema)

    val javaGenerator = JavaGenerator.get(schema)
        .withProfile(profile)
        .withAndroid(android)
        .withAndroidAnnotations(androidAnnotations)
        .withCompact(compact)

    return object : SchemaHandler {
      override fun handle(type: Type) {
        val typeSpec = javaGenerator.generateType(type)
        val javaTypeName = javaGenerator.generatedTypeName(type)
        val javaFile = JavaFile.builder(javaTypeName.packageName(), typeSpec)
            .addFileComment("\$L", WireCompiler.CODE_GENERATED_BY_WIRE)
            .apply {
              addFileComment("\nSource file: \$L", type.location.withPathOnly())
            }.build()

        val path = fs.getPath(outDirectory)
        logger.artifact(path, javaFile)
        try {
          javaFile.writeTo(path)
        } catch (e: IOException) {
          throw IOException("Error emitting ${javaFile.packageName}.${javaFile.typeSpec.name} " +
              "to $outDirectory", e)
        }
      }

      override fun handle(service: Service) {
        // Service handling isn't supporting in Java.
      }
    }
  }
}

/** Generate `.kt` sources. */
data class KotlinTarget(
  override val includes: List<String> = listOf("*"),
  override val excludes: List<String> = listOf(),

  override val exclusive: Boolean = true,

  val outDirectory: String,

  /** True for emitted types to implement `android.os.Parcelable`. */
  val android: Boolean = false,

  /** True for emitted types to implement APIs for easier migration from the Java target. */
  val javaInterop: Boolean = false,

  /** Blocking or suspending. */
  val rpcCallStyle: RpcCallStyle = RpcCallStyle.SUSPENDING,

  /** Client or server. */
  val rpcRole: RpcRole = RpcRole.CLIENT,

  /** True for emitted services to implement one interface per RPC. */
  val singleMethodServices: Boolean = false
) : Target() {
  override fun newHandler(
    schema: Schema,
    fs: FileSystem,
    logger: WireLogger,
    newProfileLoader: NewProfileLoader
  ): SchemaHandler {
    val kotlinGenerator = KotlinGenerator(
        schema = schema,
        emitAndroid = android,
        javaInterop = javaInterop,
        rpcCallStyle = rpcCallStyle,
        rpcRole = rpcRole
    )

    return object : SchemaHandler {
      override fun handle(type: Type) {
        val typeSpec = kotlinGenerator.generateType(type)
        val className = kotlinGenerator.generatedTypeName(type)
        val kotlinFile = FileSpec.builder(className.packageName, typeSpec.name!!)
            .addComment(WireCompiler.CODE_GENERATED_BY_WIRE)
            .apply {
              addComment("\nSource file: %L", type.location.withPathOnly())
            }
            .addType(typeSpec)
            .build()

        val path = fs.getPath(outDirectory)
        logger.artifact(path, kotlinFile)

        try {
          kotlinFile.writeTo(path)
        } catch (e: IOException) {
          throw IOException("Error emitting " +
              "${kotlinFile.packageName}.${className.canonicalName} to $outDirectory", e)
        }
      }

      override fun handle(service: Service) {
        if (singleMethodServices) {
          service.rpcs().forEach { rpc ->
            write(
                service,
                kotlinGenerator.generatedServiceName(service, rpc),
                kotlinGenerator.generateService(service, rpc)
            )
          }
        } else {
          write(
              service,
              kotlinGenerator.generatedServiceName(service),
              kotlinGenerator.generateService(service)
          )
        }
      }

      private fun write(service: Service, name: ClassName, typeSpec: TypeSpec) {
        val kotlinFile = FileSpec.builder(name.packageName, name.simpleName)
            .addComment(WireCompiler.CODE_GENERATED_BY_WIRE)
            .addComment("\nSource file: %L", service.location().withPathOnly())
            .addType(typeSpec)
            .build()

        val path = fs.getPath(outDirectory)
        logger.artifact(path, kotlinFile)

        try {
          kotlinFile.writeTo(path)
        } catch (e: IOException) {
          throw IOException("Error emitting " +
              "${kotlinFile.packageName}.${service.type()} to $outDirectory", e)
        }
      }
    }
  }
}

data class ProtoTarget(
  val outDirectory: String
) : Target() {
  override val includes: List<String> = listOf()
  override val excludes: List<String> = listOf()
  override val exclusive: Boolean = false

  override fun newHandler(
    schema: Schema,
    fs: FileSystem,
    logger: WireLogger,
    newProfileLoader: NewProfileLoader
  ): SchemaHandler {
    return object : SchemaHandler {
      override fun handle(type: Type) = Unit
      override fun handle(service: Service) = Unit
      override fun handle(
        protoFile: ProtoFile,
        emittingRules: EmittingRules,
        claimedDefinitions: ClaimedDefinitions,
        isExclusive: Boolean
      ) {
        if (protoFile.isEmpty()) return

        val relativePath: String = protoFile.location.path
            .substringBeforeLast("/", missingDelimiterValue = ".")
        val outputDirectory = fs.getPath(outDirectory, relativePath)

        require(Files.notExists(outputDirectory) || Files.isDirectory(outputDirectory)) {
          "path $outputDirectory exists but is not a directory."
        }
        Files.createDirectories(outputDirectory)

        val outputFilePath = outputDirectory.resolve("${protoFile.name()}.proto")

        logger.artifact(outputDirectory, protoFile.location.path)

        outputFilePath.sink().buffer().use { sink ->
          try {
            sink.writeUtf8(protoFile.toSchema())
          } catch (e: IOException) {
            throw IOException("Error emitting $outputFilePath to $outDirectory", e)
          }
        }
      }
    }
  }

  private fun ProtoFile.isEmpty() = types.isEmpty() && services.isEmpty() && extendList.isEmpty()
}

/** Omit code generation for these sources. Use this for a dry-run. */
data class NullTarget(
  override val includes: List<String> = listOf("*"),
  override val excludes: List<String> = listOf()
) : Target() {
  override val exclusive: Boolean = true

  override fun newHandler(
    schema: Schema,
    fs: FileSystem,
    logger: WireLogger,
    newProfileLoader: NewProfileLoader
  ): SchemaHandler {
    return object : SchemaHandler {
      override fun handle(type: Type) {
        logger.artifactSkipped(type.type)
      }

      override fun handle(service: Service) {
        logger.artifactSkipped(service.type())
      }
    }
  }
}

/**
 * Generate something custom defined by an external class.
 *
 * This API is currently unstable. We will be changing this API in the future.
 */
data class CustomTargetBeta(
  override val includes: List<String> = listOf("*"),
  override val excludes: List<String> = listOf(),
  override val exclusive: Boolean = true,
  val outDirectory: String,
  /**
   * A fully qualified class name for a class that implements [CustomHandlerBeta]. The class must
   * have a no-arguments public constructor.
   */
  val customHandlerClass: String
) : Target() {
  override fun newHandler(
    schema: Schema,
    fs: FileSystem,
    logger: WireLogger,
    newProfileLoader: NewProfileLoader
  ): SchemaHandler {
    val customHandlerType = try {
      Class.forName(customHandlerClass)
    } catch (exception: ClassNotFoundException) {
      throw IllegalArgumentException("Couldn't find CustomHandlerClass '$customHandlerClass'")
    }

    val constructor = try {
      customHandlerType.getConstructor()
    } catch (exception: NoSuchMethodException) {
      throw IllegalArgumentException("No public constructor on $customHandlerClass")
    }

    val instance = constructor.newInstance() as? CustomHandlerBeta
        ?: throw IllegalArgumentException(
            "$customHandlerClass does not implement CustomHandlerBeta")

    return instance.newHandler(schema, fs, outDirectory, logger, newProfileLoader)
  }
}

/**
 * Implementations of this interface must have a no-arguments public constructor.
 *
 * This API is currently unstable. We will be changing this API in the future.
 */
interface CustomHandlerBeta {
  fun newHandler(
    schema: Schema,
    fs: FileSystem,
    outDirectory: String,
    logger: WireLogger,
    newProfileLoader: NewProfileLoader
  ): Target.SchemaHandler
}

// TODO: merge this interface with Loader.
interface NewProfileLoader {
  fun loadProfile(name: String, schema: Schema): Profile
}

/*
 * Copyright 2018 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.ConsoleWireLogger
import com.squareup.wire.WireLogger
import java.nio.file.FileSystem
import java.nio.file.FileSystems

/**
 * An invocation of the Wire compiler. Each invocation performs the following operations:
 *
 *  1. Read source `.proto` files directly from the file system or from archive files (ie. `.jar`
 *     and `.zip` files). This will also load imported `.proto` files from either the [sourcePath]
 *     or [protoPath]. The collection of loaded type declarations is called a schema.
 *
 *  2. Validate the schema and resolve references between types.
 *
 *  3. Optionally prune the schema. This builds a new schema that is a subset of the original. The
 *     new schema contains only types that are both transitively reachable from [treeShakingRoots]
 *     and not in [treeShakingRubbish].
 *
 *  4. Call each target. It will generate sources for protos in the [sourcePath] that are in its
 *     [Target.includes], that are not in its [Target.excludes], and that haven't already been
 *     emitted by an earlier target.
 *
 *
 * Source Directories and Archives
 * -------------------------------
 *
 * The [sourcePath] and [protoPath] lists contain locations that are of the following forms:
 *
 *  * Locations of `.proto` files.
 *
 *  * Locations of directories that contain a tree of `.proto` files. Typically this is a directory
 *    ending in `src/main/proto`.
 *
 *  * Locations of `.zip` and `.jar` archives that contain a tree of `.proto` files. Typically this
 *    is a `.jar` file from a Maven repository.
 *
 * When one `.proto` message imports another, the import is resolved from the base of each location
 * and archive. If the build is in the unfortunate situation where an import could be resolved by
 * multiple files, whichever was listed first takes precedence.
 *
 * Although the content and structure of [sourcePath] and [protoPath] are the same, only types
 * defined in [sourcePath] are used to generate sources.
 *
 *
 * Matching Packages, Types, and Members
 * -------------------------------------
 *
 * The [treeShakingRoots], [treeShakingRubbish], [Target.includes] and [Target.excludes] lists
 * contain strings that select proto types and members. Strings in these lists are in one of these
 * forms:
 *
 *  * Package names followed by `.*`, like `squareup.dinosaurs.*`. This matches types defined in the
 *    package and its descendant packages. A lone asterisk `*` matches all packages.
 *
 *  * Fully-qualified type names like `squareup.dinosaurs.Dinosaur`. Types may be messages, enums,
 *    or services.
 *
 *  * Fully-qualified member names like `squareup.dinosaurs.Dinosaur#name`. These are type names
 *    followed by `#` followed by a member name. Members may be message fields, enum constants, or
 *    service RPCs.
 *
 * It is an error to specify mutually-redundant values in any of these lists. For example, the list
 * `[squareup.dinosaurs, squareup.dinosaurs.Dinosaur]` is invalid because the second element is
 * already matched by the first.
 *
 * Every element in each lists must apply to at least one declaration. Otherwise that option is
 * unnecessary and a possible typo.
 *
 *
 * Composability
 * -------------
 *
 * There are many moving parts in this system! For most applications it is safe to use [sourcePath]
 * and [targets] only. The other options are for the benefit of large and modular applications.
 *
 * ### Use [protoPath] when one proto module depends on another proto module.
 *
 * These `.proto` files are used for checking dependencies only. It is assumed that the sources for
 * these protos are generated elsewhere.
 *
 * ### Use tree shaking to remove unwanted types.
 *
 * [Tree shaking](https://en.wikipedia.org/wiki/Tree_shaking) can be used to create a
 * small-as-possible generated footprint even if the source declarations are large. This works like
 * [ProGuard](https://en.wikipedia.org/wiki/ProGuard_(software)) and other code shrinking compilers:
 * it allows you to benefit from a shared codebase without creating a large artifact.
 *
 * ### Use multiple targets to split generated code across multiple programming languages.
 *
 * If your project is already using generated Java, it’s difficult to switch to generated Kotlin.
 * Instead of switching everything over at once you can use multiple targets to switch over
 * incrementally. Targets consume their types; subsequent targets get whatever types are left over.
 */
data class WireRun(
  /**
   * Source `.proto` files for this task to generate from.
   */
  val sourcePath: List<Location>,

  /**
   * Sources `.proto` files for this task to use when resolving references.
   */
  val protoPath: List<Location> = listOf(),

  /**
   * The roots of the schema model. Wire will prune the schema model to only include types in this
   * list and the types transitively required by them.
   *
   * If a member is included in this list then the enclosing type is included but its other members
   * are not. For example, if `squareup.dinosaurs.Dinosaur#name` is in this list then the emitted
   * source of the `Dinosaur` message will have the `name` field, but not the `length_meters` or
   * `mass_kilograms` fields.
   */
  val treeShakingRoots: List<String> = listOf("*"),

  /**
   * Types and members that will be stripped from the schema model. Wire will remove the elements
   * themselves and also all references to them.
   */
  val treeShakingRubbish: List<String> = listOf(),

  /**
   * The exclusive lower bound of the version range. Fields with `until` values greater than this
   * are retained.
   */
  val oldest: String? = null,

  /**
   * The inclusive upper bound of the version range. Fields with `since` values less than or equal
   * to this are retained.
   */
  val newest: String? = null,

  /**
   * Action to take with the loaded, resolved, and possibly-pruned schema.
   */
  val targets: List<Target>,

  /** True to build proto3 artifacts. This is unsupported and does not work. */
  val proto3Preview: Boolean = false
) {

  fun execute(fs: FileSystem = FileSystems.getDefault(), logger: WireLogger = ConsoleWireLogger()) {
    return NewSchemaLoader(fs).use { newSchemaLoader ->
      execute(fs, logger, newSchemaLoader)
    }
  }

  private fun execute(fs: FileSystem, logger: WireLogger, schemaLoader: NewSchemaLoader) {
    schemaLoader.initRoots(sourcePath, protoPath)

    // Validate the schema and resolve references
    val fullSchema = schemaLoader.loadSchema()
    val sourceLocationPaths = schemaLoader.sourcePathFiles.map { it.location.path }

    // Optionally prune the schema.
    val schema = treeShake(fullSchema, logger)

    val targetToEmittingRules = targets.associateWith {
      EmittingRules.Builder()
          .include(it.includes)
          .exclude(it.excludes)
          .build()
    }
    val targetsExclusiveLast = targets.sortedBy { it.exclusive }

    // Call each target.
    val skippedForSyntax = mutableListOf<ProtoFile>()
    for (protoFile in schema.protoFiles) {
      if (protoFile.syntax == ProtoFile.Syntax.PROTO_3 && !proto3Preview) {
        skippedForSyntax += protoFile
        continue
      }
      if (!sourceLocationPaths.contains(protoFile.location.path)) {
        continue
      }

      val claimedDefinitions = ClaimedDefinitions()
      claimedDefinitions.claim(ProtoType.ANY)

      for (target in targetsExclusiveLast) {
        val schemaHandler = target.newHandler(schema, fs, logger, schemaLoader)
        schemaHandler.handle(
            protoFile,
            targetToEmittingRules[target]!!,
            claimedDefinitions,
            isExclusive = target.exclusive
        )
      }
    }

    for (emittingRules in targetToEmittingRules.values) {
      if (emittingRules.unusedIncludes().isNotEmpty()) {
        logger.info("""Unused includes in targets:
            |  ${emittingRules.unusedIncludes().joinToString(separator = "\n  ")}
            """.trimMargin())
      }

      if (emittingRules.unusedExcludes().isNotEmpty()) {
        logger.info("""Unused excludes in targets:
            |  ${emittingRules.unusedExcludes().joinToString(separator = "\n  ")}
            """.trimMargin())
      }
    }

    if (skippedForSyntax.isNotEmpty()) {
      logger.info("""Skipped .proto files with unsupported syntax. Add this line to fix:
          |  syntax = "proto2";
          |  ${skippedForSyntax.joinToString(separator = "\n  ") { it.location.toString() }}
          """.trimMargin())
    }
  }

  /** Returns a subset of schema with unreachable and unwanted elements removed. */
  private fun treeShake(schema: Schema, logger: WireLogger): Schema {
    if (treeShakingRoots == listOf("*") &&
        treeShakingRubbish.isEmpty() &&
        oldest == null &&
        newest == null) {
      return schema
    }

    val pruningRules = PruningRules.Builder()
        .addRoot(treeShakingRoots)
        .prune(treeShakingRubbish)
        .oldest(oldest)
        .newest(newest)
        .build()

    val result = schema.prune(pruningRules)

    for (rule in pruningRules.unusedRoots()) {
      logger.info("Unused element in treeShakingRoots: $rule")
    }

    for (rule in pruningRules.unusedPrunes()) {
      logger.info("Unused element in treeShakingRubbish: $rule")
    }

    return result
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.gradle

import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional

open class WireExtension(project: Project) {
  private val objectFactory = project.objects

  internal val sourcePaths = mutableSetOf<String>()
  internal val protoPaths = mutableSetOf<String>()
  internal val sourceTrees = mutableSetOf<SourceDirectorySet>()
  internal val protoTrees = mutableSetOf<SourceDirectorySet>()
  internal val sourceJars = mutableSetOf<ProtoRootSet>()
  internal val protoJars = mutableSetOf<ProtoRootSet>()
  internal val roots = mutableSetOf<String>()
  internal val prunes = mutableSetOf<String>()
  internal var oldest: String? = null
  internal var newest: String? = null

  @Input
  @Optional
  fun roots() = roots.toSet()

  /**
   * See [com.squareup.wire.schema.WireRun.treeShakingRoots]
   */
  fun root(vararg roots: String) {
    this.roots.addAll(roots)
  }

  @Input
  @Optional
  fun prunes() = prunes.toSet()

  /**
   * See [com.squareup.wire.schema.WireRun.treeShakingRubbish]
   */
  fun prune(vararg prunes: String) {
    this.prunes.addAll(prunes)
  }

  @Input
  @Optional
  fun oldest() = oldest

  /**
   * See [com.squareup.wire.schema.WireRun.oldest]
   */
  fun oldest(oldest: String) {
    this.oldest = oldest
  }

  @Input
  @Optional
  fun newest() = newest

  /**
   * See [com.squareup.wire.schema.WireRun.newest]
   */
  fun newest(newest: String) {
    this.newest = newest
  }

  /**
   * Sets oldest and newest to the same version.
   */
  fun version(version: String) {
    this.oldest = version
    this.newest = version
  }

  /**
   * A user-provided file listing [roots] and [prunes]
   */
  @get:Input
  @get:Optional
  var rules: String? = null

  /** Specified what types to output where. Maps to [com.squareup.wire.schema.Target] */
  @get:Input
  val outputs = mutableListOf<WireOutput>()

  /** Not supported, do not use. */
  @get:Input
  @get:Optional
  var proto3Preview: String? = null

  @InputFiles
  @Optional
  fun getSourcePaths() = sourcePaths.toSet()

  @InputFiles
  @Optional
  fun getSourceTrees() = sourceTrees.toSet()

  @InputFiles
  @Optional
  fun getSourceJars() = sourceJars.toSet()

  /**
   * Source paths for local jars and directories, as well as remote binary dependencies
   */
  fun sourcePath(vararg sourcePaths: String) {
    this.sourcePaths.addAll(sourcePaths)
  }

  /**
   * Source paths for local file trees, backed by a [org.gradle.api.file.SourceDirectorySet]
   * Must provide at least a [org.gradle.api.file.SourceDirectorySet.srcDir]
   */
  fun sourcePath(action: Action<ProtoRootSet>) {
    populateRootSets(action, sourceTrees, sourceJars, "source-tree")
  }

  @InputFiles
  @Optional
  fun getProtoPaths(): Set<String> {
    return protoPaths
  }

  @InputFiles
  @Optional
  fun getProtoTrees(): Set<SourceDirectorySet> {
    return protoTrees
  }

  @InputFiles
  @Optional
  fun getProtoJars(): Set<ProtoRootSet> {
    return protoJars
  }

  /**
   * Proto paths for local jars and directories, as well as remote binary dependencies
   */
  fun protoPath(vararg protoPaths: String) {
    this.protoPaths.addAll(protoPaths)
  }

  /**
   * Proto paths for local file trees, backed by a [org.gradle.api.file.SourceDirectorySet]
   * Must provide at least a [org.gradle.api.file.SourceDirectorySet.srcDir]
   */
  fun protoPath(action: Action<ProtoRootSet>) {
    populateRootSets(action, protoTrees, protoJars, "proto-tree")
  }

  private fun populateRootSets(
    action: Action<ProtoRootSet>,
    sourceTrees: MutableSet<SourceDirectorySet>,
    sourceJars: MutableSet<ProtoRootSet>,
    name: String
  ) {
    val protoRootSet = objectFactory.newInstance(ProtoRootSet::class.java)
    action.execute(protoRootSet)

    val hasSrcDirs = protoRootSet.srcDirs.isNotEmpty()
    val hasSrcJar = protoRootSet.srcJar != null

    check(!hasSrcDirs || !hasSrcJar) {
      "Cannot set both srcDirs and srcJars in the same protoPath closure"
    }

    if (hasSrcDirs) {
      // map to SourceDirectorySet which does the work for us!
      val protoTree = objectFactory.sourceDirectorySet(name, "Wire proto sources for $name.")
      protoTree.srcDirs(protoRootSet.srcDirs)
      protoTree.filter.include("**/*.proto")
      protoTree.filter.include(protoRootSet.includes)
      sourceTrees.add(protoTree)
    }

    if (hasSrcJar) {
      sourceJars.add(protoRootSet)
    }
  }

  fun java(action: Action<JavaOutput>) {
    val javaOutput = objectFactory.newInstance(JavaOutput::class.java)
    action.execute(javaOutput)
    outputs += javaOutput
  }

  fun kotlin(action: Action<KotlinOutput>) {
    val kotlinOutput = objectFactory.newInstance(KotlinOutput::class.java)
    action.execute(kotlinOutput)
    outputs += kotlinOutput
  }

  fun proto(action: Action<ProtoOutput>) {
    val protoOutput = objectFactory.newInstance(ProtoOutput::class.java)
    action.execute(protoOutput)
    outputs += protoOutput
  }

  fun custom(action: Action<CustomOutput>) {
    val customOutput = objectFactory.newInstance(CustomOutput::class.java)
    action.execute(customOutput)
    outputs += customOutput
  }

  open class ProtoRootSet {
    val srcDirs = mutableListOf<String>()
    var srcJar: String? = null
    val includes = mutableListOf<String>()

    fun srcDir(dir: String) {
      srcDirs += dir
    }

    fun srcDirs(vararg dirs: String) {
      srcDirs += dirs
    }

    fun srcJar(jar: String) {
      srcJar = jar
    }

    fun include(vararg includePaths: String) {
      includes += includePaths
    }
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.gradle

import com.squareup.wire.gradle.WireExtension.ProtoRootSet
import com.squareup.wire.schema.Location
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.FileCollectionDependency
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.file.FileOrUriNotationConverter
import org.gradle.api.logging.Logger
import java.io.File
import java.net.URI

/**
 * Builds Wire's inputs (expressed as [Location] lists) from Gradle's objects (expressed as
 * directory trees, jars, and coordinates). This includes registering dependencies with the project
 * so they can be resolved for us.
 */
internal class WireInput(
  var project: Project,
  var configuration: Configuration
) {
  val name: String
    get() = configuration.name

  private val dependencyToIncludes = mutableMapOf<Dependency, List<String>>()

  fun addPaths(paths: Set<String>) {
    for (path in paths) {
      val dependency = resolveDependency(path)
      configuration.dependencies.add(dependency)
    }
  }

  fun addJars(jars: Set<ProtoRootSet>) {
    for (jar in jars) {
      jar.srcJar?.let { path ->
        val dependency = resolveDependency(path)
        dependencyToIncludes[dependency] = jar.includes
        configuration.dependencies.add(dependency)
      }
    }
  }

  fun addTrees(trees: Set<SourceDirectorySet>) {
    for (tree in trees) {
      // TODO: this eagerly resolves dependencies; fix this!
      tree.srcDirs.forEach {
        check(it.exists()) {
          "Invalid path string: \"${it.relativeTo(project.projectDir)}\". Path does not exist."
        }
      }
      val dependency = project.dependencies.create(tree)
      configuration.dependencies.add(dependency)
    }
  }

  private fun resolveDependency(path: String): Dependency {
    val parser = FileOrUriNotationConverter.parser()

    val converted = parser.parseNotation(path)

    if (converted is File) {
      val file = if (!converted.isAbsolute) File(project.projectDir, converted.path) else converted

      check(file.exists()) { "Invalid path string: \"$path\". Path does not exist." }

      return when {
        file.isDirectory -> project.dependencies.create(project.files(path))
        file.isJar -> project.dependencies.create(project.files(file.path))
        else -> throw IllegalArgumentException(
            """
            |Invalid path string: "$path".
            |For individual files, use the following syntax:
            |wire {
            |  sourcePath {
            |    srcDir 'dirPath'
            |    include 'relativePath'
            |  }
            |}
            """.trimMargin()
        )
      }
    } else if (converted is URI && isURL(converted)) {
      throw IllegalArgumentException(
          "Invalid path string: \"$path\". URL dependencies are not allowed."
      )
    } else {
      // Assume it's a possible external dependency and let Gradle sort it out later.
      return project.dependencies.create(path)
    }
  }

  private fun isURL(uri: URI) =
      try {
        uri.toURL()
        true
      } catch (e: Exception) {
        false
      }

  fun debug(logger: Logger) {
    configuration.dependencies.forEach { dep ->
      val srcDirs = ((dep as? FileCollectionDependency)?.files as? SourceDirectorySet)?.srcDirs
      val includes = dependencyToIncludes[dep] ?: listOf()
      logger.debug("dep: $dep -> $srcDirs")
      logger.debug("$name.files for dep: ${configuration.files(dep)}")
      logger.debug("$name.includes for dep: $includes")
    }
  }

  fun toLocations(): List<Location> {
    return configuration.dependencies
        .flatMap { dep ->
          configuration.files(dep)
              .flatMap { file ->
                file.toLocations(dep)
              }
        }
  }

  private fun File.toLocations(dependency: Dependency): List<Location> {
    if (dependency is FileCollectionDependency && dependency.files is SourceDirectorySet) {
      val srcDir = (dependency.files as SourceDirectorySet).srcDirs.first {
        path.startsWith(it.path + "/")
      }
      return listOf(Location.get(
          base = srcDir.path,
          path = path.substring(srcDir.path.length + 1)
      ))
    }

    val includes = dependencyToIncludes[dependency] ?: listOf()

    if (includes.isEmpty()) {
      return listOf(Location.get(path))
    }

    return includes.map { include ->
      Location.get(base = path, path = include)
    }
  }

  private val File.isJar
    get() = path.endsWith(".jar")
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.gradle

import com.squareup.wire.kotlin.RpcCallStyle
import com.squareup.wire.kotlin.RpcRole
import com.squareup.wire.schema.CustomTargetBeta
import com.squareup.wire.schema.JavaTarget
import com.squareup.wire.schema.KotlinTarget
import com.squareup.wire.schema.ProtoTarget
import com.squareup.wire.schema.Target
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import javax.inject.Inject

/**
 * Specifies Wire's outputs (expressed as a list of [Target] objects) using Gradle's DSL (expressed
 * as destination directories and configuration options). This includes registering output
 * directories with the project so they can be compiled after they are generated.
 */
abstract class WireOutput {
  /** This will be set to a non-null value before [toTarget] is invoked. */
  var out: String? = null

  /** Create a target for the WireCompiler to use when emitting sources. */
  abstract fun toTarget(): Target

  /** Configure the project's graph to compile the files emitted. */
  abstract fun applyToProject(
    project: Project,
    wireTask: TaskProvider<WireTask>?,
    kotlin: Boolean
  )
}

open class JavaOutput @Inject constructor() : WireOutput() {
  var includes: List<String>? = null
  var excludes: List<String>? = null
  var exclusive: Boolean = true
  var android: Boolean = false
  var androidAnnotations: Boolean = false
  var compact: Boolean = false

  override fun toTarget(): JavaTarget {
    return JavaTarget(
        includes = includes ?: listOf("*"),
        excludes = excludes ?: listOf(),
        exclusive = exclusive,
        outDirectory = out!!,
        android = android,
        androidAnnotations = androidAnnotations,
        compact = compact
    )
  }

  override fun applyToProject(
    project: Project,
    wireTask: TaskProvider<WireTask>?,
    kotlin: Boolean
  ) {
    @Suppress("UNCHECKED_CAST")
    val compileJavaTask = project.tasks.named("compileJava") as TaskProvider<JavaCompile>
    compileJavaTask.configure {
      it.source(out)
      it.dependsOn(wireTask)
    }
    if (kotlin) {
      val sourceSetContainer = project.property("sourceSets") as SourceSetContainer
      val mainSourceSet = sourceSetContainer.getByName("main") as SourceSet
      mainSourceSet.java.srcDirs(out)

      @Suppress("UNCHECKED_CAST")
      val compileKotlinTask = project.tasks.named("compileKotlin") as TaskProvider<KotlinCompile>
      compileKotlinTask.configure {
        it.dependsOn(wireTask)
      }
    }
  }
}

open class KotlinOutput @Inject constructor() : WireOutput() {
  var includes: List<String>? = null
  var excludes: List<String>? = null
  var exclusive: Boolean = true
  var android: Boolean = false
  var javaInterop: Boolean = false
  var rpcCallStyle: String = "suspending"
  var rpcRole: String = "client"
  var singleMethodServices: Boolean = false

  override fun toTarget(): KotlinTarget {
    val rpcCallStyle = RpcCallStyle.values()
        .singleOrNull { it.toString().equals(rpcCallStyle, ignoreCase = true) }
        ?: throw IllegalArgumentException(
            "Unknown rpcCallStyle $rpcCallStyle. Valid values: ${RpcCallStyle.values().contentToString()}")
    val rpcRole = RpcRole.values()
        .singleOrNull { it.toString().equals(rpcRole, ignoreCase = true) }
        ?: throw IllegalArgumentException(
            "Unknown rpcRole $rpcRole. Valid values: ${RpcRole.values().contentToString()}")

    return KotlinTarget(
        includes = includes ?: listOf("*"),
        excludes = excludes ?: listOf(),
        exclusive = exclusive,
        outDirectory = out!!,
        android = android,
        javaInterop = javaInterop,
        rpcCallStyle = rpcCallStyle,
        rpcRole = rpcRole,
        singleMethodServices = singleMethodServices
    )
  }

  override fun applyToProject(
    project: Project,
    wireTask: TaskProvider<WireTask>?,
    kotlin: Boolean
  ) {
    val compileKotlinTasks = project.tasks.withType(KotlinCompile::class.java)
    check(compileKotlinTasks.isNotEmpty()) {
      "To generate Kotlin protos, please apply a Kotlin plugin."
    }
    compileKotlinTasks.configureEach {
      it.source(out)
      it.dependsOn(wireTask)
    }
  }
}

open class ProtoOutput @Inject constructor() : WireOutput() {
  override fun toTarget(): ProtoTarget {
    return ProtoTarget(
        outDirectory = out!!
    )
  }

  override fun applyToProject(
    project: Project,
    wireTask: TaskProvider<WireTask>?,
    kotlin: Boolean
  ) {
  }
}

open class CustomOutput @Inject constructor() : WireOutput() {
  var includes: List<String>? = null
  var excludes: List<String>? = null
  var exclusive: Boolean = true
  var customHandlerClass: String? = null

  override fun toTarget(): CustomTargetBeta {
    return CustomTargetBeta(
        includes = includes ?: listOf("*"),
        excludes = excludes ?: listOf(),
        exclusive = exclusive,
        outDirectory = out!!,
        customHandlerClass = customHandlerClass
            ?: throw IllegalArgumentException("customHandlerClass required")
    )
  }

  override fun applyToProject(
    project: Project,
    wireTask: TaskProvider<WireTask>?,
    kotlin: Boolean
  ) {
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.gradle

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import java.io.File

class WirePlugin : Plugin<Project> {
  private var kotlin = false
  private var java = false

  private lateinit var sourceSetContainer: SourceSetContainer

  override fun apply(project: Project) {
    val logger = project.logger

    val extension = project.extensions.create(
        "wire", WireExtension::class.java, project
    )

    project.configurations.create("protoSource")
    project.configurations.create("protoPath")

    project.tasks.register("generateProtos", WireTask::class.java) { task ->
      task.group = "wire"
      task.description = "Generate Wire protocol buffer implementation for .proto files"
    }

    project.plugins.all {
      logger.debug("plugin: $it")
      when (it) {
        is JavaBasePlugin -> {
          java = true
        }
        is KotlinBasePluginWrapper -> {
          kotlin = true
        }
      }
    }

    project.afterEvaluate {
      if (logger.isDebugEnabled) {
        sourceSetContainer = project.property("sourceSets") as SourceSetContainer
        sourceSetContainer.forEach {
          logger.debug("source set: ${it.name}")
        }
      }

      when {
        kotlin || java -> applyJvm(it, extension)
        else ->
          throw IllegalArgumentException(
              "Either the Java or Kotlin plugin must be applied before the Wire Gradle plugin."
          )
      }
    }
  }

  private fun applyJvm(
    project: Project,
    extension: WireExtension
  ) {
    val sourceInput = WireInput(project, project.configurations.getByName("protoSource"))
    if (extension.sourcePaths.isNotEmpty() ||
        extension.sourceTrees.isNotEmpty() ||
        extension.sourceJars.isNotEmpty()) {
      sourceInput.addTrees(extension.sourceTrees)
      sourceInput.addJars(extension.sourceJars)
      sourceInput.addPaths(extension.sourcePaths)
    } else {
      sourceInput.addPaths(setOf("src/main/proto"))
    }

    val protoInput = WireInput(project, project.configurations.getByName("protoPath"))
    if (extension.protoPaths.isNotEmpty() ||
        extension.protoTrees.isNotEmpty() ||
        extension.protoJars.isNotEmpty()) {
      protoInput.addTrees(extension.protoTrees)
      protoInput.addJars(extension.protoJars)
      protoInput.addPaths(extension.protoPaths)
    }

    // At this point, all source and proto file references should be set up for Gradle to resolve.

    val outputs = extension.outputs.toMutableList()
    if (outputs.isEmpty()) {
      outputs.add(JavaOutput())
    }

    for (output in outputs) {
      if (output.out == null) {
        output.out = "${project.buildDir}/generated/source/wire"
      } else {
        output.out = project.file(output.out!!).path
      }
    }

    val targets = outputs.map { it.toTarget() }

    val wireTask = project.tasks.named("generateProtos") as TaskProvider<WireTask>
    wireTask.configure {
      it.outputDirectories = outputs.map { output -> File(output.out!!) }
      it.source(sourceInput.configuration)
      it.sourceInput = sourceInput
      it.protoInput = protoInput
      it.roots = extension.roots.toList()
      it.prunes = extension.prunes.toList()
      it.oldest = extension.oldest
      it.newest = extension.newest
      it.rules = extension.rules
      it.targets = targets
      it.proto3Preview = extension.proto3Preview
    }

    for (output in outputs) {
      output.applyToProject(project, wireTask, kotlin)
    }
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.gradle

import com.squareup.wire.VERSION
import com.squareup.wire.schema.Target
import com.squareup.wire.schema.WireRun
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectories
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskAction
import java.io.File

open class WireTask : SourceTask() {
  @get:OutputDirectories
  var outputDirectories: List<File>? = null

  @get:Input
  var pluginVersion: String = VERSION

  @get:Internal
  internal lateinit var sourceInput: WireInput

  @get:Internal
  internal lateinit var protoInput: WireInput

  @Input
  lateinit var roots: List<String>

  @Input
  lateinit var prunes: List<String>

  @Input
  @Optional
  var oldest: String? = null

  @Input
  @Optional
  var newest: String? = null

  @Input
  @Optional
  var rules: String? = null

  @Input
  lateinit var targets: List<Target>

  @Input
  @Optional
  var proto3Preview: String? = null

  @TaskAction
  fun generateWireFiles() {
    val includes = mutableListOf<String>()
    val excludes = mutableListOf<String>()

    rules?.let {
      project.file(it)
          .forEachLine { line ->
            when (line.firstOrNull()) {
              '+' -> includes.add(line.substring(1))
              '-' -> excludes.add(line.substring(1))
              else -> Unit
            }
          }
    }

    if (includes.isNotEmpty()) {
      logger.info("INCLUDE:\n * ${includes.joinToString(separator = "\n * ")}")
    }
    if (excludes.isNotEmpty()) {
      logger.info("EXCLUDE:\n * ${excludes.joinToString(separator = "\n * ")}")
    }
    if (includes.isEmpty() && excludes.isEmpty()) logger.info("NO INCLUDES OR EXCLUDES")

    if (logger.isDebugEnabled) {
      sourceInput.debug(logger)
      protoInput.debug(logger)
      logger.debug("roots: $roots")
      logger.debug("prunes: $prunes")
      logger.debug("rules: $rules")
      logger.debug("targets: $targets")
    }

    val wireRun = WireRun(
        sourcePath = sourceInput.toLocations(),
        protoPath = protoInput.toLocations(),
        treeShakingRoots = if (roots.isEmpty()) includes else roots,
        treeShakingRubbish = if (prunes.isEmpty()) excludes else prunes,
        oldest = oldest,
        newest = newest,
        targets = targets,
        proto3Preview = (proto3Preview == "UNSUPPORTED")
    )
    wireRun.execute()
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okio.BufferedSource
import okio.Source

internal sealed class GrpcDecoder(val name: String) {
  /** Returns a stream that decodes `source`. */
  abstract fun decode(source: BufferedSource): Source

  internal object IdentityGrpcDecoder : GrpcDecoder("identity") {
    override fun decode(source: BufferedSource) = source
  }

  internal object GzipGrpcDecoder : GrpcDecoder("gzip") {
    override fun decode(source: BufferedSource) = source.asGzip()
  }
}

internal fun String.toGrpcDecoding(): GrpcDecoder {
  return when (this) {
    "identity" -> GrpcDecoder.IdentityGrpcDecoder
    "gzip" -> GrpcDecoder.GzipGrpcDecoder
    "deflate" -> throw ProtocolException("deflate not yet supported")
    "snappy" -> throw ProtocolException("snappy not yet supported")
    else -> throw ProtocolException("unsupported grpc-encoding: $this")
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okio.BufferedSink
import okio.buffer

internal sealed class GrpcEncoder(val name: String) {
  /** Returns a stream that decodes `source`. */
  abstract fun encode(sink: BufferedSink): BufferedSink

  internal object IdentityGrpcEncoder : GrpcEncoder("identity") {
    override fun encode(sink: BufferedSink) = sink
  }

  internal object GzipGrpcEncoder : GrpcEncoder("gzip") {
    override fun encode(sink: BufferedSink) = sink.asGzip().buffer()
  }
}

internal fun String.toGrpcEncoder(): GrpcEncoder {
  return when (this) {
    "identity" -> GrpcEncoder.IdentityGrpcEncoder
    "gzip" -> GrpcEncoder.GzipGrpcEncoder
    "deflate" -> throw ProtocolException("deflate not yet supported")
    "snappy" -> throw ProtocolException("snappy not yet supported")
    else -> throw ProtocolException("unsupported grpc-encoding: $this")
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import com.squareup.wire.MessageSink
import com.squareup.wire.ProtoAdapter
import okio.Buffer
import okio.BufferedSink

/**
 * Writes a sequence of gRPC messages as an HTTP/2 stream.
 *
 * @param sink the HTTP/2 stream body.
 * @param messageAdapter a proto adapter for each message.
 * @param callForCancel the HTTP call that can be canceled to signal abnormal termination.
 * @param grpcEncoding the content coding for the stream body.
 */
internal class GrpcMessageSink<T : Any> constructor(
  private val sink: BufferedSink,
  private val messageAdapter: ProtoAdapter<T>,
  private val callForCancel: Call?,
  private val grpcEncoding: String
) : MessageSink<T> {
  private var closed = false
  override fun write(message: T) {
    check(!closed) { "closed" }

    val encodedMessage = Buffer()
    grpcEncoding.toGrpcEncoder().encode(encodedMessage).use(BufferedSink::close) { encodingSink ->
      messageAdapter.encode(encodingSink, message)
    }

    val compressedFlag = if (grpcEncoding == "identity") 0 else 1
    sink.writeByte(compressedFlag)
    // TODO: fail if the message size is more than MAX_INT
    sink.writeInt(encodedMessage.size.toInt())
    sink.writeAll(encodedMessage)
    sink.flush()
  }

  override fun cancel() {
    check(!closed) { "closed" }
    callForCancel?.cancel()
  }

  override fun close() {
    if (closed) return
    closed = true
    sink.close()
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import com.squareup.wire.MessageSource
import com.squareup.wire.ProtoAdapter
import okio.Buffer
import okio.BufferedSource
import okio.buffer

/**
 * Reads an HTTP/2 stream as a sequence of gRPC messages.
 *
 * @param source the HTTP/2 stream body.
 * @param messageAdapter a proto adapter for each message.
 * @param grpcEncoding the "grpc-encoding" header, or null if it is absent.
 */
internal class GrpcMessageSource<T : Any>(
  private val source: BufferedSource,
  private val messageAdapter: ProtoAdapter<T>,
  private val grpcEncoding: String? = null
) : MessageSource<T> {
  override fun read(): T? {
    if (source.exhausted()) return null

    // Length-Prefixed-Message → Compressed-Flag Message-Length Message
    //         Compressed-Flag → 0 / 1 # encoded as 1 byte unsigned integer
    //          Message-Length → {length of Message} # encoded as 4 byte unsigned integer
    //                 Message → *{binary octet}

    val compressedFlag = source.readByte()
    val messageDecoding: GrpcDecoder = when {
      compressedFlag.toInt() == 0 -> GrpcDecoder.IdentityGrpcDecoder
      compressedFlag.toInt() == 1 -> {
        grpcEncoding?.toGrpcDecoding() ?: throw ProtocolException(
            "message is encoded but message-encoding header was omitted")
      }
      else -> throw ProtocolException("unexpected compressed-flag: $compressedFlag")
    }

    val encodedLength = source.readInt().toLong() and 0xffffffffL

    val encodedMessage = Buffer().write(source, encodedLength)

    return messageDecoding.decode(encodedMessage).buffer().use(BufferedSource::close) {
      messageAdapter.decode(it)
    }
  }

  fun readExactlyOneAndClose(): T {
    use(GrpcMessageSource<T>::close) { reader ->
      val result = reader.read() ?: throw ProtocolException("expected 1 message but got none")
      val end = reader.read()
      if (end != null) throw ProtocolException("expected 1 message but got multiple")
      return result
    }
  }

  override fun close() = source.close()
}

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okio.Sink
import okio.Source

// TODO make internal https://youtrack.jetbrains.com/issue/KT-37316
expect interface Call {
  fun cancel()
}

internal expect fun Sink.asGzip(): Sink
internal expect fun Source.asGzip(): Source

internal expect fun Throwable.addSuppressed(other: Throwable)

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

/**
 * Executes the given [block] function on this resource and then closes it down correctly using
 * [close] whether an exception is thrown or not.
 *
 * Differs from the JVM `use` in that it can be called on any type provided that you supply a
 * [close] function.
 *
 * @return the result of [block] function invoked on this resource.
 */
internal inline fun <C, R> C.use(close: (C) -> Unit, block: (C) -> R): R {
  var closed = false

  return try {
    block(this)
  } catch (first: Throwable) {
    try {
      closed = true
      close(this)
    } catch (second: Throwable) {
      first.addSuppressed(second)
    }
    throw first
  } finally {
    if (!closed) {
      close(this)
    }
  }
}

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okio.Sink
import okio.Source

actual interface Call {
  actual fun cancel()
}

internal actual fun Sink.asGzip(): Sink {
  throw UnsupportedOperationException("Gzip not implemented for JS")
}

internal actual fun Source.asGzip(): Source {
  throw UnsupportedOperationException("Gzip not implemented for JS")
}

internal actual fun Throwable.addSuppressed(other: Throwable) {
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import com.squareup.wire.MessageSource
import com.squareup.wire.ProtoAdapter
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import java.io.IOException
import java.util.concurrent.LinkedBlockingDeque

/**
 * This message source uses a [LinkedBlockingDeque] to connect a reading source with a writing
 * response callback.
 *
 * It uses two sentinel types:
 *
 *  * Failure: enqueued when there's an exception on the stream.
 *  * Complete: enqueued when the stream completes normally.
 */
internal class BlockingMessageSource<R : Any>(
  val responseAdapter: ProtoAdapter<R>,
  val call: Call
) : MessageSource<R> {
  private val queue = LinkedBlockingDeque<Any>(1)

  override fun read(): R? {
    return when (val result = queue.take()) {
      is Complete -> {
        queue.put(result) // Replace it.
        null
      }
      is Failure -> {
        queue.put(result) // Replace it.
        throw result.e
      }
      else -> result as R
    }
  }

  override fun close() {
    call.cancel() // Short-circuit the request stream if it's still active.
  }

  /** Read messages from the response body and write them to the deque. */
  fun readFromResponseBodyCallback(): Callback {
    return object : Callback {
      override fun onFailure(call: Call, e: IOException) {
        queue.put(Failure(e))
      }

      override fun onResponse(call: Call, response: Response) {
        try {
          response.use {
            response.messageSource(responseAdapter).use { reader ->
              while (true) {
                val message = reader.read() ?: break
                queue.put(message)
              }

              val exception = response.grpcStatusToException()
              if (exception != null) throw exception
            }
          }
          queue.put(Complete)
        } catch (e: IOException) {
          call.cancel() // Break the request stream if the response stream breaks.
          queue.put(Failure(e))
        }
      }
    }
  }

  private object Complete
  private class Failure(val e: IOException)
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okio.ForwardingTimeout
import okio.Timeout
import java.util.concurrent.TimeUnit

internal class LateInitTimeout : ForwardingTimeout(Timeout()) {
  fun init(newDelegate: Timeout) {
    val oldDelegate = this.delegate
    newDelegate.timeout(oldDelegate.timeoutNanos(), TimeUnit.NANOSECONDS)
    if (oldDelegate.hasDeadline()) newDelegate.deadlineNanoTime(oldDelegate.deadlineNanoTime())
    delegate = newDelegate
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okhttp3.MediaType
import okhttp3.RequestBody
import okio.BufferedSink
import okio.Pipe
import okio.buffer

/**
 * A duplex request body that provides early writes via a pipe.
 */
internal class PipeDuplexRequestBody(
  private val contentType: MediaType?,
  pipeMaxBufferSize: Long
) : RequestBody() {
  private val pipe = Pipe(pipeMaxBufferSize)

  fun createSink() = pipe.sink.buffer()

  override fun contentType() = contentType

  override fun writeTo(sink: BufferedSink) {
    pipe.fold(sink)
  }

  override fun isDuplex() = true
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import com.squareup.wire.GrpcCall
import com.squareup.wire.GrpcClient
import com.squareup.wire.GrpcMethod
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import okio.Timeout
import java.io.IOException
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

internal class RealGrpcCall<S : Any, R : Any>(
  private val grpcClient: GrpcClient,
  private val grpcMethod: GrpcMethod<S, R>
) : GrpcCall<S, R> {
  /** Non-null until the call is executed. */
  private var call: Call? = null
  private var canceled = false

  override val timeout: Timeout = LateInitTimeout()

  override fun cancel() {
    canceled = true
    call?.cancel()
  }

  override fun isCanceled(): Boolean = canceled

  override suspend fun execute(request: S): R {
    val call = initCall(request)

    return suspendCancellableCoroutine { continuation ->
      continuation.invokeOnCancellation {
        cancel()
      }

      call.enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
          continuation.resumeWithException(e)
        }

        override fun onResponse(call: Call, response: Response) {
          var message: R? = null
          var exception: IOException? = null
          try {
            message = response.messageSource(grpcMethod.responseAdapter).readExactlyOneAndClose()
            exception = response.grpcStatusToException()
          } catch (e: IOException) {
            exception = e
          }

          if (exception != null) {
            continuation.resumeWithException(exception)
          } else {
            continuation.resume(message!!)
          }
        }
      })
    }
  }

  override fun executeBlocking(request: S): R {
    val call = initCall(request)
    val response = call.execute()
    response.use {
      val message: R = response.messageSource(grpcMethod.responseAdapter).readExactlyOneAndClose()
      val exception: IOException? = response.grpcStatusToException()
      if (exception != null) throw exception
      return message
    }
  }

  override fun enqueue(request: S, callback: GrpcCall.Callback<S, R>) {
    val call = initCall(request)
    call.enqueue(object : Callback {
      override fun onFailure(call: Call, e: IOException) {
        callback.onFailure(this@RealGrpcCall, e)
      }

      override fun onResponse(call: Call, response: Response) {
        response.use {
          var message: R? = null
          var exception: IOException?
          try {
            message = response.messageSource(grpcMethod.responseAdapter).readExactlyOneAndClose()
            exception = response.grpcStatusToException()
          } catch (e: IOException) {
            exception = e
          }
          if (exception != null) {
            callback.onFailure(this@RealGrpcCall, exception)
          } else {
            callback.onSuccess(this@RealGrpcCall, message!!)
          }
        }
      }
    })
  }

  override fun isExecuted(): Boolean = call?.isExecuted() ?: false

  override fun clone(): GrpcCall<S, R> {
    val result = RealGrpcCall(grpcClient, grpcMethod)
    val oldTimeout = this.timeout
    result.timeout.also { newTimeout ->
      newTimeout.timeout(oldTimeout.timeoutNanos(), TimeUnit.NANOSECONDS)
      if (oldTimeout.hasDeadline()) newTimeout.deadlineNanoTime(oldTimeout.deadlineNanoTime())
    }
    return result
  }

  private fun initCall(request: S): Call {
    check(this.call == null) { "already executed" }

    val requestBody = newRequestBody(grpcMethod.requestAdapter, request)
    val result = grpcClient.newCall(grpcMethod.path, requestBody)
    this.call = result
    if (canceled) result.cancel()
    (timeout as LateInitTimeout).init(result.timeout())
    return result
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import com.squareup.wire.GrpcClient
import com.squareup.wire.GrpcMethod
import com.squareup.wire.GrpcStreamingCall
import com.squareup.wire.MessageSink
import com.squareup.wire.MessageSource
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import okio.Timeout
import java.util.concurrent.TimeUnit

internal class RealGrpcStreamingCall<S : Any, R : Any>(
  private val grpcClient: GrpcClient,
  private val grpcMethod: GrpcMethod<S, R>
) : GrpcStreamingCall<S, R> {
  private val requestBody = newDuplexRequestBody()
  private val call = grpcClient.newCall(grpcMethod.path, requestBody)

  override val timeout: Timeout
    get() = call.timeout()

  init {
    timeout.clearTimeout()
    timeout.clearDeadline()
  }

  override fun cancel() {
    call.cancel()
  }

  override fun isCanceled(): Boolean = call.isCanceled()

  override fun execute(): Pair<SendChannel<S>, ReceiveChannel<R>> {
    val requestChannel = Channel<S>(1)
    val responseChannel = Channel<R>(1)
    requestChannel.writeToRequestBody(requestBody, grpcMethod.requestAdapter, call)
    call.enqueue(responseChannel.readFromResponseBodyCallback(grpcMethod.responseAdapter))

    responseChannel.invokeOnClose {
      if (responseChannel.isClosedForReceive) {
        // Short-circuit the request stream if it's still active.
        call.cancel()
        requestChannel.cancel()
      }
    }

    return requestChannel to responseChannel
  }

  override fun executeBlocking(): Pair<MessageSink<S>, MessageSource<R>> {
    val messageSource = BlockingMessageSource(grpcMethod.responseAdapter, call)
    val messageSink = requestBody.messageSink(grpcMethod.requestAdapter, call)
    call.enqueue(messageSource.readFromResponseBodyCallback())

    return messageSink to messageSource
  }

  override fun isExecuted(): Boolean = call.isExecuted()

  override fun clone(): GrpcStreamingCall<S, R> {
    val result = RealGrpcStreamingCall(grpcClient, grpcMethod)
    val oldTimeout = this.timeout
    result.timeout.also { newTimeout ->
      newTimeout.timeout(oldTimeout.timeoutNanos(), TimeUnit.NANOSECONDS)
      if (oldTimeout.hasDeadline()) newTimeout.deadlineNanoTime(oldTimeout.deadlineNanoTime())
      else newTimeout.clearDeadline()
    }
    return result
  }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import com.squareup.wire.ProtoAdapter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody
import okhttp3.Response
import okio.BufferedSink
import java.io.IOException

internal val APPLICATION_GRPC_MEDIA_TYPE: MediaType = "application/grpc".toMediaType()

/** Returns a new request body that writes [onlyMessage]. */
internal fun <S : Any> newRequestBody(
  requestAdapter: ProtoAdapter<S>,
  onlyMessage: S
): RequestBody {
  return object : RequestBody() {
    override fun contentType() = APPLICATION_GRPC_MEDIA_TYPE

    override fun writeTo(sink: BufferedSink) {
      val grpcMessageSink = GrpcMessageSink(
          sink = sink,
          messageAdapter = requestAdapter,
          callForCancel = null,
          grpcEncoding = "gzip"
      )
      grpcMessageSink.use {
        it.write(onlyMessage)
      }
    }
  }
}

/**
 * Returns a new duplex request body that allows us to write request messages even after the
 * response status, headers, and body have been received.
 */
internal fun newDuplexRequestBody(): PipeDuplexRequestBody {
  return PipeDuplexRequestBody(APPLICATION_GRPC_MEDIA_TYPE, pipeMaxBufferSize = 1024 * 1024)
}

/** Writes messages to the request body. */
internal fun <S : Any> PipeDuplexRequestBody.messageSink(
  requestAdapter: ProtoAdapter<S>,
  callForCancel: Call
) = GrpcMessageSink(
    sink = createSink(),
    messageAdapter = requestAdapter,
    callForCancel = callForCancel,
    grpcEncoding = "gzip"
)

/** Sends the response messages to the channel. */
internal fun <R : Any> SendChannel<R>.readFromResponseBodyCallback(
  responseAdapter: ProtoAdapter<R>
): Callback {
  return object : Callback {
    override fun onFailure(call: Call, e: IOException) {
      // Something broke. Kill the response channel.
      close(e)
    }

    override fun onResponse(call: Call, response: Response) {
      runBlocking {
        response.use {
          response.messageSource(responseAdapter).use { reader ->
            while (true) {
              val message = reader.read() ?: break
              send(message)
            }

            close(response.grpcStatusToException())
          }
        }
      }
    }
  }
}

/**
 * Stream messages from the request channel to the request body stream. This means:
 *
 * 1. read a message (non blocking, suspending code)
 * 2. write it to the stream (blocking)
 * 3. repeat. We also have to wait for all 2s to end before closing the writer
 */
internal fun <S : Any> ReceiveChannel<S>.writeToRequestBody(
  requestBody: PipeDuplexRequestBody,
  requestAdapter: ProtoAdapter<S>,
  callForCancel: Call
) {
  CoroutineScope(Dispatchers.IO).launch {
    requestBody.messageSink(requestAdapter, callForCancel).use { requestWriter ->
      var success = false
      try {
        consumeEach { message ->
          requestWriter.write(message)
        }
        success = true
      } finally {
        if (!success) requestWriter.cancel()
      }
    }
  }
}

/** Reads messages from the response body. */
internal fun <R : Any> Response.messageSource(
  protoAdapter: ProtoAdapter<R>
): GrpcMessageSource<R> {
  val grpcEncoding = header("grpc-encoding")
  val responseSource = body!!.source()
  return GrpcMessageSource(responseSource, protoAdapter, grpcEncoding)
}

/** Maps the response trailer to either success (null) or an exception. */
internal fun Response.grpcStatusToException(): IOException? {
  val grpcStatus = trailers().get("grpc-status") ?: header("grpc-status")
  return when (grpcStatus) {
    "0" -> null
    else -> {
      // also see https://github.com/grpc/grpc-go/blob/master/codes/codes.go#L31
      IOException("unexpected or absent grpc-status: $grpcStatus")
    }
  }
}

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okio.Sink
import okio.Source
import okio.gzip
import java.lang.reflect.Method

actual typealias Call = okhttp3.Call

internal actual inline fun Sink.asGzip(): Sink = gzip()
internal actual inline fun Source.asGzip(): Source = gzip()

internal actual fun Throwable.addSuppressed(other: Throwable) {
  AddSuppressedMethod?.invoke(this, other)
}

private val AddSuppressedMethod: Method? by lazy {
  try {
    Throwable::class.java.getMethod("addSuppressed", Throwable::class.java)
  } catch (t: Throwable) {
    null
  }
}

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okio.Sink
import okio.Source

actual interface Call {
  actual fun cancel()
}

internal actual fun Sink.asGzip(): Sink {
  throw UnsupportedOperationException("Gzip not implemented for native")
}

internal actual fun Source.asGzip(): Source {
  throw UnsupportedOperationException("Gzip not implemented for native")
}

internal actual fun Throwable.addSuppressed(other: Throwable) {
}

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.java

import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeName
import com.squareup.wire.java.internal.ProfileFileElement
import com.squareup.wire.schema.ProtoType

/**
 * Describes how to map `.proto` to `.java`. A single repository of `.proto` files
 * may have multiple profiles; for example a project may target both Android and Java.
 */
class Profile(
  private val profileFiles: List<ProfileFileElement> = emptyList()
) {
  fun getTarget(type: ProtoType): TypeName? {
    val typeConfig = typeConfig(type)
    return if (typeConfig != null) ClassName.bestGuess(typeConfig.target) else null
  }

  fun getAdapter(type: ProtoType): AdapterConstant? {
    val typeConfig = typeConfig(type)
    return if (typeConfig != null) AdapterConstant(typeConfig.adapter) else null
  }

  /** Returns the config for [type], or null if it is not configured.  */
  private fun typeConfig(type: ProtoType) =
    profileFiles
        .flatMap { it.typeConfigs }
        .find { it.type == type.toString() }
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */

/**
 * Extension functions/properties for the Field class
 */
package com.squareup.wire.kotlin

import com.squareup.wire.schema.Field
import com.squareup.wire.schema.ProtoType

internal val Field.isMap: Boolean
  get() = type!!.isMap

internal val Field.keyType: ProtoType
  get() = type!!.keyType!!

internal val Field.valueType: ProtoType
  get() = type!!.valueType!!

/*
 * Copyright 2018 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.kotlin

import com.squareup.kotlinpoet.ANY
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.DOUBLE
import com.squareup.kotlinpoet.FLOAT
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.INT
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.KModifier.OVERRIDE
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.LONG
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.MemberName.Companion.member
import com.squareup.kotlinpoet.NOTHING
import com.squareup.kotlinpoet.NameAllocator
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.STRING
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.buildCodeBlock
import com.squareup.kotlinpoet.joinToCode
import com.squareup.kotlinpoet.jvm.jvmField
import com.squareup.kotlinpoet.jvm.jvmStatic
import com.squareup.wire.EnumAdapter
import com.squareup.wire.FieldEncoding
import com.squareup.wire.GrpcCall
import com.squareup.wire.GrpcClient
import com.squareup.wire.GrpcMethod
import com.squareup.wire.GrpcStreamingCall
import com.squareup.wire.Message
import com.squareup.wire.MessageSink
import com.squareup.wire.MessageSource
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.ProtoReader
import com.squareup.wire.ProtoWriter
import com.squareup.wire.WireEnum
import com.squareup.wire.WireField
import com.squareup.wire.WireRpc
import com.squareup.wire.schema.EnclosingType
import com.squareup.wire.schema.EnumType
import com.squareup.wire.schema.Field
import com.squareup.wire.schema.MessageType
import com.squareup.wire.schema.OneOf
import com.squareup.wire.schema.Options.Companion.ENUM_OPTIONS
import com.squareup.wire.schema.Options.Companion.ENUM_VALUE_OPTIONS
import com.squareup.wire.schema.Options.Companion.FIELD_OPTIONS
import com.squareup.wire.schema.Options.Companion.MESSAGE_OPTIONS
import com.squareup.wire.schema.ProtoFile
import com.squareup.wire.schema.ProtoMember
import com.squareup.wire.schema.ProtoType
import com.squareup.wire.schema.Rpc
import com.squareup.wire.schema.Schema
import com.squareup.wire.schema.Service
import com.squareup.wire.schema.Type
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import okio.ByteString
import okio.ByteString.Companion.encode
import java.math.BigInteger
import java.util.Locale

class KotlinGenerator private constructor(
  val schema: Schema,
  private val nameToKotlinName: Map<ProtoType, ClassName>,
  private val emitAndroid: Boolean,
  private val javaInterOp: Boolean,
  private val rpcCallStyle: RpcCallStyle,
  private val rpcRole: RpcRole
) {
  private val nameAllocatorStore = mutableMapOf<Type, NameAllocator>()

  private val ProtoType.typeName
    get() = nameToKotlinName.getValue(this)
  private val ProtoType.isEnum
    get() = schema.getType(this) is EnumType
  private val Type.typeName
    get() = type.typeName
  private val Service.serviceName
    get() = type().typeName

  /** Returns the full name of the class generated for [type].  */
  fun generatedTypeName(type: Type) = type.typeName

  /**
   * Returns the full name of the class generated for [service]#[rpc]. This returns a name like
   * `RouteGuideClient` or `RouteGuideGetFeatureBlockingServer`.
   */
  fun generatedServiceName(service: Service, rpc: Rpc? = null): ClassName {
    val typeName = service.serviceName
    val simpleName = buildString {
      append(typeName.simpleName)
      if (rpc != null) {
        append(rpc.name)
      }
      when (rpcCallStyle) {
        RpcCallStyle.SUSPENDING -> Unit // Suspending is implicit.
        RpcCallStyle.BLOCKING -> {
          if (rpcRole == RpcRole.SERVER) append("Blocking")
        }
      }
      when (rpcRole) {
        RpcRole.CLIENT -> append("Client")
        RpcRole.SERVER -> append("Server")
      }
    }
    return typeName.peerClass(simpleName)
  }

  fun generateType(type: Type): TypeSpec {
    check(type.type != ProtoType.ANY)
    return when (type) {
      is MessageType -> generateMessage(type)
      is EnumType -> generateEnum(type)
      is EnclosingType -> generateEnclosing(type)
      else -> error("Unknown type $type")
    }
  }

  /**
   * If [onlyRpc] isn't null, this will generate code only for this onlyRpc; otherwise, all RPCs of
   * the [service] will be code generated.
   */
  fun generateService(service: Service, onlyRpc: Rpc? = null): TypeSpec {
    val serviceName = generatedServiceName(service, onlyRpc)
    val builder = if (rpcRole == RpcRole.SERVER) {
      TypeSpec.interfaceBuilder(serviceName)
    } else {
      TypeSpec.classBuilder(serviceName)
          .primaryConstructor(FunSpec.constructorBuilder()
              .addParameter("client", GrpcClient::class)
              .build())
          .addProperty(PropertySpec.builder("client", GrpcClient::class, PRIVATE)
              .initializer("client")
              .build())
    }
    builder
        .apply {
          if (service.documentation().isNotBlank()) {
            addKdoc("%L\n", service.documentation().sanitizeKdoc())
          }
        }
        .addSuperinterface(com.squareup.wire.Service::class.java)

    val rpcs = if (onlyRpc == null) service.rpcs() else listOf(onlyRpc)
    for (rpc in rpcs) {
      builder.addFunction(generateRpcFunction(
          rpc, service.name(), service.type().enclosingTypeOrPackage))
    }

    return builder.build()
  }

  private fun generateRpcFunction(
    rpc: Rpc,
    serviceName: String,
    servicePackageName: String?
  ): FunSpec {
    val packageName = if (servicePackageName.isNullOrBlank()) "" else "$servicePackageName."
    val funSpecBuilder = FunSpec.builder(rpc.name)
        .apply {
          if (rpc.documentation.isNotBlank()) {
            addKdoc("%L\n", rpc.documentation.sanitizeKdoc())
          }
        }

    val requestType = rpc.requestType!!.typeName
    val responseType = rpc.responseType!!.typeName

    if (rpcRole == RpcRole.SERVER) {
      val wireRpcAnnotationSpec = AnnotationSpec.builder(WireRpc::class.asClassName())
          .addMember("path = %S", "/$packageName$serviceName/${rpc.name}")
          // TODO(oldergod|jwilson) Lets' use Profile for this.
          .addMember("requestAdapter = %S", rpc.requestType!!.adapterString())
          .addMember("responseAdapter = %S", rpc.responseType!!.adapterString())
          .build()
      funSpecBuilder
          .addAnnotation(wireRpcAnnotationSpec)
          .addModifiers(KModifier.ABSTRACT)
      if (rpcCallStyle == RpcCallStyle.SUSPENDING) {
        funSpecBuilder.addModifiers(KModifier.SUSPEND)
      }
      when {
        rpc.requestStreaming && rpc.responseStreaming -> {
          funSpecBuilder
              .addParameter("request", readableStreamOf(requestType))
              .addParameter("response", writableStreamOf(responseType))
        }
        rpc.requestStreaming -> {
          funSpecBuilder
              .addParameter("request", readableStreamOf(requestType))
              .returns(responseType)
        }
        rpc.responseStreaming -> {
          funSpecBuilder
              .addParameter("request", requestType)
              .addParameter("response", writableStreamOf(responseType))
        }
        else -> {
          funSpecBuilder
              .addParameter("request", requestType)
              .returns(responseType)
        }
      }
    } else {
      val grpcMethod = CodeBlock.builder()
          .addStatement("%T(⇥⇥", GrpcMethod::class)
          .addStatement("path = %S,", "/$packageName$serviceName/${rpc.name}")
          .addStatement("requestAdapter = %L,", rpc.requestType!!.getAdapterName())
          .addStatement("responseAdapter = %L", rpc.responseType!!.getAdapterName())
          .add("⇤⇤)")
          .build()
      when {
        rpc.requestStreaming || rpc.responseStreaming -> {
          funSpecBuilder
              .returns(
                  GrpcStreamingCall::class.asClassName().parameterizedBy(requestType, responseType)
              )
              .addStatement("return client.newStreamingCall(%L)", grpcMethod)
        }
        else -> {
          funSpecBuilder
              .returns(
                  GrpcCall::class.asClassName().parameterizedBy(requestType, responseType)
              )
              .addStatement("return client.newCall(%L)", grpcMethod)
        }
      }
    }

    return funSpecBuilder.build()
  }

  private fun writableStreamOf(typeName: TypeName): ParameterizedTypeName {
    return when (rpcCallStyle) {
      RpcCallStyle.SUSPENDING -> ReceiveChannel::class.asClassName().parameterizedBy(typeName)
      RpcCallStyle.BLOCKING -> MessageSink::class.asClassName().parameterizedBy(typeName)
    }
  }

  private fun readableStreamOf(typeName: TypeName): ParameterizedTypeName {
    return when (rpcCallStyle) {
      RpcCallStyle.SUSPENDING -> SendChannel::class.asClassName().parameterizedBy(typeName)
      RpcCallStyle.BLOCKING -> MessageSource::class.asClassName().parameterizedBy(typeName)
    }
  }

  private fun readableSingleOf(typeName: TypeName): ParameterizedTypeName {
    return when (rpcCallStyle) {
      RpcCallStyle.SUSPENDING -> Deferred::class.asClassName().parameterizedBy(typeName)
      RpcCallStyle.BLOCKING -> MessageSource::class.asClassName().parameterizedBy(typeName)
    }
  }

  private fun pairOf(a: TypeName, b: TypeName) =
      Pair::class.asClassName().parameterizedBy(a, b)

  private fun nameAllocator(message: Type): NameAllocator {
    return nameAllocatorStore.getOrPut(message) {
      NameAllocator().apply {
        when (message) {
          is EnumType -> {
            newName("value", "value")
            newName("ADAPTER", "ADAPTER")
            newName("ENUM_OPTIONS", "ENUM_OPTIONS")
            message.constants.forEach { constant ->
              newName(constant.name, constant)
            }
          }
          is MessageType -> {
            newName("unknownFields", "unknownFields")
            newName("ADAPTER", "ADAPTER")
            newName("reader", "reader")
            newName("Builder", "Builder")
            newName("builder", "builder")
            newName("MESSAGE_OPTIONS", "MESSAGE_OPTIONS")

            if (emitAndroid) {
              newName("CREATOR", "CREATOR")
            }
            message.fieldsAndOneOfFields.forEach { field ->
              if (field.name == field.type!!.simpleName ||
                  schema.getType(field.qualifiedName) != null) {
                newName(field.qualifiedName, field)
              } else {
                newName(field.name, field)
              }
            }
          }
        }
      }
    }
  }

  private fun generateMessage(type: MessageType): TypeSpec {
    val className = type.typeName
    val builderClassName = className.nestedClass("Builder")
    val nameAllocator = nameAllocator(type)
    val adapterName = nameAllocator["ADAPTER"]
    val unknownFields = nameAllocator["unknownFields"]
    val superclass = if (emitAndroid) ANDROID_MESSAGE else MESSAGE
    val companionBuilder = TypeSpec.companionObjectBuilder()

    addDefaultFields(type, companionBuilder, nameAllocator)
    addAdapter(type, companionBuilder)

    val classBuilder = TypeSpec.classBuilder(className)
        .apply {
          if (type.documentation.isNotBlank()) {
            addKdoc("%L\n", type.documentation.sanitizeKdoc())
          }
        }
        .superclass(if (javaInterOp) {
          superclass.parameterizedBy(className, builderClassName)
        } else {
          superclass.parameterizedBy(className, NOTHING)
        })
        .addSuperclassConstructorParameter(adapterName)
        .addSuperclassConstructorParameter(unknownFields)
        .addFunction(generateNewBuilderMethod(type, builderClassName))
        .addFunction(generateEqualsMethod(type, nameAllocator))
        .addFunction(generateHashCodeMethod(type, nameAllocator))
        .addFunction(generateToStringMethod(type, nameAllocator))
        .addFunction(generateCopyMethod(type, nameAllocator))
        .apply {
          if (javaInterOp) {
            addType(generateBuilderClass(type, className, builderClassName))
          }
        }

    if (emitAndroid) {
      addAndroidCreator(type, companionBuilder)
    }

    addMessageConstructor(type, classBuilder)

    if (type.oneOfs.isNotEmpty()) {
      classBuilder.addInitializerBlock(generateInitializerOneOfBlock(type))
    }

    classBuilder.addType(companionBuilder.build())

    type.nestedTypes.forEach { classBuilder.addType(generateType(it)) }

    return classBuilder.build()
  }

  private fun generateInitializerOneOfBlock(type: MessageType): CodeBlock {
    return buildCodeBlock {
      val nameAllocator = nameAllocator(type)
      type.oneOfs
          .filter { oneOf -> oneOf.fields.size >= 2 }
          .forEach { oneOf ->
            val countNonNull = MemberName("com.squareup.wire.internal", "countNonNull")
            val fieldNames = oneOf.fields.joinToString(", ", transform = nameAllocator::get)
            beginControlFlow("require(%M(%L) <= 1)", countNonNull, fieldNames)
            addStatement("%S", "At most one of $fieldNames may be non-null")
            endControlFlow()
          }
    }
  }

  private fun generateNewBuilderMethod(type: MessageType, builderClassName: ClassName): FunSpec {
    val funBuilder = FunSpec.builder("newBuilder")
        .addModifiers(OVERRIDE)

    if (!javaInterOp) {
      return funBuilder
          .addAnnotation(AnnotationSpec.builder(Deprecated::class)
              .addMember("message = %S", "Shouldn't be used in Kotlin")
              .addMember("level = %T.%L", DeprecationLevel::class, DeprecationLevel.HIDDEN)
              .build())
          .returns(NOTHING)
          .addStatement("throw %T()", ClassName("kotlin", "AssertionError"))
          .build()
    }

    funBuilder.returns(builderClassName)

    val nameAllocator = nameAllocator(type)

    funBuilder.addStatement("val builder = Builder()")

    type.fieldsAndOneOfFields.forEach { field ->
      val fieldName = nameAllocator[field]
      funBuilder.addStatement("builder.%1L = %1L", fieldName)
    }

    return funBuilder
        .addStatement("builder.addUnknownFields(unknownFields)")
        .addStatement("return builder")
        .build()
  }

  // Example:
  //
  // override fun equals(other: Any?): Boolean {
  //   if (other === this) return true
  //   if (other !is SimpleMessage) return false
  //   return unknownFields == other.unknownFields
  //       && optional_int32 == other.optional_int32
  // }
  private fun generateEqualsMethod(type: MessageType, nameAllocator: NameAllocator): FunSpec {
    val localNameAllocator = nameAllocator.copy()
    val otherName = localNameAllocator.newName("other")
    val kotlinType = type.typeName
    val result = FunSpec.builder("equals")
        .addModifiers(OVERRIDE)
        .addParameter(otherName, ANY.copy(nullable = true))
        .returns(BOOLEAN)

    val body = buildCodeBlock {
      addStatement("if (%N === this) return true", otherName)
      addStatement("if (%N !is %T) return false", otherName, kotlinType)
      add("«return unknownFields == %N.unknownFields", otherName)
      val fields = type.fieldsAndOneOfFields
      for (field in fields) {
        val fieldName = localNameAllocator[field]
        add("\n&& %1L == %2N.%1L", fieldName, otherName)
      }
      add("\n»")
    }
    result.addCode(body)

    return result.build()
  }

  // Example:
  //
  // override fun hashCode(): Int {
  //   var result = super.hashCode
  //   if (result == 0) {
  //     result = unknownFields.hashCode()
  //     result = result * 37 + f?.hashCode()
  //     super.hashCode = result
  //   }
  //   return result
  // }
  //
  // For repeated fields, the final "0" in the example above changes to a "1"
  // in order to be the same as the system hash code for an empty list.
  //
  private fun generateHashCodeMethod(type: MessageType, nameAllocator: NameAllocator): FunSpec {
    val localNameAllocator = nameAllocator.copy()
    val resultName = localNameAllocator.newName("result")
    val result = FunSpec.builder("hashCode")
        .addModifiers(OVERRIDE)
        .returns(INT)

    val fields = type.fieldsAndOneOfFields
    if (fields.isEmpty()) {
      result.addStatement("return unknownFields.hashCode()")
      return result.build()
    }

    val body = buildCodeBlock {
      addStatement("var %N = super.hashCode", resultName)
      beginControlFlow("if (%N == 0)", resultName)

      val hashCode = MemberName("kotlin", "hashCode")
      addStatement("%N = unknownFields.hashCode()", resultName)
      for (field in fields) {
        val fieldName = localNameAllocator[field]
        add("%1N = %1N * 37 + ", resultName)
        if (field.isRepeated || field.isRequired || field.isMap) {
          addStatement("%L.hashCode()", fieldName)
        } else {
          addStatement("%L.%M()", fieldName, hashCode)
        }
      }

      addStatement("super.hashCode = %N", resultName)
      endControlFlow()
      addStatement("return %N", resultName)
    }
    result.addCode(body)

    return result.build()
  }

  // Example:
  //
  // fun copy(
  //   name: String = this.name,
  //   id: Int = this.id,
  //   email: String? = this.email,
  //   phone: List<PhoneNumber> = this.phone,
  //   unknownFields: ByteString = this.unknownFields
  // ): Person {
  //   return Person(name, id, email, phone, unknownFields)
  // }
  private fun generateCopyMethod(type: MessageType, nameAllocator: NameAllocator): FunSpec {
    val className = generatedTypeName(type)
    val result = FunSpec.builder("copy")
        .returns(type.typeName)
    val fieldNames = mutableListOf<String>()
    for (field in type.fieldsAndOneOfFields) {
      val fieldName = nameAllocator[field]
      result.addParameter(ParameterSpec.builder(fieldName, field.typeName)
          .defaultValue("this.%N", fieldName)
          .build())
      fieldNames += fieldName
    }
    result.addParameter(ParameterSpec.builder("unknownFields", ByteString::class)
        .defaultValue("this.unknownFields")
        .build())
    fieldNames += "unknownFields"
    result.addStatement("return %L", fieldNames
        .joinToString(prefix = className.simpleName + "(", postfix = ")"))
    return result.build()
  }

  private fun generateBuilderClass(
    type: MessageType,
    className: ClassName,
    builderClassName: ClassName
  ): TypeSpec {
    val builder = TypeSpec.classBuilder("Builder")
        .superclass(Message.Builder::class.asTypeName()
            .parameterizedBy(className, builderClassName))

    if (!javaInterOp) {
      return builder
          .primaryConstructor(FunSpec.constructorBuilder()
              .addParameter("message", className)
              .build())
          .addProperty(PropertySpec.builder("message", className)
              .addModifiers(PRIVATE)
              .initializer("message")
              .build())
          .addFunction(FunSpec.builder("build")
              .addModifiers(OVERRIDE)
              .returns(className)
              .addStatement("return message")
              .build())
          .build()
    }

    val nameAllocator = nameAllocator(type)
    val builderClass = className.nestedClass("Builder")

    val returnBody = buildCodeBlock {
      add("return %T(⇥\n", className)

      val missingRequiredFields = MemberName("com.squareup.wire.internal", "missingRequiredFields")
      type.fieldsAndOneOfFields.forEach { field ->
        val fieldName = nameAllocator[field]

        val throwExceptionBlock = if (!field.isRepeated && field.isRequired) {
          CodeBlock.of(" ?: throw %1M(%2L, %2S)", missingRequiredFields, field.name)
        } else {
          CodeBlock.of("")
        }

        addStatement("%1L = %1L%2L,", fieldName, throwExceptionBlock)
      }
      add("unknownFields = buildUnknownFields()")
      add("⇤\n)\n") // close the block
    }

    type.fieldsAndOneOfFields.forEach { field ->
      val fieldName = nameAllocator[field]

      val propertyBuilder = PropertySpec.builder(fieldName, field.declarationClass)
          .mutable(true)
          .initializer(field.defaultValue)

      if (javaInterOp) {
        propertyBuilder.addAnnotation(JvmField::class)
      }

      builder.addProperty(propertyBuilder.build())
    }

    type.fields().forEach { field ->
      builder.addFunction(builderSetter(field, nameAllocator, builderClass, oneOf = null))
    }

    type.oneOfs.forEach { oneOf ->
      oneOf.fields.forEach { field ->
        builder.addFunction(builderSetter(field, nameAllocator, builderClass, oneOf))
      }
    }

    val buildFunction = FunSpec.builder("build")
        .addModifiers(OVERRIDE)
        .returns(className)
        .addCode(returnBody)
        .build()

    return builder.addFunction(buildFunction)
        .build()
  }

  private fun builderSetter(
    field: Field,
    nameAllocator: NameAllocator,
    builderType: TypeName,
    oneOf: OneOf?
  ): FunSpec {
    val fieldName = nameAllocator[field]
    val funBuilder = FunSpec.builder(fieldName)
        .addParameter(fieldName, field.getClass())
        .returns(builderType)
    if (field.documentation.isNotBlank()) {
      funBuilder.addKdoc("%L\n", field.documentation.sanitizeKdoc())
    }
    if (field.isDeprecated) {
      funBuilder.addAnnotation(AnnotationSpec.builder(Deprecated::class)
          .addMember("message = %S", "$fieldName is deprecated")
          .build())
    }
    if (field.isRepeated) {
      val checkElementsNotNull = MemberName("com.squareup.wire.internal", "checkElementsNotNull")
      funBuilder.addStatement("%M(%L)", checkElementsNotNull, fieldName)
    }

    return funBuilder
        .addStatement("this.%1L = %1L", fieldName)
        .apply {
          if (oneOf != null) {
            for (other in oneOf.fields) {
              if (field != other) {
                addStatement("this.%L = null", nameAllocator[other])
              }
            }
          }
        }
        .addStatement("return this")
        .build()
  }

  /**
   * Example
   * ```
   * class Person(
   *   val name: String,
   *   val email: String? = null,
   *   val phone: List<PhoneNumber> = emptyList(),
   *   unknownFields: ByteString = ByteString.EMPTY
   * )
   * ```
   */
  private fun addMessageConstructor(message: MessageType, classBuilder: TypeSpec.Builder) {
    val constructorBuilder = FunSpec.constructorBuilder()
    val nameAllocator = nameAllocator(message)
    val byteClass = ProtoType.BYTES.typeName

    val fields = message.fieldsAndOneOfFields

    fields.forEach { field ->
      val fieldClass = field.typeName
      val fieldName = nameAllocator[field]

      val parameterSpec = ParameterSpec.builder(fieldName, fieldClass)
      if (!field.isRequired) {
        parameterSpec.defaultValue(field.defaultValue)
      }

      if (field.isDeprecated) {
        parameterSpec.addAnnotation(AnnotationSpec.builder(Deprecated::class)
            .addMember("message = %S", "$fieldName is deprecated")
            .build())
      }

      parameterSpec.addAnnotation(wireFieldAnnotation(field))

      if (javaInterOp) {
        parameterSpec.addAnnotation(JvmField::class)
      }

      constructorBuilder.addParameter(parameterSpec.build())
      classBuilder.addProperty(PropertySpec.builder(fieldName, fieldClass)
          .initializer(fieldName)
          .apply {
            if (field.documentation.isNotBlank()) {
              addKdoc("%L\n", field.documentation.sanitizeKdoc())
            }
            if (field.isExtension) {
              addKdoc("Extension source: %L\n", field.location.withPathOnly())
            }
          }
          .build())
    }

    val unknownFields = nameAllocator["unknownFields"]
    constructorBuilder.addParameter(
        ParameterSpec.builder(unknownFields, byteClass)
            .defaultValue("%T.EMPTY", byteClass)
            .build())

    classBuilder.primaryConstructor(constructorBuilder.build())
  }

  private fun wireFieldAnnotation(field: Field): AnnotationSpec {
    return AnnotationSpec.builder(WireField::class)
        .useSiteTarget(FIELD)
        .addMember("tag = %L", field.tag)
        .apply {
          if (field.type!!.isMap) {
            addMember("keyAdapter = %S", field.type!!.keyType!!.adapterString())
            addMember("adapter = %S", field.type!!.valueType!!.adapterString())
          } else {
            addMember("adapter = %S", field.type!!.adapterString())
          }
        }
        .apply {
          if (!field.isOptional) {
            if (field.isPacked) {
              addMember("label = %T.PACKED", WireField.Label::class)
            } else if (field.label != null) {
              addMember("label = %T.%L", WireField.Label::class, field.label!!)
            }
          }
        }
        .apply { if (field.isRedacted) addMember("redacted = true") }
        .build()
  }

  private fun ProtoType.adapterString() = when {
    isScalar -> ProtoAdapter::class.java.name + '#' + toString().toUpperCase(Locale.US)
    else -> typeName.reflectionName() + "#ADAPTER"
  }

  private fun generateToStringMethod(type: MessageType, nameAllocator: NameAllocator): FunSpec {
    val localNameAllocator = nameAllocator.copy()
    val className = generatedTypeName(type)
    val fields = type.fieldsAndOneOfFields
    val body = buildCodeBlock {
      if (fields.isEmpty()) {
        addStatement("return %S", className.simpleName + "{}")
      } else {
        val resultName = localNameAllocator.newName("result")
        addStatement("val %N = mutableListOf<%T>()", resultName, STRING)
        for (field in fields) {
          val fieldName = localNameAllocator[field]
          if (field.isRepeated || field.isMap) {
            add("if (%N.isNotEmpty()) ", fieldName)
          } else if (!field.isRequired) {
            add("if (%N != null) ", fieldName)
          }
          addStatement("%N += %P", resultName, buildString {
            append(fieldName)
            if (field.isRedacted) {
              append("=██")
            } else {
              append("=\$")
              append(fieldName)
            }
          })
        }
        addStatement(
            "return %N.joinToString(prefix = %S, separator = %S, postfix = %S)",
            resultName,
            className.simpleName + "{",
            ", ",
            "}"
        )
      }
    }
    return FunSpec.builder("toString")
        .addModifiers(OVERRIDE)
        .returns(String::class)
        .addCode(body)
        .build()
  }

  private fun addDefaultFields(
    type: MessageType,
    companionBuilder: TypeSpec.Builder,
    nameAllocator: NameAllocator
  ) {
    for (field in type.fieldsAndOneOfFields) {
      val default = field.default ?: continue

      val fieldName = "DEFAULT_" + nameAllocator[field].toUpperCase(Locale.US)
      val fieldType = field.getClass().copy(nullable = false)
      val fieldValue = defaultFieldInitializer(field.type!!, default)
      companionBuilder.addProperty(
          PropertySpec.builder(fieldName, fieldType)
              .apply {
                if (field.type!!.isScalar && field.type != ProtoType.BYTES) {
                  addModifiers(KModifier.CONST)
                } else {
                  jvmField()
                }
              }
              .initializer(fieldValue)
              .build())
    }
  }

  private fun defaultFieldInitializer(protoType: ProtoType, defaultValue: Any): CodeBlock {
    val typeName = protoType.typeName
    return when {
      defaultValue is List<*> -> defaultValue.toListFieldInitializer(protoType)
      defaultValue is Map<*, *> -> defaultValue.toMapFieldInitializer(protoType)
      typeName == BOOLEAN -> CodeBlock.of("%L", defaultValue)
      typeName == INT -> defaultValue.toIntFieldInitializer()
      typeName == LONG -> defaultValue.toLongFieldInitializer()
      typeName == FLOAT -> CodeBlock.of("%Lf", defaultValue.toString())
      typeName == DOUBLE -> CodeBlock.of("%L", defaultValue.toString())
      typeName == STRING -> CodeBlock.of("%S", defaultValue)
      typeName == ByteString::class.asTypeName() -> CodeBlock.of("%S.%M()!!",
          defaultValue.toString().encode(charset = Charsets.ISO_8859_1).base64(),
          ByteString.Companion::class.asClassName().member("decodeBase64"))
      protoType.isEnum -> CodeBlock.of("%T.%L", typeName, defaultValue)
      else -> throw IllegalStateException("$protoType is not an allowed scalar type")
    }
  }

  private fun List<*>.toListFieldInitializer(protoType: ProtoType): CodeBlock = buildCodeBlock {
    add("listOf(")
    var first = true
    forEach {
      if (!first) add(",")
      first = false
      add("\n⇥%L⇤", defaultFieldInitializer(protoType, it!!))
    }
    add("\n)")
  }

  private fun Map<*, *>.toMapFieldInitializer(protoType: ProtoType): CodeBlock = buildCodeBlock {
    add("%T(", protoType.typeName)
    var first = true
    entries.forEach { entry ->
      val field = schema.getField(entry.key as ProtoMember)!!
      val valueInitializer = defaultFieldInitializer(field.type!!, entry.value!!)
      val nameAllocator = nameAllocator(schema.getType(protoType)!!)
      if (!first) add(",")
      first = false
      add("\n⇥%L = %L⇤", nameAllocator[field], valueInitializer)
    }
    add("\n)")
  }

  private fun Any.toIntFieldInitializer(): CodeBlock = when (val int = valueToInt()) {
    Int.MIN_VALUE -> CodeBlock.of("%T.MIN_VALUE", INT)
    Int.MAX_VALUE -> CodeBlock.of("%T.MAX_VALUE", INT)
    else -> CodeBlock.of("%L", int)
  }

  private fun Any.valueToInt(): Int {
    val string = toString()
    return when {
      string.startsWith("0x") || string.startsWith("0X") ->
        string.substring("0x".length).toInt(radix = 16) // Hexadecimal.
      string.startsWith("0") && string != "0" ->
        throw IllegalStateException("Octal literal unsupported $this")
      else -> BigInteger(string).toInt()
    }
  }

  private fun Any.toLongFieldInitializer(): CodeBlock = when (val long = valueToLong()) {
    Long.MIN_VALUE -> CodeBlock.of("%T.MIN_VALUE", LONG)
    Long.MAX_VALUE -> CodeBlock.of("%T.MAX_VALUE", LONG)
    else -> CodeBlock.of("%LL", long)
  }

  private fun Any.valueToLong(): Long {
    val string = toString()
    return when {
      string.startsWith("0x") || string.startsWith("0X") ->
        string.substring("0x".length).toLong(radix = 16) // Hexadecimal.
      string.startsWith("0") && string != "0" ->
        throw IllegalStateException("Octal literal unsupported $this")
      else -> BigInteger(string).toLong()
    }
  }

  /**
   * Example
   * ```
   * companion object {
   *  @JvmField
   *  val ADAPTER : ProtoAdapter<Person> =
   *      object : ProtoAdapter<Person>(FieldEncoding.LENGTH_DELIMITED, Person::class, "square.github.io/wire/unknown") {
   *    override fun encodedSize(value: Person): Int { .. }
   *    override fun encode(writer: ProtoWriter, value: Person) { .. }
   *    override fun decode(reader: ProtoReader): Person { .. }
   *    override fun redact(value: Person): Person? { .. }
   *  }
   * }
   * ```
   */
  private fun addAdapter(type: MessageType, companionObjBuilder: TypeSpec.Builder) {
    val nameAllocator = nameAllocator(type)
    val parentClassName = generatedTypeName(type)
    val adapterName = nameAllocator["ADAPTER"]

    val adapterObject = TypeSpec.anonymousClassBuilder()
        .superclass(ProtoAdapter::class.asClassName().parameterizedBy(parentClassName))
        .addSuperclassConstructorParameter("\n⇥%T.LENGTH_DELIMITED",
            FieldEncoding::class.asClassName())
        .addSuperclassConstructorParameter("\n%T::class", parentClassName)
        .addSuperclassConstructorParameter("\n%S\n⇤", type.type.typeUrl!!)
        .addFunction(encodedSizeFun(type))
        .addFunction(encodeFun(type))
        .addFunction(decodeFun(type))
        .addFunction(redactFun(type))

    for (field in type.fields()) {
      if (field.isMap) {
        adapterObject.addProperty(field.toProtoAdapterPropertySpec())
      }
    }

    val adapterType = ProtoAdapter::class.asClassName().parameterizedBy(parentClassName)

    companionObjBuilder.addProperty(PropertySpec.builder(adapterName, adapterType)
        .jvmField()
        .initializer("%L", adapterObject.build())
        .build())
  }

  private fun Field.toProtoAdapterPropertySpec(): PropertySpec {
    val adapterType = ProtoAdapter::class.asTypeName()
        .parameterizedBy(Map::class.asTypeName()
            .parameterizedBy(keyType.typeName, valueType.typeName))

    return PropertySpec.builder("${name}Adapter", adapterType, PRIVATE)
        .initializer(
            "%T.newMapAdapter(%L, %L)",
            ProtoAdapter::class,
            keyType.getAdapterName(),
            valueType.getAdapterName()
        )
        .build()
  }

  private fun encodedSizeFun(message: MessageType): FunSpec {
    val className = generatedTypeName(message)
    val nameAllocator = nameAllocator(message)
    val body = buildCodeBlock {
      add("return \n⇥")
      message.fieldsAndOneOfFields.forEach { field ->
        val fieldName = nameAllocator[field]
        add("%L.encodedSizeWithTag(%L, value.%L) +\n", adapterFor(field), field.tag, fieldName)
      }
      add("value.unknownFields.size⇤\n")
    }
    return FunSpec.builder("encodedSize")
        .addParameter("value", className)
        .returns(Int::class)
        .addCode(body)
        .addModifiers(OVERRIDE)
        .build()
  }

  private fun adapterFor(field: Field) = buildCodeBlock {
    add("%L", field.getAdapterName())
    if (field.isPacked) {
      add(".asPacked()")
    } else if (field.isRepeated) {
      add(".asRepeated()")
    }
  }

  private fun encodeFun(message: MessageType): FunSpec {
    val className = generatedTypeName(message)
    val body = buildCodeBlock {
      val nameAllocator = nameAllocator(message)

      message.fieldsAndOneOfFields.forEach { field ->
        val fieldName = nameAllocator[field]
        addStatement("%L.encodeWithTag(writer, %L, value.%L)",
            adapterFor(field),
            field.tag,
            fieldName)
      }
      addStatement("writer.writeBytes(value.unknownFields)")
    }

    return FunSpec.builder("encode")
        .addParameter("writer", ProtoWriter::class)
        .addParameter("value", className)
        .addCode(body)
        .addModifiers(OVERRIDE)
        .build()
  }

  private fun decodeFun(message: MessageType): FunSpec {
    val className = nameToKotlinName.getValue(message.type)
    val nameAllocator = nameAllocator(message).copy()

    val declarationBody = buildCodeBlock {
      message.fieldsAndOneOfFields.forEach { field ->
        val fieldName = nameAllocator[field]
        val fieldDeclaration: CodeBlock = field.getDeclaration(fieldName)
        addStatement("%L", fieldDeclaration)
      }
    }

    val returnBody = buildCodeBlock {
      addStatement("return %T(⇥", className)

      val missingRequiredFields = MemberName("com.squareup.wire.internal", "missingRequiredFields")
      message.fieldsAndOneOfFields.forEach { field ->
        val fieldName = nameAllocator[field]

        val throwExceptionBlock = if (!field.isRepeated && !field.isMap && field.isRequired) {
          CodeBlock.of(" ?: throw %1M(%2L, %3S)", missingRequiredFields, fieldName, field.name)
        } else {
          CodeBlock.of("")
        }

        addStatement("%1L = %1L%2L,", fieldName, throwExceptionBlock)
      }

      add("unknownFields = unknownFields")
      add("⇤\n)\n") // close the block
    }

    val decodeBlock = buildCodeBlock {
      val fields = message.fieldsAndOneOfFields
      if (fields.isEmpty()) {
        addStatement("val unknownFields = reader.forEachTag(reader::readUnknownField)")
      } else {
        val tag = nameAllocator.newName("tag")
        addStatement("val unknownFields = reader.forEachTag { %L ->", tag)
        addStatement("⇥when (%L) {⇥", tag)

        message.fieldsAndOneOfFields.forEach { field ->
          val fieldName = nameAllocator[field]
          val adapterName = field.getAdapterName()

          if (field.type!!.isEnum) {
            beginControlFlow("%L -> try", field.tag)
            addStatement("%L", decodeAndAssign(field, fieldName, adapterName))
            nextControlFlow("catch (e: %T)", ProtoAdapter.EnumConstantNotFoundException::class)
            addStatement("reader.addUnknownField(%L, %T.VARINT, e.value.toLong())", tag,
                FieldEncoding::class)
            endControlFlow()
          } else {
            addStatement("%L -> %L", field.tag, decodeAndAssign(field, fieldName, adapterName))
          }
        }
        addStatement("else -> reader.readUnknownField(%L)", tag)
        add("⇤}\n⇤}\n") // close the block
      }
    }

    return FunSpec.builder("decode")
        .addParameter("reader", ProtoReader::class)
        .returns(className)
        .addCode(declarationBody)
        .addCode(decodeBlock)
        .addCode(returnBody)
        .addModifiers(OVERRIDE)
        .build()
  }

  private fun decodeAndAssign(field: Field, fieldName: String, adapterName: CodeBlock): CodeBlock {
    val decode = CodeBlock.of("%L.decode(reader)", adapterName)
    return CodeBlock.of(when {
      field.isRepeated -> "%L.add(%L)"
      field.isMap -> "%L.putAll(%L)"
      else -> "%L = %L"
    }, fieldName, decode)
  }

  private fun redactFun(message: MessageType): FunSpec {
    val className = nameToKotlinName.getValue(message.type)
    val nameAllocator = nameAllocator(message)
    val result = FunSpec.builder("redact")
        .addModifiers(OVERRIDE)
        .addParameter("value", className)
        .returns(className)

    val redactedMessageFields = message.fields().filter { it.isRedacted }
    val requiredRedactedMessageFields = redactedMessageFields.filter { it.isRequired }
    if (requiredRedactedMessageFields.isNotEmpty()) {
      result.addStatement(
          "throw %T(%S)",
          ClassName("kotlin", "UnsupportedOperationException"),
          requiredRedactedMessageFields.joinToString(
              prefix = if (requiredRedactedMessageFields.size > 1) "Fields [" else "Field '",
              postfix = if (requiredRedactedMessageFields.size > 1) "] are " else "' is " +
                  "required and cannot be redacted.",
              transform = nameAllocator::get
          )
      )
      return result.build()
    }

    val redactedFields = mutableListOf<CodeBlock>()
    for (field in message.fieldsAndOneOfFields) {
      val fieldName = nameAllocator[field]
      val redactedField = field.redact(fieldName)
      if (redactedField != null) {
        redactedFields += CodeBlock.of("%N = %L", fieldName, redactedField)
      }
    }
    redactedFields += CodeBlock.of("unknownFields = %T.EMPTY", ByteString::class)
    return result
        .addStatement(
            "return %L",
            redactedFields.joinToCode(separator = ",\n", prefix = "value.copy(\n⇥", suffix = "\n⇤)")
        )
        .build()
  }

  private fun Field.redact(fieldName: String): CodeBlock? {
    if (isRedacted) {
      return when {
        isRepeated -> CodeBlock.of("emptyList()")
        isMap -> CodeBlock.of("emptyMap()")
        else -> CodeBlock.of("null")
      }
    } else if (!type!!.isScalar && !type!!.isEnum) {
      val redactElements = MemberName("com.squareup.wire.internal", "redactElements")
      if (isRepeated) {
        return CodeBlock.of("value.%N.%M(%L)", fieldName, redactElements, getAdapterName())
      } else if (isMap) {
        // We only need to ask the values to redact themselves if the type is a message.
        if (!valueType.isScalar && !valueType.isEnum) {
          val adapterName = valueType.getAdapterName()
          return CodeBlock.of("value.%N.%M(%L)", fieldName, redactElements, adapterName)
        }
      } else {
        val adapterName = getAdapterName()
        return if (isRequired) {
          CodeBlock.of("%L.redact(value.%N)", adapterName, fieldName)
        } else {
          CodeBlock.of("value.%N?.let(%L::redact)", fieldName, adapterName)
        }
      }
    }
    return null
  }

  // TODO add support for custom adapters.
  private fun Field.getAdapterName(nameDelimiter: Char = '.'): CodeBlock {
    return if (type!!.isMap) {
      CodeBlock.of("%NAdapter", name)
    } else {
      type!!.getAdapterName(nameDelimiter)
    }
  }

  private fun ProtoType.getAdapterName(adapterFieldDelimiterName: Char = '.'): CodeBlock {
    return when {
      isScalar -> CodeBlock.of(
          "%T$adapterFieldDelimiterName%L",
          ProtoAdapter::class, simpleName.toUpperCase(Locale.US)
      )
      isMap -> throw IllegalArgumentException("Can't create single adapter for map type $this")
      else -> CodeBlock.of("%T${adapterFieldDelimiterName}ADAPTER", typeName)
    }
  }

  /**
   * Example
   * ```
   * enum class PhoneType(override val value: Int) : WireEnum {
   *     HOME(0),
   *     ...
   *
   *     companion object {
   *       fun fromValue(value: Int): PhoneType = ...
   *
   *       val ADAPTER: ProtoAdapter<PhoneType> = ...
   *     }
   * ```
   * }
   */
  private fun generateEnum(message: EnumType): TypeSpec {
    val type = message.type
    val nameAllocator = nameAllocator(message)

    val valueName = nameAllocator["value"]

    val primaryConstructor = FunSpec.constructorBuilder()
        .addParameter(valueName, Int::class, OVERRIDE)

    val builder = TypeSpec.enumBuilder(type.simpleName)
        .apply {
          if (message.documentation.isNotBlank()) {
            addKdoc("%L\n", message.documentation.sanitizeKdoc())
          }
        }
        .addSuperinterface(WireEnum::class)
        .addProperty(PropertySpec.builder(valueName, Int::class)
            .initializer(valueName)
            .build())
        .addType(generateEnumCompanion(message))

    val allOptionFieldsBuilder = mutableSetOf<ProtoMember>()

    // Each enum constant option requires a constructor parameter and property
    message.constants.forEach { constant ->
      constant.options.map.keys.forEach { protoMember ->
        if (allOptionFieldsBuilder.add(protoMember)) {
          val optionField = schema.getField(protoMember)!!
          primaryConstructor.addParameter(optionField.name, optionField.typeName)
          builder.addProperty(PropertySpec.builder(optionField.name, optionField.typeName)
              .initializer(optionField.name)
              .build())
        }
      }
    }

    message.constants.forEach { constant ->
      builder.addEnumConstant(nameAllocator[constant], TypeSpec.anonymousClassBuilder()
          .addSuperclassConstructorParameter("%L", constant.tag)
          .apply {
            allOptionFieldsBuilder.toList().forEach { protoMember ->
              val field = schema.getField(protoMember)!!
              val fieldValue = constant.options.map[protoMember]
              if (fieldValue != null) {
                addSuperclassConstructorParameter("%L",
                    defaultFieldInitializer(field.type!!, fieldValue))
              } else {
                addSuperclassConstructorParameter("null")
              }
            }
            if (constant.documentation.isNotBlank()) {
              addKdoc("%L\n", constant.documentation.sanitizeKdoc())
            }
            if (constant.options.get(ENUM_DEPRECATED) == "true") {
              addAnnotation(AnnotationSpec.builder(Deprecated::class)
                  .addMember("message = %S", "${constant.name} is deprecated")
                  .build())
            }
          }
          .build())
    }

    return builder.primaryConstructor(primaryConstructor.build())
        .build()
  }

  private fun generateEnumCompanion(message: EnumType): TypeSpec {
    val nameAllocator = nameAllocator(message)
    val companionObjectBuilder = TypeSpec.companionObjectBuilder()
    val parentClassName = nameToKotlinName.getValue(message.type)
    val valueName = nameAllocator["value"]
    val fromValue = FunSpec.builder("fromValue")
        .jvmStatic()
        .addParameter(valueName, Int::class)
        .returns(parentClassName.copy(nullable = true))
        .apply {
          addCode("return when (value) {\n⇥")
          message.constants.forEach { constant ->
            addCode("%L -> %L\n", constant.tag, nameAllocator[constant])
          }
          addCode("else -> null")
          addCode("\n⇤}\n") // close the block
        }
        .build()

    return companionObjectBuilder.addFunction(fromValue)
        .addProperty(generateEnumAdapter(message))
        .build()
  }

  /**
   * Example
   * ```
   * @JvmField
   * val ADAPTER = object : EnumAdapter<PhoneType>(PhoneType::class) {
   *     override fun fromValue(value: Int): PhoneType = PhoneType.fromValue(value)
   * }
   * ```
   */
  private fun generateEnumAdapter(message: EnumType): PropertySpec {
    val parentClassName = nameToKotlinName.getValue(message.type)
    val nameAllocator = nameAllocator(message)

    val adapterName = nameAllocator["ADAPTER"]
    val valueName = nameAllocator["value"]

    val adapterType = ProtoAdapter::class.asClassName().parameterizedBy(parentClassName)
    val adapterObject = TypeSpec.anonymousClassBuilder()
        .superclass(EnumAdapter::class.asClassName().parameterizedBy(parentClassName))
        .addSuperclassConstructorParameter("\n⇥%T::class\n⇤", parentClassName)
        .addFunction(FunSpec.builder("fromValue")
            .addModifiers(OVERRIDE)
            .addParameter(valueName, Int::class)
            .returns(parentClassName.copy(nullable = true))
            .addStatement("return %T.fromValue(value)", parentClassName)
            .build())
        .build()

    return PropertySpec.builder(adapterName, adapterType)
        .jvmField()
        .initializer("%L", adapterObject)
        .build()
  }

  /**
   * Example
   * ```
   * companion object {
   *     @JvmStatic
   *     val CREATOR: Parcelable.Creator<Person> = AndroidMessage.newCreator(ADAPTER)
   * }
   * ```
   */
  private fun addAndroidCreator(type: MessageType, companionObjBuilder: TypeSpec.Builder) {
    val nameAllocator = nameAllocator(type)
    val parentClassName = generatedTypeName(type)
    val creatorName = nameAllocator["CREATOR"]
    val creatorTypeName = ClassName("android.os", "Parcelable", "Creator")
        .parameterizedBy(parentClassName)

    companionObjBuilder.addProperty(PropertySpec.builder(creatorName, creatorTypeName)
        .jvmField()
        .initializer("%T.newCreator(ADAPTER)", ANDROID_MESSAGE)
        .build())
  }

  private fun generateEnclosing(type: EnclosingType): TypeSpec {
    val classBuilder = TypeSpec.classBuilder(type.typeName)
        .primaryConstructor(FunSpec.constructorBuilder().addModifiers(PRIVATE).build())

    type.nestedTypes.forEach { classBuilder.addType(generateType(it)) }

    return classBuilder.build()
  }

  private fun Field.getDeclaration(allocatedName: String) = when {
    isRepeated -> CodeBlock.of("val $allocatedName = mutableListOf<%T>()", type!!.typeName)
    isMap -> CodeBlock.of("val $allocatedName = mutableMapOf<%T, %T>()",
        keyType.typeName, valueType.typeName)
    else -> CodeBlock.of("var $allocatedName: %T = %L", declarationClass, defaultValue)
  }

  private val Field.declarationClass: TypeName
    get() = when {
      isRepeated || isMap -> getClass()
      else -> getClass().copy(nullable = true)
    }

  private fun ProtoType.asTypeName(): TypeName = when {
    isMap -> Map::class.asTypeName()
        .parameterizedBy(keyType!!.asTypeName(), valueType!!.asTypeName())
    else -> nameToKotlinName.getValue(this)
  }

  private fun Field.getClass(baseClass: TypeName = type!!.asTypeName()) = when {
    isRepeated -> List::class.asClassName().parameterizedBy(baseClass)
    isOptional -> baseClass.copy(nullable = true)
    else -> baseClass.copy(nullable = false)
  }

  private val Field.typeName: TypeName
    get() = when {
      isRepeated -> List::class.asClassName().parameterizedBy(type!!.typeName)
      isMap -> Map::class.asTypeName().parameterizedBy(keyType.typeName, valueType.typeName)
      !isRequired -> type!!.typeName.copy(nullable = true)
      else -> type!!.typeName
    }

  private val Field.defaultValue: CodeBlock
    get() = when {
      isRepeated -> CodeBlock.of("emptyList()")
      isMap -> CodeBlock.of("emptyMap()")
      else -> CodeBlock.of("null")
    }

  companion object {
    private val BUILT_IN_TYPES = mapOf(
        ProtoType.BOOL to BOOLEAN,
        ProtoType.BYTES to ByteString::class.asClassName(),
        ProtoType.DOUBLE to DOUBLE,
        ProtoType.FLOAT to FLOAT,
        ProtoType.FIXED32 to INT,
        ProtoType.FIXED64 to LONG,
        ProtoType.INT32 to INT,
        ProtoType.INT64 to LONG,
        ProtoType.SFIXED32 to INT,
        ProtoType.SFIXED64 to LONG,
        ProtoType.SINT32 to INT,
        ProtoType.SINT64 to LONG,
        ProtoType.STRING to String::class.asClassName(),
        ProtoType.UINT32 to INT,
        ProtoType.UINT64 to LONG,
        ProtoType.ANY to ClassName("com.squareup.wire", "AnyMessage"),
        FIELD_OPTIONS to ClassName("com.google.protobuf", "FieldOptions"),
        MESSAGE_OPTIONS to ClassName("com.google.protobuf", "MessageOptions"),
        ENUM_OPTIONS to ClassName("com.google.protobuf", "EnumOptions")
    )
    private val MESSAGE = Message::class.asClassName()
    private val ANDROID_MESSAGE = MESSAGE.peerClass("AndroidMessage")

    private val ENUM_DEPRECATED = ProtoMember.get(ENUM_VALUE_OPTIONS, "deprecated")

    @JvmStatic @JvmName("get")
    operator fun invoke(
      schema: Schema,
      emitAndroid: Boolean = false,
      javaInterop: Boolean = false,
      rpcCallStyle: RpcCallStyle = RpcCallStyle.SUSPENDING,
      rpcRole: RpcRole = RpcRole.CLIENT
    ): KotlinGenerator {
      val map = mutableMapOf<ProtoType, ClassName>()

      fun putAll(kotlinPackage: String, enclosingClassName: ClassName?, types: List<Type>) {
        for (type in types) {
          val className = enclosingClassName?.nestedClass(type.type.simpleName)
              ?: ClassName(kotlinPackage, type.type.simpleName)
          map[type.type] = className
          putAll(kotlinPackage, className, type.nestedTypes)
        }
      }

      for (protoFile in schema.protoFiles) {
        val kotlinPackage = protoFile.kotlinPackage()
        putAll(kotlinPackage, null, protoFile.types)

        for (service in protoFile.services) {
          val className = ClassName(kotlinPackage, service.type().simpleName)
          map[service.type()] = className
        }
      }

      map.putAll(BUILT_IN_TYPES)

      return KotlinGenerator(schema, map, emitAndroid, javaInterop, rpcCallStyle, rpcRole)
    }

    internal fun String.sanitizeKdoc(): String {
      return this
          // Remove trailing whitespace on each line.
          .replace("[^\\S\n]+\n".toRegex(), "\n")
          .replace("\\s+$".toRegex(), "")
          .replace("\\*/".toRegex(), "*/")
          .replace("/\\*".toRegex(), "/*")
    }
  }
}

private fun ProtoFile.kotlinPackage() = javaPackage() ?: packageName ?: ""

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.kotlin

enum class RpcCallStyle {
  /** Generate coroutines APIs that require a Kotlin coroutines context. */
  SUSPENDING,

  /** Generate blocking APIs callable by Java and Kotlin. */
  BLOCKING;
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.kotlin

enum class RpcRole {
  /** Generate interfaces best suited to sending outbound calls. */
  CLIENT,

  /** Generate interfaces best suited to receiving inbound calls. */
  SERVER;
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okio.IOException
import kotlin.reflect.KClass

expect interface Serializable

// TODO(egorand): Remove when https://youtrack.jetbrains.com/issue/KT-26283 lands
@OptionalExpectation
@UseExperimental(ExperimentalMultiplatform::class)
expect annotation class Throws(vararg val exceptionClasses: KClass<out Throwable>)

expect abstract class ObjectStreamException : IOException

expect class ProtocolException(host: String) : IOException

expect fun <T> MutableList<T>.toUnmodifiableList(): List<T>

expect fun <K, V> MutableMap<K, V>.toUnmodifiableMap(): Map<K, V>

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import kotlin.jvm.JvmName

internal class ImmutableList<T>(list: List<T>) : AbstractList<T?>(), RandomAccess, Serializable {
  private val list = ArrayList(list)

  override val size: Int
    @get:JvmName("size") get() = list.size

  override fun get(index: Int): T = list[index]

  override fun toArray(): Array<Any?> {
    return list.toTypedArray() // Optimizing for mutable copy by MutableOnWriteList.
  }

  @Throws(ObjectStreamException::class)
  private fun writeReplace(): Any = list.toUnmodifiableList()
}

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
@file:JvmName("Internal")
@file:JvmMultifileClass

package com.squareup.wire.internal

import com.squareup.wire.ProtoAdapter
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName

// Methods for generated code use only. Not subject to public API rules.

fun <T> newMutableList(): MutableList<T> = MutableOnWriteList(emptyList())

fun <K, V> newMutableMap(): MutableMap<K, V> = LinkedHashMap()

@Deprecated(
    message = "Please regenerate code using wire-compiler version 3.0.0 or higher.",
    replaceWith = ReplaceWith("com.squareup.internal.Internal.copyOf(list)")
)
fun <T> copyOf(name: String, list: List<T>?): MutableList<T> = copyOf(list!!)

fun <T> copyOf(list: List<T>): MutableList<T> {
  return if (list === emptyList<T>() || list is ImmutableList<*>) {
    MutableOnWriteList(list)
  } else {
    ArrayList(list)
  }
}

@Deprecated(
    message = "Please regenerate code using wire-compiler version 3.0.0 or higher.",
    replaceWith = ReplaceWith("com.squareup.internal.Internal.copyOf(map)")
)
fun <K, V> copyOf(name: String, map: Map<K, V>?): MutableMap<K, V> = copyOf(map!!)

fun <K, V> copyOf(map: Map<K, V>): MutableMap<K, V> = LinkedHashMap(map)

fun <T> immutableCopyOf(name: String, list: List<T>): List<T> {
  var list = list
  if (list is MutableOnWriteList<*>) {
    list = (list as MutableOnWriteList<T>).mutableList
  }
  if (list === emptyList<T>() || list is ImmutableList<*>) {
    return list
  }
  val result = ImmutableList(list)
  // Check after the list has been copied to defend against races.
  require(null !in result) { "$name.contains(null)" }
  return result as List<T>
}

fun <K, V> immutableCopyOf(name: String, map: Map<K?, V?>): Map<K, V> {
  if (map.isEmpty()) {
    return emptyMap()
  }
  val result = LinkedHashMap(map)
  // Check after the map has been copied to defend against races.
  require(null !in result.keys) { "$name.containsKey(null)" }
  require(null !in result.values) { "$name.containsValue(null)" }
  return (result as MutableMap<K, V>).toUnmodifiableMap()
}

@JvmName("-redactElements") // Hide from Java
fun <T> List<T>.redactElements(adapter: ProtoAdapter<T>): List<T> = map(adapter::redact)

@JvmName("-redactElements") // Hide from Java
fun <K, V> Map<K, V>.redactElements(adapter: ProtoAdapter<V>): Map<K, V> {
  return mapValues { (_, value) -> adapter.redact(value) }
}

fun equals(a: Any?, b: Any?): Boolean = a === b || (a != null && a == b)

/**
 * Create an exception for missing required fields.
 *
 * @param args Alternating field value and field name pairs.
 */
fun missingRequiredFields(vararg args: Any?): IllegalStateException {
  var plural = ""
  val fields = buildString {
    for (i in 0 until args.size step 2) {
      if (args[i] == null) {
        if (isNotEmpty()) {
          plural = "s" // Found more than one missing field
        }
        append("\n  ")
        append(args[i + 1])
      }
    }
  }
  throw IllegalStateException("Required field$plural not set:$fields")
}

/** Throw [NullPointerException] if any of `list`'s items is null. */
fun checkElementsNotNull(list: List<*>) {
  for (i in 0 until list.size) {
    if (list[i] == null) {
      throw NullPointerException("Element at index $i is null")
    }
  }
}

/** Throw [NullPointerException] if any of`map`'s keys or values is null. */
fun checkElementsNotNull(map: Map<*, *>) {
  for ((key, value) in map) {
    if (key == null) {
      throw NullPointerException("map.containsKey(null)")
    }
    if (value == null) {
      throw NullPointerException("Value for key $key is null")
    }
  }
}

/** Returns the number of non-null values in `a, b`. */
fun countNonNull(a: Any?, b: Any?): Int = (if (a != null) 1 else 0) + (if (b != null) 1 else 0)

/** Returns the number of non-null values in `a, b, c`. */
fun countNonNull(a: Any?, b: Any?, c: Any?): Int {
  return (if (a != null) 1 else 0) + (if (b != null) 1 else 0) + (if (c != null) 1 else 0)
}

/** Returns the number of non-null values in `a, b, c, d, rest`. */
fun countNonNull(a: Any?, b: Any?, c: Any?, d: Any?, vararg rest: Any?): Int {
  var result = 0
  if (a != null) result++
  if (b != null) result++
  if (c != null) result++
  if (d != null) result++
  for (o in rest) {
    if (o != null) result++
  }
  return result
}

/*
 * Copyright 2015 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import kotlin.jvm.JvmName

/** A wrapper around an empty/immutable list which only switches to mutable on first mutation. */
internal class MutableOnWriteList<T>(
  private val immutableList: List<T>
) : AbstractMutableList<T>(), RandomAccess, Serializable {
  internal var mutableList: List<T> = immutableList

  override fun get(index: Int): T = mutableList[index]

  override val size: Int
    @get:JvmName("size") get() = mutableList.size

  override fun set(index: Int, element: T): T {
    if (mutableList === immutableList) {
      mutableList = ArrayList(immutableList)
    }
    return (mutableList as ArrayList).set(index, element)
  }

  override fun add(index: Int, element: T) {
    if (mutableList === immutableList) {
      mutableList = ArrayList(immutableList)
    }
    (mutableList as ArrayList).add(index, element)
  }

  override fun removeAt(index: Int): T {
    if (mutableList === immutableList) {
      mutableList = ArrayList(immutableList)
    }
    return (mutableList as ArrayList).removeAt(index)
  }

  @Throws(ObjectStreamException::class)
  private fun writeReplace(): Any = ArrayList(mutableList)
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
@file:JvmName("RuntimeUtils")

package com.squareup.wire.internal

import kotlin.jvm.JvmName

@Suppress("NOTHING_TO_INLINE") // Syntactic sugar.
internal inline infix fun Byte.and(other: Int): Int = toInt() and other

@Suppress("NOTHING_TO_INLINE") // Syntactic sugar.
internal inline infix fun Byte.shl(other: Int): Int = toInt() shl other

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import okio.IOException
import kotlin.reflect.KClass

actual interface Serializable

actual abstract class ObjectStreamException : IOException()

actual class ProtocolException actual constructor(host: String) : IOException(host)

@Suppress("NOTHING_TO_INLINE") // Syntactic sugar.
actual inline fun <T> MutableList<T>.toUnmodifiableList(): List<T> = this

@Suppress("NOTHING_TO_INLINE") // Syntactic sugar.
actual inline fun <K, V> MutableMap<K, V>.toUnmodifiableMap(): Map<K, V> = this

/*
 * Copyright 2019 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import java.util.Collections

actual typealias Serializable = java.io.Serializable

actual typealias Throws = kotlin.jvm.Throws

actual typealias ObjectStreamException = java.io.ObjectStreamException

actual typealias ProtocolException = java.net.ProtocolException

@Suppress("NOTHING_TO_INLINE") // Syntactic sugar.
actual inline fun <T> MutableList<T>.toUnmodifiableList(): List<T>
    = Collections.unmodifiableList(this)

@Suppress("NOTHING_TO_INLINE") // Syntactic sugar.
actual inline fun <K, V> MutableMap<K, V>.toUnmodifiableMap(): Map<K, V> =
    Collections.unmodifiableMap(this)

/*
 * Copyright 2015 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import com.squareup.wire.Message
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.WireField
import java.lang.reflect.Field
import java.lang.reflect.Method

/**
 * Read, write, and describe a tag within a message. This class knows how to assign fields to a
 * builder object, and how to extract values from a message object.
 */
class FieldBinding<M : Message<M, B>, B : Message.Builder<M, B>> internal constructor(
  wireField: WireField,
  private val messageField: Field,
  builderType: Class<B>
) {
  val label: WireField.Label = wireField.label
  val name: String = messageField.name
  val tag: Int = wireField.tag
  private val keyAdapterString = wireField.keyAdapter
  private val adapterString = wireField.adapter
  val redacted: Boolean = wireField.redacted
  private val builderField = getBuilderField(builderType, name)
  private val builderMethod = getBuilderMethod(builderType, name, messageField.type)

  // Delegate adapters are created lazily; otherwise we could stack overflow!
  private var singleAdapter: ProtoAdapter<*>? = null
  private var keyAdapter: ProtoAdapter<*>? = null
  private var adapter: ProtoAdapter<Any>? = null

  val isMap: Boolean
    get() = keyAdapterString.isNotEmpty()

  private fun getBuilderField(builderType: Class<*>, name: String): Field {
    try {
      return builderType.getField(name)
    } catch (_: NoSuchFieldException) {
      throw AssertionError("No builder field ${builderType.name}.$name")
    }
  }

  private fun getBuilderMethod(builderType: Class<*>, name: String, type: Class<*>): Method {
    try {
      return builderType.getMethod(name, type)
    } catch (_: NoSuchMethodException) {
      throw AssertionError("No builder method ${builderType.name}.$name(${type.name})")
    }
  }

  fun singleAdapter(): ProtoAdapter<*> {
    return singleAdapter ?: ProtoAdapter.get(adapterString).also { singleAdapter = it }
  }

  fun keyAdapter(): ProtoAdapter<*> {
    return keyAdapter ?: ProtoAdapter.get(keyAdapterString).also { keyAdapter = it }
  }

  internal fun adapter(): ProtoAdapter<Any> {
    val result = adapter
    if (result != null) return result
    if (isMap) {
      val keyAdapter = keyAdapter() as ProtoAdapter<Any>
      val valueAdapter = singleAdapter() as ProtoAdapter<Any>
      return (ProtoAdapter.newMapAdapter(keyAdapter, valueAdapter) as ProtoAdapter<Any>).also {
        adapter = it
      }
    }
    return (singleAdapter().withLabel(label) as ProtoAdapter<Any>).also { adapter = it }
  }

  /** Accept a single value, independent of whether this value is single or repeated. */
  internal fun value(builder: B, value: Any) {
    when {
      label.isRepeated -> {
        when (val list = getFromBuilder(builder)) {
          is MutableList<*> -> (list as MutableList<Any>).add(value)
          is List<*> -> {
            val mutableList = list.toMutableList()
            mutableList.add(value)
            set(builder, mutableList)
          }
          else -> {
            val type = list?.let { it::class.java }
            throw ClassCastException("Expected a list type, got $type.")
          }
        }
      }
      keyAdapterString.isNotEmpty() -> {
        when (val map = getFromBuilder(builder)) {
          is MutableMap<*, *> -> map.putAll(value as Map<Nothing, Nothing>)
          is Map<*, *> -> {
            val mutableMap = map.toMutableMap()
            mutableMap.putAll(value as Map<out Any?, Any?>)
            set(builder, mutableMap)
          }
          else -> {
            val type = map?.let { it::class.java }
            throw ClassCastException("Expected a map type, got $type.")
          }
        }
      }
      else -> set(builder, value)
    }
  }

  /** Assign a single value for required/optional fields, or a list for repeated/packed fields. */
  operator fun set(builder: B, value: Any?) {
    if (label.isOneOf) {
      // In order to maintain the 'oneof' invariant, call the builder setter method rather
      // than setting the builder field directly.
      builderMethod.invoke(builder, value)
    } else {
      builderField.set(builder, value)
    }
  }

  operator fun get(message: M): Any? = messageField.get(message)

  internal fun getFromBuilder(builder: B): Any? = builderField.get(builder)
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
@file:JvmName("Internal")
@file:JvmMultifileClass

package com.squareup.wire.internal

import com.squareup.wire.ProtoAdapter

// Methods for generated code use only. Not subject to public API rules.

fun <T> redactElements(list: java.util.List<T>, adapter: ProtoAdapter<T>) {
  for (i in 0 until list.size) {
    list[i] = adapter.redact(list[i])
  }
}

fun <T> redactElements(map: java.util.Map<*, T>, adapter: ProtoAdapter<T>) {
  for (entry in map.entrySet()) {
    entry.setValue(adapter.redact(entry.value))
  }
}

/*
 * Copyright 2013 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.internal

import com.squareup.wire.FieldEncoding
import com.squareup.wire.Message
import com.squareup.wire.Message.Builder
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.ProtoReader
import com.squareup.wire.ProtoWriter
import com.squareup.wire.WireField
import java.io.IOException
import java.util.Collections
import java.util.LinkedHashMap

class RuntimeMessageAdapter<M : Message<M, B>, B : Builder<M, B>>(
  private val messageType: Class<M>,
  private val builderType: Class<B>,
  val fieldBindings: Map<Int, FieldBinding<M, B>>,
  typeUrl: String?
) : ProtoAdapter<M>(FieldEncoding.LENGTH_DELIMITED, messageType.kotlin, typeUrl) {

  fun newBuilder(): B = builderType.newInstance()

  override fun encodedSize(value: M): Int {
    val cachedSerializedSize = value.cachedSerializedSize
    if (cachedSerializedSize != 0) return cachedSerializedSize

    var size = 0
    for (fieldBinding in fieldBindings.values) {
      val binding = fieldBinding[value] ?: continue
      size += fieldBinding.adapter().encodedSizeWithTag(fieldBinding.tag, binding)
    }
    size += value.unknownFields.size

    value.cachedSerializedSize = size
    return size
  }

  @Throws(IOException::class)
  override fun encode(writer: ProtoWriter, value: M) {
    for (fieldBinding in fieldBindings.values) {
      val binding = fieldBinding[value] ?: continue
      fieldBinding.adapter().encodeWithTag(writer, fieldBinding.tag, binding)
    }
    writer.writeBytes(value.unknownFields)
  }

  override fun redact(value: M): M {
    val builder = value.newBuilder()
    for (fieldBinding in fieldBindings.values) {
      if (fieldBinding.redacted && fieldBinding.label == WireField.Label.REQUIRED) {
        throw UnsupportedOperationException(
            "Field '${fieldBinding.name}' in ${type?.javaObjectType?.name} is required and " +
                "cannot be redacted.")
      }
      val isMessage = Message::class.java
          .isAssignableFrom(fieldBinding.singleAdapter().type?.javaObjectType)
      if (fieldBinding.redacted || isMessage && !fieldBinding.label.isRepeated) {
        val builderValue = fieldBinding.getFromBuilder(builder)
        if (builderValue != null) {
          val redactedValue = fieldBinding.adapter().redact(builderValue)
          fieldBinding[builder] = redactedValue
        }
      } else if (isMessage && fieldBinding.label.isRepeated) {
        @Suppress("UNCHECKED_CAST")
        val values = fieldBinding.getFromBuilder(builder) as List<Any>
        @Suppress("UNCHECKED_CAST")
        val adapter = fieldBinding.singleAdapter() as ProtoAdapter<Any>
        fieldBinding[builder] = values.redactElements(adapter)
      }
    }
    builder.clearUnknownFields()
    return builder.build()
  }

  override fun equals(other: Any?): Boolean {
    return other is RuntimeMessageAdapter<*, *> && other.messageType == messageType
  }

  override fun hashCode(): Int = messageType.hashCode()

  override fun toString(value: M): String = buildString {
    for (fieldBinding in fieldBindings.values) {
      val binding = fieldBinding[value]
      if (binding != null) {
        append(", ")
        append(fieldBinding.name)
        append('=')
        append(if (fieldBinding.redacted) REDACTED else binding)
      }
    }

    // Replace leading comma with class name and opening brace.
    replace(0, 2, messageType.simpleName + '{')
  }

  @Throws(IOException::class)
  override fun decode(reader: ProtoReader): M {
    val builder = newBuilder()
    val token = reader.beginMessage()
    while (true) {
      val tag = reader.nextTag()
      if (tag == -1) break
      val fieldBinding = fieldBindings[tag]
      try {
        if (fieldBinding != null) {
          val adapter = if (fieldBinding.isMap) {
            fieldBinding.adapter()
          } else {
            fieldBinding.singleAdapter()
          }
          val value = adapter.decode(reader)
          fieldBinding.value(builder, value!!)
        } else {
          val fieldEncoding = reader.peekFieldEncoding()!!
          val value = fieldEncoding.rawProtoAdapter().decode(reader)
          builder.addUnknownField(tag, fieldEncoding, value)
        }
      } catch (e: EnumConstantNotFoundException) {
        // An unknown Enum value was encountered, store it as an unknown field.
        builder.addUnknownField(tag, FieldEncoding.VARINT, e.value.toLong())
      }

    }
    reader.endMessageAndGetUnknownFields(token) // Ignore return value

    return builder.build()
  }

  companion object {
    private const val REDACTED = "\u2588\u2588"

    @JvmStatic fun <M : Message<M, B>, B : Builder<M, B>> create(
      messageType: Class<M>,
      typeUrl: String?
    ): RuntimeMessageAdapter<M, B> {
      val builderType = getBuilderType(messageType)
      val fieldBindings = LinkedHashMap<Int, FieldBinding<M, B>>()

      // Create tag bindings for fields annotated with '@WireField'
      for (messageField in messageType.declaredFields) {
        val wireField = messageField.getAnnotation(WireField::class.java)
        if (wireField != null) {
          fieldBindings[wireField.tag] = FieldBinding(wireField, messageField, builderType)
        }
      }

      return RuntimeMessageAdapter(messageType, builderType,
          Collections.unmodifiableMap(fieldBindings), typeUrl)
    }

    private fun <M : Message<M, B>, B : Builder<M, B>> getBuilderType(
      messageType: Class<M>
    ): Class<B> {
      try {
        return Class.forName("${messageType.name}\$Builder") as Class<B>
      } catch (_: ClassNotFoundException) {
        throw IllegalArgumentException(
            "No builder class found for message type ${messageType.name}")
      }
    }
  }
}

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

expect object CoreLoader : Loader

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.internal.parser.MessageElement

/** An empty type which only holds nested types.  */
class EnclosingType internal constructor(
  override val location: Location,
  override val type: ProtoType,
  override val documentation: String,
  override val nestedTypes: List<Type>
) : Type() {
  override val options
    get() = Options(Options.MESSAGE_OPTIONS, listOf())

  override fun linkMembers(linker: Linker) {}
  override fun linkOptions(linker: Linker, syntaxRules: SyntaxRules) {
    nestedTypes.forEach { it.linkOptions(linker, syntaxRules) }
  }

  override fun validate(linker: Linker, syntaxRules: SyntaxRules) {
    nestedTypes.forEach { it.validate(linker, syntaxRules) }
  }

  override fun retainAll(schema: Schema, markSet: MarkSet): Type? {
    val retainedNestedTypes = nestedTypes.mapNotNull { it.retainAll(schema, markSet) }
    if (retainedNestedTypes.isEmpty()) return null
    return EnclosingType(location, type, documentation, retainedNestedTypes)
  }

  override fun retainLinked(linkedTypes: Set<ProtoType>): Type? {
    val retainedNestedTypes = nestedTypes.mapNotNull { it.retainLinked(linkedTypes) }
    if (retainedNestedTypes.isEmpty()) return null
    return EnclosingType(location, type, documentation, retainedNestedTypes)
  }

  fun toElement() = MessageElement(
      location = location,
      name = type.simpleName,
      nestedTypes = toElements(nestedTypes)
  )
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.internal.parser.EnumConstantElement

class EnumConstant private constructor(
  val location: Location,
  val name: String,
  val tag: Int,
  val documentation: String,
  val options: Options
) {
  internal fun toElement() =
    EnumConstantElement(location, name, tag, documentation, options.elements)

  internal fun linkOptions(linker: Linker) = options.link(linker)

  internal fun retainAll(
    schema: Schema,
    markSet: MarkSet
  ) = EnumConstant(location, name, tag, documentation, options.retainAll(schema, markSet))

  internal fun retainLinked() =
      EnumConstant(location, name, tag, documentation, options.retainLinked())

  companion object {
    internal fun fromElements(elements: List<EnumConstantElement>) =
      elements.map {
        EnumConstant(
            it.location, it.name, it.tag, it.documentation,
            Options(Options.ENUM_VALUE_OPTIONS, it.options)
        )
      }

    internal fun toElements(constants: List<EnumConstant>) = constants.map { it.toElement() }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.Options.Companion.ENUM_OPTIONS
import com.squareup.wire.schema.internal.parser.EnumElement
import kotlin.jvm.JvmStatic

class EnumType private constructor(
  override val type: ProtoType,
  override val location: Location,
  override val documentation: String,
  private val name: String,
  val constants: List<EnumConstant>,
  override val options: Options
) : Type() {
  private var allowAlias: Any? = null

  override val nestedTypes: List<Type>
    get() = emptyList() // Enums do not allow nested type declarations.

  fun allowAlias() = "true" == allowAlias

  /** Returns the constant named `name`, or null if this enum has no such constant.  */
  fun constant(name: String) = constants.find { it.name == name }

  /** Returns the constant tagged `tag`, or null if this enum has no such constant.  */
  fun constant(tag: Int) = constants.find { it.tag == tag }

  override fun linkMembers(linker: Linker) {}

  override fun linkOptions(linker: Linker, syntaxRules: SyntaxRules) {
    options.link(linker)
    for (constant in constants) {
      constant.linkOptions(linker)
    }
    allowAlias = options.get(ALLOW_ALIAS)
  }

  override fun validate(linker: Linker, syntaxRules: SyntaxRules) {
    val linker = linker.withContext(this)

    if ("true" != allowAlias) {
      validateTagUniqueness(linker)
    }
    if (syntaxRules.enumRequiresZeroValueAtFirstPosition()) {
      validateZeroValueAtFirstPosition(linker)
    }
  }

  private fun validateZeroValueAtFirstPosition(linker: Linker) {
    if (constants.isEmpty() || constants.first().tag != 0) {
      linker.addError("missing a zero value at the first element [proto3]")
    }
  }

  private fun validateTagUniqueness(linker: Linker) {
    val tagToConstants = linkedMapOf<Int, MutableList<EnumConstant>>()
    constants.forEach {
      tagToConstants
          .getOrPut(it.tag) { mutableListOf() }
          .add(it)
    }

    for ((tag, constants) in tagToConstants) {
      if (constants.size > 1) {
        val error = buildString {
          append("multiple enum constants share tag $tag:")
          constants.forEachIndexed { index, it ->
            append("\n  ${index + 1}. ${it.name} (${it.location})")
          }
        }
        linker.addError(error)
      }
    }
  }

  override fun retainAll(schema: Schema, markSet: MarkSet): Type? {
    // If this type is not retained, prune it.
    if (!markSet.contains(type)) return null

    val retainedConstants = constants
        .filter { markSet.contains(ProtoMember.get(type, it.name)) }
        .map { it.retainAll(schema, markSet) }

    val result = EnumType(
        type = type,
        location = location,
        documentation = documentation,
        name = name,
        constants = retainedConstants,
        options = options.retainAll(schema, markSet)
    )
    result.allowAlias = allowAlias
    return result
  }

  override fun retainLinked(linkedTypes: Set<ProtoType>): Type? {
    if (!linkedTypes.contains(type)) {
      return null
    }

    val retainedConstants = constants.map { it.retainLinked() }

    return EnumType(
        type = type,
        location = location,
        documentation = documentation,
        name = name,
        constants = retainedConstants,
        options = options.retainLinked()
    )
  }

  fun toElement(): EnumElement {
    return EnumElement(
        location = location,
        name = name,
        documentation = documentation,
        options = options.elements,
        constants = EnumConstant.toElements(constants)
    )
  }

  companion object {
    internal val ALLOW_ALIAS = ProtoMember.get(ENUM_OPTIONS, "allow_alias")

    @JvmStatic
    fun fromElement(
      protoType: ProtoType,
      enumElement: EnumElement
    ): EnumType {

      return EnumType(
          type = protoType,
          location = enumElement.location,
          documentation = enumElement.documentation,
          name = enumElement.name,
          constants = EnumConstant.fromElements(enumElement.constants),
          options = Options(ENUM_OPTIONS, enumElement.options)
      )
    }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.Field.Companion.retainAll
import com.squareup.wire.schema.Options.Companion.GOOGLE_PROTOBUF_OPTION_TYPES
import com.squareup.wire.schema.internal.parser.ExtendElement
import kotlin.jvm.JvmStatic

class Extend private constructor(
  val location: Location,
  val documentation: String,
  val name: String,
  val fields: List<Field>
) {
  // Null until this extend is linked.
  var type: ProtoType? = null
    private set

  fun link(linker: Linker) {
    val linker = linker.withContext(this)
    type = linker.resolveMessageType(name)
    val type = linker[type!!]
    if (type != null) {
      (type as MessageType).addExtensionFields(fields)
    }
  }

  fun validate(linker: Linker, syntaxRules: SyntaxRules) {
    val linker = linker.withContext(this)
    linker.validateImport(location, type!!)

    if (!syntaxRules.canExtend(ProtoType.get(name))) {
      linker.addError("extensions are not allowed [proto3]")
    }
  }

  fun retainAll(schema: Schema, markSet: MarkSet): Extend? {
    val retainedFields = retainAll(schema, markSet, type!!, fields)
    if (retainedFields.isEmpty()) return null
    return Extend(location, documentation, name, retainedFields)
  }

  companion object {
    @JvmStatic
    fun fromElements(
      packageName: String?,
      extendElements: List<ExtendElement>
    ) = extendElements.map {
      Extend(
          location = it.location,
          documentation = it.documentation,
          name = it.name,
          fields = Field.fromElements(packageName, it.fields, true)
      )
    }

    @JvmStatic
    fun toElements(extendList: List<Extend>) = extendList.map {
      ExtendElement(
          location = it.location,
          name = it.name,
          documentation = it.documentation,
          fields = Field.toElements(it.fields)
      )
    }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.internal.isValidTag
import com.squareup.wire.schema.internal.parser.ExtensionsElement
import kotlin.jvm.JvmStatic

class Extensions private constructor(
  val location: Location,
  val documentation: String,
  val values: List<Any>
) {
  fun validate(linker: Linker) {
    val linker = linker.withContext(this)
    val outOfRangeTags = mutableListOf<String>()
    values.forEach { value ->
      when (value) {
        is Int -> {
          if (!value.isValidTag()) {
            outOfRangeTags.add("$value")
          }
        }
        is IntRange -> {
          if (!value.first.isValidTag() || !value.last.isValidTag()) {
            outOfRangeTags.add("${value.first} to ${value.last}")
          }
        }
        else -> throw AssertionError()
      }
    }
    if (outOfRangeTags.isNotEmpty()) {
      linker.addError("tags are out of range: ${outOfRangeTags.joinToString(separator = ", ")}")
    }
  }

  companion object {
    @JvmStatic
    fun fromElements(elements: List<ExtensionsElement>) =
        elements.map { Extensions(it.location, it.documentation, it.values) }

    @JvmStatic
    fun toElements(extensions: List<Extensions>) =
        extensions.map { ExtensionsElement(it.location, it.documentation, it.values) }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.Options.Companion.FIELD_OPTIONS
import com.squareup.wire.schema.Options.Companion.GOOGLE_PROTOBUF_OPTION_TYPES
import com.squareup.wire.schema.internal.parser.FieldElement
import com.squareup.wire.schema.internal.parser.OptionElement.Companion.PACKED_OPTION_ELEMENT
import kotlin.jvm.JvmStatic

class Field private constructor(
  val packageName: String?,

  val location: Location,

  /** May be null for proto3 fields. */
  val label: Label?,

  val name: String,

  val documentation: String,

  val tag: Int,

  val default: String?,

  private val elementType: String,

  val options: Options,

  val isExtension: Boolean
) {
  // Null until this field is linked.
  var type: ProtoType? = null
    private set

  // Null until this field is linked.
  private var deprecated: Any? = null

  // Null until this field is linked.
  private var packed: Any? = null

  // Null until this field is linked.
  var isRedacted: Boolean = false
    private set

  val isRepeated: Boolean
    get() = label == Label.REPEATED

  val isOptional: Boolean
    get() = label == Label.OPTIONAL

  val isRequired: Boolean
    get() = label == Label.REQUIRED

  /**
   * Returns this field's name, prefixed with its package name. Uniquely identifies extension
   * fields, such as in options.
   */
  val qualifiedName: String
    get() {
      return when {
        packageName != null -> "$packageName.$name"
        else -> name
      }
    }

  val isDeprecated: Boolean
    get() = "true" == deprecated

  val isPacked: Boolean
    get() = "true" == packed

  private fun isPackable(linker: Linker, type: ProtoType): Boolean {
    return type != ProtoType.STRING &&
        type != ProtoType.BYTES &&
        linker.get(type) !is MessageType
  }

  fun link(linker: Linker) {
    type = linker.withContext(this).resolveType(elementType)
  }

  fun linkOptions(linker: Linker, syntaxRules: SyntaxRules) {
    val linker = linker.withContext(this)
    options.link(linker)
    deprecated = options.get(DEPRECATED)
    packed = options.get(PACKED)
        ?: if (syntaxRules.isPackedByDefault(type!!, label)) PACKED_OPTION_ELEMENT.value else null
    // We allow any package name to be used as long as it ends with '.redacted'.
    isRedacted = options.optionMatches(".*\\.redacted", "true")
  }

  fun validate(linker: Linker, syntaxRules: SyntaxRules) {
    val linker = linker.withContext(this)
    if (isPacked && !isPackable(linker, type!!)) {
      linker.addError("packed=true not permitted on $type")
    }
    if (isExtension && isRequired) {
      linker.addError("extension fields cannot be required")
    }
    if (default != null && !syntaxRules.allowUserDefinedDefaultValue()) {
      linker.addError("user-defined default values are not permitted [proto3]")
    }
    linker.validateImport(location, type!!)
  }

  fun retainAll(schema: Schema, markSet: MarkSet, enclosingType: ProtoType): Field? {
    if (!isUsedAsOption(schema, markSet, enclosingType)) {
      // If the type is null this field was never linked. Prune it.
      // TODO(jwilson): perform this transformation in the Linker.
      val type = type ?: return null

      // For map types only the value can participate in pruning as the key will always be scalar.
      if (type.isMap && type.valueType!! !in markSet) return null

      if (!markSet.contains(type)) return null
    }

    val memberName = if (isExtension) qualifiedName else name
    val protoMember = ProtoMember.get(enclosingType, memberName)

    if (!markSet.contains(protoMember) &&
        !(GOOGLE_PROTOBUF_OPTION_TYPES.contains(enclosingType) && !isExtension)) return null

    return withOptions(options.retainAll(schema, markSet))
  }

  /** Returns a copy of this whose options is [options].  */
  private fun withOptions(options: Options): Field {
    val result = Field(
        packageName = packageName,
        location = location,
        label = label,
        name = name,
        documentation = documentation,
        tag = tag,
        default = default,
        elementType = elementType,
        options = options,
        isExtension = isExtension
    )
    result.type = type
    result.deprecated = deprecated
    result.packed = packed
    result.isRedacted = isRedacted
    return result
  }

  private fun isUsedAsOption(schema: Schema, markSet: MarkSet, enclosingType: ProtoType): Boolean {
    for (protoFile in schema.protoFiles) {
      if (protoFile.types.any { isUsedAsOption(markSet, enclosingType, it) }) return true
      if (protoFile.services.any { isUsedAsOption(markSet, enclosingType, it) }) return true
    }
    return false
  }

  private fun isUsedAsOption(
    markSet: MarkSet,
    enclosingType: ProtoType,
    service: Service
  ): Boolean {
    if (service.type() !in markSet) return false

    val protoMember = ProtoMember.get(enclosingType, if (isExtension) qualifiedName else name)
    if (service.options().assignsMember(protoMember)) return true
    if (service.rpcs().any { it.options.assignsMember(protoMember) }) return true

    return false
  }

  private fun isUsedAsOption(markSet: MarkSet, enclosingType: ProtoType, type: Type): Boolean {
    if (type.type !in markSet) return false

    val protoMember = ProtoMember.get(enclosingType, if (isExtension) qualifiedName else name)

    when (type) {
      is MessageType -> {
        if (type.options.assignsMember(protoMember)) return true
        if (type.fields().any { it.options.assignsMember(protoMember) }) return true
      }
      is EnumType -> {
        if (type.options.assignsMember(protoMember)) return true
        if (type.constants.any { it.options.assignsMember(protoMember) }) return true
      }
    }

    if (type.nestedTypes.any { isUsedAsOption(markSet, enclosingType, it) }) return true

    return false
  }

  override fun toString() = name

  enum class Label {
    OPTIONAL,
    REQUIRED,
    REPEATED,
    /** Indicates the field is a member of a `oneof` block.  */
    ONE_OF
  }

  companion object {
    internal val DEPRECATED = ProtoMember.get(FIELD_OPTIONS, "deprecated")
    internal val PACKED = ProtoMember.get(FIELD_OPTIONS, "packed")

    @JvmStatic
    fun fromElements(
      packageName: String?,
      fieldElements: List<FieldElement>,
      extension: Boolean
    ) = fieldElements.map {
      Field(
          packageName = packageName,
          location = it.location,
          label = it.label,
          name = it.name,
          documentation = it.documentation,
          tag = it.tag,
          default = it.defaultValue,
          elementType = it.type,
          options = Options(FIELD_OPTIONS, it.options),
          isExtension = extension
      )
    }

    @JvmStatic
    fun toElements(fields: List<Field>) = fields.map {
      FieldElement(
          location = it.location,
          label = it.label,
          type = it.elementType,
          name = it.name,
          defaultValue = it.default,
          tag = it.tag,
          documentation = it.documentation,
          options = it.options.elements
      )
    }

    @JvmStatic
    fun retainLinked(fields: List<Field>) = fields
        .filter { it.type != null } // If the type is non-null, then the field has been linked.
        .map { it.withOptions(it.options.retainLinked()) }

    @JvmStatic
    fun retainAll(
      schema: Schema,
      markSet: MarkSet,
      enclosingType: ProtoType,
      fields: Collection<Field>
    ) = fields.mapNotNull { it.retainAll(schema, markSet, enclosingType) }
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

internal class FileLinker(
  val protoFile: ProtoFile,
  private val linker: Linker
) {
  /** Lazily computed set of files used to reference other types and options. */
  private var effectiveImports: Set<String>? = null
  /** True once this linker has registered its types with the enclosing linker. */
  private var typesRegistered = false
  private var extensionsLinked = false
  private var importedExtensionsRegistered = false
  private var fileOptionsLinked = false
  /** The set of types defined in this file whose members have been linked. */
  private val typesWithMembersLinked: MutableSet<ProtoType> = LinkedHashSet()

  /**
   * Returns all effective imports. This is computed on-demand by unioning all direct imports plus
   * the recursive set of all public imports.
   */
  fun effectiveImports(): Set<String> {
    if (effectiveImports == null) {
      val sink: MutableSet<String> = LinkedHashSet()
      addImportsRecursive(sink, protoFile.imports)
      addImportsRecursive(sink, protoFile.publicImports)
      effectiveImports = LinkedHashSet(sink)
    }
    return effectiveImports!!
  }

  private fun addImportsRecursive(
    sink: MutableSet<String>,
    paths: Collection<String>
  ) {
    for (path in paths) {
      if (sink.add(path)) {
        val fileLinker = linker.getFileLinker(path)
        addImportsRecursive(sink, fileLinker.protoFile.publicImports)
      }
    }
  }

  fun requireTypesRegistered() {
    if (typesRegistered) return
    typesRegistered = true

    for (type in protoFile.types) {
      addTypes(type)
    }
  }

  private fun addTypes(type: Type) {
    linker.addType(type.type, type)
    for (nestedType in type.nestedTypes) {
      addTypes(nestedType)
    }
  }

  fun requireExtensionsLinked() {
    if (extensionsLinked) return
    extensionsLinked = true

    requireTypesRegistered()
    for (extend in protoFile.extendList) {
      extend.link(linker)
    }
  }

  /**
   * This file might use extensions defined on one of the files we import. Make sure those
   * extensions are registered before we try to use our extensions.
   */
  fun requireImportedExtensionsRegistered() {
    if (importedExtensionsRegistered) return
    importedExtensionsRegistered = true

    for (importedFileLinker in linker.contextImportedTypes()) {
      importedFileLinker.requireExtensionsLinked()
    }
  }

  fun linkMembers() {
    linkMembersRecursive(protoFile.types)
    for (service in protoFile.services) {
      service.link(linker)
    }
  }

  /** Link the members of [types] and their nested types. */
  private fun linkMembersRecursive(types: List<Type>) {
    for (type in types) {
      requireMembersLinked(type)
      linkMembersRecursive(type.nestedTypes)
    }
  }

  /** Link the members of [type] that haven't been linked already. */
  fun requireMembersLinked(type: Type) {
    if (typesWithMembersLinked.add(type.type)) {
      type.linkMembers(linker)
    }
  }

  /**
   * This requires traversal of members of imported types! This may potentially include non-direct
   * dependencies!
   */
  fun linkOptions(syntaxRules: SyntaxRules) {
    requireFileOptionsLinked()
    for (type in protoFile.types) {
      type.linkOptions(linker, syntaxRules)
    }
    for (service in protoFile.services) {
      service.linkOptions(linker)
    }
  }

  fun requireFileOptionsLinked() {
    if (fileOptionsLinked) return
    fileOptionsLinked = true

    protoFile.linkOptions(linker)
  }

  fun validate(syntaxRules: SyntaxRules) {
    protoFile.validate(linker)

    for (type in protoFile.types) {
      type.validate(linker, syntaxRules)
    }
    for (service in protoFile.services) {
      service.validate(linker)
    }
    for (extend in protoFile.extendList) {
      extend.validate(linker, syntaxRules)
    }
  }
}

package com.squareup.wire.schema

import com.squareup.wire.schema.internal.parser.OptionElement

data class LinkedOptionEntry(
  val optionElement: OptionElement,
  val protoMember: ProtoMember,
  val value: Any?
)

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.ProtoType.Companion.get
import com.squareup.wire.schema.internal.MutableQueue
import com.squareup.wire.schema.internal.isValidTag
import com.squareup.wire.schema.internal.mutableQueueOf

/** Links local field types and option types to the corresponding declarations. */
class Linker {
  private val loader: Loader
  private val fileLinkers: MutableMap<String, FileLinker>
  private val fileOptionsQueue: MutableQueue<FileLinker>
  private val protoTypeNames: MutableMap<String, Type>
  private val errors: MutableList<String>
  private val contextStack: List<Any>
  private val requestedTypes: MutableSet<ProtoType?>

  internal constructor(loader: Loader) {
    this.loader = loader
    fileLinkers = mutableMapOf()
    fileOptionsQueue = mutableQueueOf()
    protoTypeNames = mutableMapOf()
    contextStack = emptyList()
    errors = mutableListOf()
    requestedTypes = mutableSetOf()
  }

  private constructor(enclosing: Linker, additionalContext: Any) {
    loader = enclosing.loader
    fileLinkers = enclosing.fileLinkers
    fileOptionsQueue = enclosing.fileOptionsQueue
    protoTypeNames = enclosing.protoTypeNames
    contextStack = enclosing.contextStack + additionalContext
    errors = enclosing.errors
    requestedTypes = enclosing.requestedTypes
  }

  /** Returns a linker for [path], loading the file if necessary. */
  internal fun getFileLinker(path: String): FileLinker {
    val existing = fileLinkers[path]
    if (existing != null) return existing

    val protoFile = loader.load(path)
    val result = FileLinker(protoFile, withContext(protoFile))
    fileLinkers[path] = result
    fileOptionsQueue += result
    return result
  }

  /**
   * Link all features of all files in [sourceProtoFiles] to create a schema. This will also
   * partially link any imported files necessary.
   */
  fun link(sourceProtoFiles: Iterable<ProtoFile>): Schema {
    val sourceFiles: List<FileLinker> = sourceProtoFiles.map { sourceFile ->
      FileLinker(sourceFile, withContext(sourceFile))
          .also { fileLinker ->
            fileLinkers[sourceFile.location.path] = fileLinker
          }
    }

    for (fileLinker in sourceFiles) {
      fileLinker.requireTypesRegistered()
    }

    // Also link descriptor.proto's types, which are necessary for options.
    val descriptorProto = getFileLinker("google/protobuf/descriptor.proto")
    descriptorProto.requireTypesRegistered()

    for (fileLinker in sourceFiles) {
      fileLinker.requireExtensionsLinked()
    }

    for (fileLinker in sourceFiles) {
      fileLinker.requireImportedExtensionsRegistered()
    }

    for (fileLinker in sourceFiles) {
      fileLinker.linkMembers()
    }

    for (fileLinker in sourceFiles) {
      // TODO(benoit) Create a map to cache those.
      val syntaxRules = SyntaxRules.get(fileLinker.protoFile.syntax)
      fileLinker.linkOptions(syntaxRules)
    }

    // For compactness we'd prefer to link the options of source files only. But we link file
    // options on referenced files to make sure that java_package is populated.
    while (fileOptionsQueue.isNotEmpty()) {
      val fileLinker = fileOptionsQueue.poll()!!
      fileLinker.requireFileOptionsLinked()
    }

    for (fileLinker in sourceFiles) {
      val syntaxRules = SyntaxRules.get(fileLinker.protoFile.syntax)
      fileLinker.validate(syntaxRules)
    }

    if (errors.isNotEmpty()) {
      throw SchemaException(errors)
    }

    val result = mutableListOf<ProtoFile>()
    for (fileLinker in fileLinkers.values) {
      if (sourceFiles.contains(fileLinker)) {
        result.add(fileLinker.protoFile)
        continue
      }

      // Retain this type if it's used by anything in the source path.
      val anyTypeIsUsed = fileLinker.protoFile.typesAndNestedTypes()
          .any { type ->
            requestedTypes.contains(type.type)
          }
      if (anyTypeIsUsed) {
        result.add(fileLinker.protoFile.retainLinked(requestedTypes as Set<ProtoType>))
      }
    }

    return Schema(result)
  }

  /** Returns the type name for the scalar, relative or fully-qualified name [name]. */
  fun resolveType(name: String): ProtoType {
    return resolveType(name, false)
  }

  /** Returns the type name for the relative or fully-qualified name [name]. */
  fun resolveMessageType(name: String): ProtoType {
    return resolveType(name, true)
  }

  private fun resolveType(name: String, messageOnly: Boolean): ProtoType {
    val type = get(name)

    if (type.isScalar) {
      if (messageOnly) {
        addError("expected a message but was $name")
      }
      return type
    }

    if (type.isMap) {
      if (messageOnly) {
        addError("expected a message but was $name")
      }
      val keyType = resolveType(type.keyType.toString(), false)
      val valueType = resolveType(type.valueType.toString(), false)
      return get(keyType, valueType, name)
    }

    var resolved: Type? = resolve(name, protoTypeNames)
    // If no type could be resolved, load imported files and try again.
    if (resolved == null) {
      for (fileLinker in contextImportedTypes()) {
        fileLinker.requireTypesRegistered()
      }
      resolved = resolve(name, protoTypeNames)
    }

    if (resolved == null) {
      addError("unable to resolve $name")
      return ProtoType.BYTES // Just return any placeholder.
    }

    if (messageOnly && resolved !is MessageType) {
      addError("expected a message but was $name")
      return ProtoType.BYTES // Just return any placeholder.
    }

    requestedTypes.add(resolved.type)

    return resolved.type
  }

  fun <T> resolve(name: String, map: Map<String, T>): T? {
    if (name.startsWith(".")) {
      // If name starts with a '.', the rest of it is fully qualified.
      val result = map[name.substring(1)]
      if (result != null) return result
    } else {
      // We've got a name suffix, like 'Person' or 'protos.Person'. Start the search from with the
      // longest prefix like foo.bar.Baz.Quux, shortening the prefix until we find a match.
      var prefix = resolveContext()
      while (prefix.isNotEmpty()) {
        val result = map["$prefix.$name"]
        if (result != null) return result

        // Strip the last nested class name or package name from the end and try again.
        val dot = prefix.lastIndexOf('.')
        prefix = if (dot != -1) prefix.substring(0, dot) else ""
      }
      val result = map[name]
      if (result != null) return result
    }
    return null
  }

  private fun resolveContext(): String {
    for (i in contextStack.indices.reversed()) {
      val context = contextStack[i]
      when {
        context is Type -> {
          return context.type.toString()
        }
        context is ProtoFile -> {
          val packageName = context.packageName
          return packageName ?: ""
        }
        context is Field && context.isExtension -> {
          return context.packageName ?: ""
        }
      }
    }
    throw IllegalStateException()
  }

  /** Returns the current package name from the context stack. */
  fun packageName(): String? {
    for (context in contextStack) {
      if (context is ProtoFile) return context.packageName
    }
    return null
  }

  /**
   * Returns the files imported in the current context. These files declare the types that may be
   * resolved.
   */
  internal fun contextImportedTypes(): List<FileLinker> {
    val result = mutableListOf<FileLinker>()
    for (i in contextStack.indices.reversed()) {
      val context = contextStack[i]

      val location = when {
        context is ProtoFile -> context.location
        context is Field && context.isExtension -> context.location
        else -> null
      }

      if (location != null) {
        val path = location.path
        val fileLinker = getFileLinker(path)
        for (effectiveImport in fileLinker.effectiveImports()) {
          result.add(getFileLinker(effectiveImport))
        }
      }
    }
    return result
  }

  /** Adds [type]. */
  fun addType(protoType: ProtoType, type: Type) {
    protoTypeNames[protoType.toString()] = type
  }

  /** Returns the type or null if it doesn't exist. */
  operator fun get(protoType: ProtoType): Type? {
    var result = protoTypeNames[protoType.toString()]

    // If no type could be resolved, load imported files and try again.
    if (result == null) {
      for (fileLinker in contextImportedTypes()) {
        fileLinker.requireTypesRegistered()
      }
      result = protoTypeNames[protoType.toString()]
    }

    if (result != null) {
      requestedTypes.add(protoType)
    }

    return result
  }

  /**
   * Returns the type or null if it doesn't exist. Before this returns it ensures members are linked
   * so that options may dereference them.
   */
  fun getForOptions(protoType: ProtoType): Type? {
    val result = get(protoType) ?: return null

    val fileLinker = getFileLinker(result.location.path)
    fileLinker.requireMembersLinked(result)
    return result
  }

  /** Returns the field named [field] on the message type of [self]. */
  fun dereference(
    self: Field,
    field: String
  ): Field? {
    @Suppress("NAME_SHADOWING") var field = field
    if (field.startsWith("[") && field.endsWith("]")) {
      field = field.substring(1, field.length - 1)
    }

    val type = getForOptions(self.type!!)
    if (type is MessageType) {
      val messageField = type.field(field)
      if (messageField != null) return messageField

      val typeExtensions = type.extensionFieldsMap()
      val extensionField = resolve(field, typeExtensions)
      if (extensionField != null) return extensionField
    }

    return null // Unable to traverse this field path.
  }

  /** Validate that the tags of [fields] are unique and in range. */
  fun validateFields(
    fields: Iterable<Field>,
    reserveds: List<Reserved>
  ) {
    val tagToField = linkedMapOf<Int, MutableSet<Field>>()
    val nameToField = linkedMapOf<String, MutableSet<Field>>()

    for (field in fields) {
      val tag = field.tag
      if (!tag.isValidTag()) {
        withContext(field).addError("tag is out of range: $tag")
      }

      for (reserved in reserveds) {
        if (reserved.matchesTag(tag)) {
          withContext(field).addError("tag $tag is reserved (${reserved.location})")
        }
        if (reserved.matchesName(field.name)) {
          withContext(field).addError("name '${field.name}' is reserved (${reserved.location})")
        }
      }

      tagToField.getOrPut(tag, { mutableSetOf() }).also { it += field }
      nameToField.getOrPut(field.qualifiedName, { mutableSetOf() }).also { it += field }
    }

    for ((key, values) in tagToField) {
      if (values.size > 1) {
        val error = StringBuilder()
        error.append("multiple fields share tag $key:")
        values.forEachIndexed { index, field ->
          error.append("\n  ${index + 1}. ${field.name} (${field.location})")
        }
        addError(error.toString())
      }
    }

    for (collidingFields in nameToField.values) {
      if (collidingFields.size > 1) {
        val first = collidingFields.iterator().next()
        val error = StringBuilder()
        error.append("multiple fields share name ${first.name}:")
        collidingFields.forEachIndexed { index, field ->
          error.append("\n  ${index + 1}. ${field.name} (${field.location})")
        }
        addError(error.toString())
      }
    }
  }

  fun validateEnumConstantNameUniqueness(nestedTypes: Iterable<Type>) {
    val nameToType = mutableMapOf<String, MutableSet<EnumType>>()
    for (type in nestedTypes) {
      if (type is EnumType) {
        for (enumConstant in type.constants) {
          nameToType.getOrPut(enumConstant.name, { mutableSetOf() }).also { it += type }
        }
      }
    }

    for ((constant, values) in nameToType) {
      if (values.size > 1) {
        val error = buildString {
          append("multiple enums share constant $constant:")
          values.forEachIndexed { index, enumType ->
            append("\n  ${index + 1}. ${enumType.type}.$constant " +
                "(${enumType.constant(constant)!!.location})")
          }
        }
        addError(error)
      }
    }
  }

  fun validateImport(
    location: Location,
    type: ProtoType
  ) {
    @Suppress("NAME_SHADOWING") var type = type

    // Map key type is always scalar. No need to validate it.
    if (type.isMap) type = type.valueType!!

    if (type.isScalar) return

    val path = location.path
    val requiredImport = get(type)!!.location.path
    val fileLinker = getFileLinker(path)
    if (path != requiredImport && !fileLinker.effectiveImports().contains(requiredImport)) {
      addError("$path needs to import $requiredImport")
    }
  }

  /** Returns a new linker that uses [context] to resolve type names and report errors. */
  fun withContext(context: Any): Linker {
    return Linker(this, context)
  }

  fun addError(message: String) {
    val error = StringBuilder()
    error.append(message)

    for (i in contextStack.indices.reversed()) {
      val context = contextStack[i]
      val prefix = if (i == contextStack.size - 1) "\n  for" else "\n  in"

      when (context) {
        is Rpc -> {
          error.append("$prefix rpc ${context.name} (${context.location})")
        }

        is Extend -> {
          val type = context.type
          error.append(
              if (type != null) "$prefix extend $type (${context.location})"
              else "$prefix extend (${context.location})")
        }

        is Field -> {
          error.append("$prefix field ${context.name} (${context.location})")
        }

        is MessageType -> {
          error.append("$prefix message ${context.type} (${context.location})")
        }

        is EnumType -> {
          error.append("$prefix enum ${context.type} (${context.location})")
        }

        is Service -> {
          error.append("$prefix service ${context.type()} (${context.location()})")
        }

        is Extensions -> {
          error.append("$prefix extensions (${context.location})")
        }
      }
    }
    errors.add(error.toString())
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

/** Loads other files as needed by their import path. */
interface Loader {
  fun load(path: String): ProtoFile
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import kotlin.jvm.JvmStatic

/**
 * Locates a .proto file, or a position within a .proto file, on the file system. This includes a
 * base directory or a .jar file, and a path relative to that base.
 */
data class Location(
  /** The base of this location; typically a directory or .jar file.  */
  val base: String,

  /** The path to this location relative to [base].  */
  val path: String,

  /** The line number of this location, or -1 for no specific line number.  */
  val line: Int = -1,

  /** The column on the line of this location, or -1 for no specific column.  */
  val column: Int = -1
) {

  fun at(line: Int, column: Int) = Location(base, path, line, column)

  /** Returns a copy of this location with an empty base.  */
  fun withoutBase() = Location("", path, line, column)

  /** Returns a copy of this location including only its path.  */
  fun withPathOnly() = Location("", path, -1, -1)

  override fun toString() = buildString {
    if (base.isNotEmpty()) {
      append(base)
      append('/')
    }
    append(path)
    if (line != -1) {
      append(" at $line")
      if (column != -1) {
        append(":$column")
      }
    }
  }

  companion object {
    @JvmStatic
    fun get(path: String): Location {
      return get("", path)
    }

    @JvmStatic
    fun get(
      base: String,
      path: String
    ): Location {
      return Location(base.trimEnd('/'), path, -1, -1)
    }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

/**
 * A mark set is used in three phases:
 *
 *  1. Marking root types and root members. These are the identifiers specifically identified by
 *     the user in the includes set. In this phase it is an error to mark a type that is excluded,
 *     or to mark both a type and one of its members.
 *  2. Marking members transitively reachable by those roots. In this phase if a member is visited,
 *     the member's enclosing type is marked instead, unless it is of a type that has a specific
 *     member already marked.
 *  3. Retaining which members and types have been marked.
 */
class MarkSet(
  val pruningRules: PruningRules
) {
  /** The types to retain. We may retain a type but not all of its members. */
  val types = mutableSetOf<ProtoType>()

  /** The members to retain. Any member not in here should be pruned! */
  val members = mutableMapOf<ProtoType, MutableSet<ProtoMember>>()
  /** The root members which are never to be pruned, including their referenced type. */
  private val rootMemberTypes = mutableMapOf<ProtoMember, ProtoType>()
  /** The members this MarkSet have seen. The values should be non-null once the marking is done. */
  private val memberTypes = mutableMapOf<ProtoMember, ProtoType>()

  /**
   * Marks `protoMember`, throwing if it is explicitly excluded. This implicitly excludes other
   * members of the same type.
   */
  fun root(protoMember: ProtoMember) {
    check(!pruningRules.prunes(protoMember))
    types.add(protoMember.type)
    rootMemberTypes[protoMember] = UNKNOWN_TYPE
    val memberSet = members.getOrPut(protoMember.type, { mutableSetOf() })
    memberSet += protoMember
  }

  /** Marks `type`, throwing if it is explicitly excluded. */
  fun root(type: ProtoType) {
    check(!pruningRules.prunes(type))
    types.add(type)
  }

  /**
   * Marks a type as transitively reachable by the includes set. Returns true if the mark is new,
   * the type will be retained, and reachable objects should be traversed.
   *
   * If there is an exclude for [type], non-root members referencing it will be pruned. The type
   * itself will also be pruned unless it is referenced by a root member.
   */
  fun mark(type: ProtoType, reference: ProtoMember): Boolean {
    memberTypes[reference] = type

    if (rootMemberTypes.containsKey(reference)) {
      rootMemberTypes[reference] = type
      // We keep.
      return types.add(type)
    }

    return mark(type)
  }

  /**
   * Marks a type as transitively reachable by the includes set. Returns true if the mark is new,
   * the type will be retained, and reachable objects should be traversed.
   */
  fun mark(type: ProtoType): Boolean {
    if (pruningRules.prunes(type)) return false
    return types.add(type)
  }

  /**
   * Marks a member as transitively reachable by the includes set. Returns true if the mark is new,
   * the member will be retained, and reachable objects should be traversed.
   */
  fun mark(protoMember: ProtoMember): Boolean {
    if (pruningRules.prunes(protoMember)) return false
    types.add(protoMember.type)
    val memberSet = members.getOrPut(protoMember.type, { mutableSetOf() })
    return memberSet.add(protoMember)
  }

  /** Returns true if `type` is marked and should be retained. */
  operator fun contains(type: ProtoType): Boolean {
    return types.contains(type)
  }

  /** Returns true if `member` is marked and should be retained. */
  operator fun contains(protoMember: ProtoMember): Boolean {
    val memberType: ProtoType? = memberTypes[protoMember]

    // We do not contain non-root members whose referenced type is excluded.
    if (!rootMemberTypes.containsKey(protoMember) &&
        memberType != null && pruningRules.prunes(memberType)) {
      return false
    }

    // A member cannot be included if its referencing type is excluded unless a root member of this
    // referenced type exists.
    if (pruningRules.prunes(protoMember.type) &&
        rootMemberTypes.containsValue(protoMember.type)) {
      return true
    }

    if (pruningRules.prunes(protoMember)) return false
    val memberSet = members[protoMember.type]
    return memberSet != null && memberSet.contains(protoMember)
  }

  companion object {
    private val UNKNOWN_TYPE = ProtoType.get("(unknown type)")
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.Extensions.Companion.fromElements
import com.squareup.wire.schema.Extensions.Companion.toElements
import com.squareup.wire.schema.Field.Companion.fromElements
import com.squareup.wire.schema.Field.Companion.retainAll
import com.squareup.wire.schema.Field.Companion.retainLinked
import com.squareup.wire.schema.Field.Companion.toElements
import com.squareup.wire.schema.OneOf.Companion.fromElements
import com.squareup.wire.schema.OneOf.Companion.toElements
import com.squareup.wire.schema.Reserved.Companion.fromElements
import com.squareup.wire.schema.Reserved.Companion.toElements
import com.squareup.wire.schema.internal.parser.MessageElement
import kotlin.jvm.JvmStatic

class MessageType private constructor(
  override val type: ProtoType,
  override val location: Location,
  override val documentation: String,
  private val name: String,
  val declaredFields: List<Field>,
  val extensionFields: MutableList<Field>,
  val oneOfs: List<OneOf>,
  override val nestedTypes: List<Type>,
  private val extensionsList: List<Extensions>,
  private val reserveds: List<Reserved>,
  override val options: Options
) : Type() {
  fun fields() = declaredFields + extensionFields

  val requiredFields: List<Field>
    get() = fieldsAndOneOfFields.filter { it.isRequired }

  val fieldsAndOneOfFields: List<Field>
    get() = declaredFields + extensionFields + oneOfs.flatMap { it.fields }

  /** Returns the field named [name], or null if this type has no such field. */
  fun field(name: String): Field? {
    for (field in declaredFields) {
      if (field.name == name) {
        return field
      }
    }
    for (oneOf in oneOfs) {
      for (field in oneOf.fields) {
        if (field.name == name) {
          return field
        }
      }
    }
    return null
  }

  /**
   * Returns the field with the qualified name [qualifiedName], or null if this type has no
   * such field.
   */
  fun extensionField(qualifiedName: String): Field? =
      extensionFields.firstOrNull { it.qualifiedName == qualifiedName }

  /** Returns the field tagged [tag], or null if this type has no such field.  */
  fun field(tag: Int): Field? {
    for (field in declaredFields) {
      if (field.tag == tag) {
        return field
      }
    }
    for (field in extensionFields) {
      if (field.tag == tag) {
        return field
      }
    }
    return null
  }

  fun extensionFieldsMap(): Map<String, Field> {
    // TODO(jwilson): simplify this to just resolve field values directly.
    val extensionsForType = mutableMapOf<String, Field>()
    for (field in extensionFields) {
      extensionsForType[field.qualifiedName] = field
    }
    return extensionsForType
  }

  fun addExtensionFields(fields: List<Field>) {
    extensionFields.addAll(fields)
  }

  override fun linkMembers(linker: Linker) {
    val linker = linker.withContext(this)
    for (field in declaredFields) {
      field.link(linker)
    }
    for (field in extensionFields) {
      field.link(linker)
    }
    for (oneOf in oneOfs) {
      oneOf.link(linker)
    }
  }

  override fun linkOptions(linker: Linker, syntaxRules: SyntaxRules) {
    val linker = linker.withContext(this)
    for (nestedType in nestedTypes) {
      nestedType.linkOptions(linker, syntaxRules)
    }
    for (field in declaredFields) {
      field.linkOptions(linker, syntaxRules)
    }
    for (field in extensionFields) {
      field.linkOptions(linker, syntaxRules)
    }
    for (oneOf in oneOfs) {
      oneOf.linkOptions(linker, syntaxRules)
    }
    options.link(linker)
  }

  override fun validate(linker: Linker, syntaxRules: SyntaxRules) {
    val linker = linker.withContext(this)
    linker.validateFields(fieldsAndOneOfFields, reserveds)
    linker.validateEnumConstantNameUniqueness(nestedTypes)
    for (field in fieldsAndOneOfFields) {
      field.validate(linker, syntaxRules)
    }
    for (nestedType in nestedTypes) {
      nestedType.validate(linker, syntaxRules)
    }
    for (extensions in extensionsList) {
      extensions.validate(linker)
    }
  }

  override fun retainAll(
    schema: Schema,
    markSet: MarkSet
  ): Type? {
    val retainedNestedTypes = nestedTypes.mapNotNull { it.retainAll(schema, markSet) }
    if (!markSet.contains(type) && !Options.GOOGLE_PROTOBUF_OPTION_TYPES.contains(type)) {
      return when {
        // This type is not retained, and none of its nested types are retained, prune it.
        retainedNestedTypes.isEmpty() -> null
        // This type is not retained but retained nested types, replace it with an enclosing type.
        else -> EnclosingType(location, type, documentation, retainedNestedTypes)
      }
    }

    val retainedOneOfs = oneOfs.mapNotNull { it.retainAll(schema, markSet, type) }

    return MessageType(
        type = type,
        location = location,
        documentation = documentation,
        name = name,
        declaredFields = retainAll(schema, markSet, type, declaredFields),
        extensionFields = retainAll(schema, markSet, type, extensionFields).toMutableList(),
        oneOfs = retainedOneOfs,
        nestedTypes = retainedNestedTypes,
        extensionsList = extensionsList,
        reserveds = reserveds,
        options = options.retainAll(schema, markSet)
    )
  }

  override fun retainLinked(linkedTypes: Set<ProtoType>): Type? {
    val retainedNestedTypes = nestedTypes.mapNotNull { it.retainLinked(linkedTypes) }

    if (!linkedTypes.contains(type)) {
      return when {
        // This type is not retained, and none of its nested types are retained, prune it.
        retainedNestedTypes.isEmpty() -> null
        // This type is not retained but retained nested types, replace it with an enclosing type.
        else -> EnclosingType(location, type, documentation, retainedNestedTypes)
      }
    }

    // We're retaining this type. Retain its fields and oneofs.
    val retainedOneOfs = oneOfs.mapNotNull { it.retainLinked() }

    return MessageType(
        type = type,
        location = location,
        documentation = documentation,
        name = name,
        declaredFields = retainLinked(declaredFields),
        extensionFields = retainLinked(extensionFields).toMutableList(),
        oneOfs = retainedOneOfs,
        nestedTypes = retainedNestedTypes,
        extensionsList = emptyList(),
        reserveds = emptyList(),
        options = options.retainLinked()
    )
  }

  fun toElement(): MessageElement {
    return MessageElement(
        location = location,
        name = name,
        documentation = documentation,
        nestedTypes = toElements(nestedTypes),
        options = options.elements,
        reserveds = toElements(reserveds),
        fields = toElements(declaredFields),
        oneOfs = toElements(oneOfs),
        extensions = toElements(extensionsList),
        groups = emptyList()
    )
  }

  companion object {
    @JvmStatic fun fromElement(
      packageName: String?,
      protoType: ProtoType,
      messageElement: MessageElement
    ): MessageType {
      check(messageElement.groups.isEmpty()) {
        "${messageElement.groups[0].location}: 'group' is not supported"
      }
      val nestedTypes =
          messageElement.nestedTypes.map { Type[packageName, protoType.nestedType(it.name), it] }
      return MessageType(
          type = protoType,
          location = messageElement.location,
          documentation = messageElement.documentation,
          name = messageElement.name,
          declaredFields = fromElements(packageName, messageElement.fields, false),
          extensionFields = mutableListOf(), // Extension fields are populated during linking.
          oneOfs = fromElements(packageName, messageElement.oneOfs, false),
          nestedTypes = nestedTypes,
          extensionsList = fromElements(messageElement.extensions),
          reserveds = fromElements(messageElement.reserveds),
          options = Options(Options.MESSAGE_OPTIONS, messageElement.options)
      )
    }
  }
}

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

expect interface Multimap<K, V> {
  fun size(): Int
  fun isEmpty(): Boolean
  fun containsKey(key: Any?): Boolean
  fun containsValue(value: Any?): Boolean

  operator fun get(key: K?): Collection<V>
  fun values(): Collection<V>
  fun asMap(): Map<K, Collection<V>>
}

internal expect fun <K, V> Map<K, Collection<V>>.toMultimap(): Multimap<K, V>

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.internal.parser.OneOfElement
import kotlin.jvm.JvmStatic

class OneOf private constructor(
  val name: String,
  val documentation: String,
  val fields: List<Field>
) {
  fun link(linker: Linker) {
    for (field in fields) {
      field.link(linker)
    }
  }

  fun linkOptions(linker: Linker, syntaxRules: SyntaxRules) {
    for (field in fields) {
      field.linkOptions(linker, syntaxRules)
    }
  }

  fun retainAll(schema: Schema, markSet: MarkSet, enclosingType: ProtoType): OneOf? {
    val retainedFields = Field.retainAll(schema, markSet, enclosingType, fields)
    return when {
      retainedFields.isEmpty() -> null
      else -> OneOf(name, documentation, retainedFields)
    }
  }

  fun retainLinked(): OneOf? {
    val retainedFields = Field.retainLinked(fields)
    return when {
      retainedFields.isEmpty() -> null
      else -> OneOf(name, documentation, retainedFields)
    }
  }

  companion object {
    @JvmStatic
    fun fromElements(
      packageName: String?,
      elements: List<OneOfElement>,
      extension: Boolean
    ) = elements.map {
      if (it.groups.isNotEmpty()) {
        val (_, location) = it.groups[0]
        throw IllegalStateException("$location: 'group' is not supported")
      }

      return@map OneOf(
          name = it.name,
          documentation = it.documentation,
          fields = Field.fromElements(packageName, it.fields, extension)
      )
    }

    @JvmStatic
    fun toElements(oneOfs: List<OneOf>) =
      oneOfs.map {
        OneOfElement(
            name = it.name,
            documentation = it.documentation,
            fields = Field.toElements(it.fields),
            groups = emptyList()
        )
      }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.ProtoMember.Companion.get
import com.squareup.wire.schema.internal.parser.OptionElement
import kotlin.jvm.JvmField

/**
 * A set of options declared on a message declaration, field declaration, enum declaration, enum
 * constant declaration, service declaration, RPC method declaration, or proto file declaration.
 * Options values may be arbitrary protocol buffer messages, but must be valid protocol buffer
 * messages.
 */
class Options(
  private val optionType: ProtoType,
  private val optionElements: List<OptionElement>
) {
  // Null until this options is linked.
  private var entries: List<LinkedOptionEntry>? = null

  val elements: List<OptionElement>
    get() {
      return entries?.map { it.optionElement } ?: optionElements
    }

  val map: Map<ProtoMember, Any?>
    get() = entries!!.toMap()

  fun retainLinked() = Options(optionType, emptyList())

  fun get(protoMember: ProtoMember): Any? {
    return entries?.find { it.protoMember == protoMember }?.value
  }

  /**
   * Returns true if any of the options in [entries] matches both of the regular expressions
   * provided: its name matches the option's name and its value matches the option's value.
   */
  fun optionMatches(namePattern: String, valuePattern: String): Boolean {
    val nameRegex = namePattern.toRegex()
    val valueRegex = valuePattern.toRegex()

    return entries!!.any { entry ->
      nameRegex.matchEntire(entry.protoMember.member) != null &&
          valueRegex.matchEntire(entry.value.toString()) != null
    }
  }

  fun link(linker: Linker) {
    var entries: List<LinkedOptionEntry> = emptyList()

    for (option in optionElements) {
      val canonicalOption: List<LinkedOptionEntry> = canonicalizeOption(linker, optionType, option)
          ?: continue

      entries = union(linker, entries, canonicalOption)
    }
    this.entries = entries
  }

  private fun canonicalizeOption(
    linker: Linker,
    extensionType: ProtoType,
    option: OptionElement
  ): List<LinkedOptionEntry>? {
    val type = linker.getForOptions(extensionType) as? MessageType
        ?: return null // No known extensions for the given extension type.

    var path: Array<String>?
    var field = type.field(option.name)

    if (field != null) {
      // This is an option declared by descriptor.proto.
      path = arrayOf(option.name)
    } else {
      // This is an option declared by an extension.
      val extensionsForType = type.extensionFieldsMap()
      path = resolveFieldPath(option.name, extensionsForType.keys)
      val packageName = linker.packageName()
      if (path == null && packageName != null) {
        // If the path couldn't be resolved, attempt again by prefixing it with the package name.
        path = resolveFieldPath(packageName + "." + option.name, extensionsForType.keys)
      }
      if (path == null) {
        return null // Unable to find the root of this field path.
      }
      field = extensionsForType[path[0]]
    }

    val result = mutableMapOf<ProtoMember, Any>()
    var last = result
    var lastProtoType: ProtoType? = type.type
    for (i in 1 until path.size) {
      val nested = mutableMapOf<ProtoMember, Any>()
      last[get(lastProtoType!!, field!!)] = nested
      lastProtoType = field.type

      // Force members linking.
      if (lastProtoType != null) {
        linker.getForOptions(lastProtoType)
      }

      last = nested
      field = linker.dereference(field, path[i]) ?: return null // Unable to dereference segment.
    }

    last[get(lastProtoType!!, field!!)] = canonicalizeValue(linker, field, option.value)

    check(result.size == 1) // TODO(benoit) might be safe to remove
    val (protoMember, value) = result.entries.first()
    return listOf(LinkedOptionEntry(option, protoMember, value))
  }

  private fun canonicalizeValue(
    linker: Linker,
    context: Field,
    value: Any
  ): Any {
    when (value) {
      is OptionElement -> {
        val result = mutableMapOf<ProtoMember, Any>()
        val field = linker.dereference(context, value.name)
        if (field == null) {
          linker.addError("unable to resolve option ${value.name} on ${context.type}")
        } else {
          val protoMember = get(context.type!!, field)
          result[protoMember] = canonicalizeValue(linker, field, value.value)
        }
        return coerceValueForField(context, result)
      }

      is Map<*, *> -> {
        val result = mutableMapOf<ProtoMember, Any>()
        for (entry in value) {
          val name = entry.key as String
          val field = linker.dereference(context, name)
          if (field == null) {
            linker.addError("unable to resolve option $name on ${context.type}")
          } else {
            val protoMember = get(context.type!!, field)
            result[protoMember] = canonicalizeValue(linker, field, entry.value!!)
          }
        }
        return coerceValueForField(context, result)
      }

      is List<*> -> {
        val result = mutableListOf<Any>()
        for (element in value) {
          result.addAll(canonicalizeValue(linker, context, element!!) as List<Any>)
        }
        return coerceValueForField(context, result)
      }

      is String -> {
        return coerceValueForField(context, value)
      }

      else -> {
        throw IllegalArgumentException("Unexpected option value: $value")
      }
    }
  }

  private fun coerceValueForField(context: Field, value: Any): Any {
    return when {
      context.isRepeated -> value as? List<*> ?: listOf(value)
      value is List<*> -> value.single()!!
      else -> value
    }
  }

  /** Combine values for the same key, resolving conflicts based on their type.  */
  private fun union(linker: Linker, a: Any, b: Any): Any {
    return when (a) {
      is List<*> -> a + b as List<*>

      is Map<*, *> -> {
        @Suppress("UNCHECKED_CAST") // All maps have this type.
        union(linker, a as Map<ProtoMember, Any?>, b as Map<ProtoMember, Any>)
      }

      else -> {
        linker.addError("conflicting options: $a, $b")
        a // Just return any placeholder.
      }
    }
  }

  private fun union(
    linker: Linker, a: List<LinkedOptionEntry>, b: List<LinkedOptionEntry>
  ): List<LinkedOptionEntry> {
    val aMap: Map<ProtoMember, Any?> = a.toMap()
    val bMap: Map<ProtoMember, Any> = b.toMap() as Map<ProtoMember, Any>

    val valuesMap: MutableMap<ProtoMember, Any?> = LinkedHashMap(aMap)
    for (entry in bMap) {
      val bValue = entry.value
      val aValue = valuesMap[entry.key]
      valuesMap[entry.key] = when {
        aValue != null -> union(linker, aValue, bValue)
        else -> bValue
      }
    }

    return a.map { it.optionElement to it.protoMember }
        .union(b.map { it.optionElement to it.protoMember })
        .map { (optionElement, protoMember) ->
          LinkedOptionEntry(
              optionElement,
              protoMember,
              valuesMap[protoMember]
          )
        }
  }

  private fun union(
    linker: Linker, a: Map<ProtoMember, Any?>, b: Map<ProtoMember, Any>
  ): Map<ProtoMember, Any?> {
    val result: MutableMap<ProtoMember, Any?> = LinkedHashMap(a)
    for (entry in b) {
      val bValue = entry.value
      val aValue = result[entry.key]
      result[entry.key] = when {
        aValue != null -> union(linker, aValue, bValue)
        else -> bValue
      }
    }
    return result
  }

  fun fields(): Multimap<ProtoType, ProtoMember> {
    return fields(PruningRules.Builder().build())
  }

  fun fields(pruningRules: PruningRules): Multimap<ProtoType, ProtoMember> {
    return mutableMapOf<ProtoType, MutableCollection<ProtoMember>>().also {
      gatherFields(it, optionType, entries?.toMap(), pruningRules)
    }.toMultimap()
  }

  private fun gatherFields(
    sink: MutableMap<ProtoType, MutableCollection<ProtoMember>>,
    type: ProtoType,
    o: Any?,
    pruningRules: PruningRules
  ) {
    when (o) {
      is Map<*, *> -> {
        for ((key, value) in o) {
          val protoMember = key as ProtoMember
          if (pruningRules.prunes(protoMember)) continue
          sink.getOrPut(type, ::ArrayList).add(protoMember)
          gatherFields(sink, protoMember.type, value!!, pruningRules)
        }
      }
      is List<*> -> {
        for (e in o) {
          gatherFields(sink, type, e!!, pruningRules)
        }
      }
    }
  }

  fun retainAll(schema: Schema, markSet: MarkSet): Options {
    if (entries.isNullOrEmpty()) return this // Nothing to prune.

    val result = Options(optionType, optionElements)

    @Suppress("UNCHECKED_CAST") // All maps have these type parameters.
    val map = retainAll(schema, markSet, optionType, entries!!.toMap()) as Map<ProtoMember, Any?>?
        ?: emptyMap<ProtoMember, Any>()

    result.entries = entries
        ?.filter { map.containsKey(it.protoMember) }
        ?.map { entry ->
          entry.copy(value = map.getValue(entry.protoMember))
        }

    return result
  }

  /** Returns an object of the same type as [o], or null if it is not retained.  */
  private fun retainAll(
    schema: Schema,
    markSet: MarkSet,
    type: ProtoType?,
    o: Any
  ): Any? {
    return when {
      o is Map<*, *> -> {
        val map = mutableMapOf<ProtoMember, Any>()
        for ((key, value) in o) {
          val protoMember = key as ProtoMember
          val isCoreMemberOfGoogleProtobuf =
              protoMember.type in GOOGLE_PROTOBUF_OPTION_TYPES &&
                  !schema.isExtensionField(protoMember)
          if (!markSet.contains(protoMember) && !isCoreMemberOfGoogleProtobuf) {
            continue  // Prune this field.
          }

          val field = schema.getField(protoMember)!!
          val retainedValue = retainAll(schema, markSet, field.type, value!!)
          if (retainedValue != null) {
            map[protoMember] = retainedValue // This retained field is non-empty.
          } else if (isCoreMemberOfGoogleProtobuf) {
            map[protoMember] = value
          }
        }
        if (map.isNotEmpty()) map else null
      }

      o is List<*> -> {
        val list = mutableListOf<Any>()
        for (value in o) {
          val retainedValue = retainAll(schema, markSet, type, value!!)
          if (retainedValue != null) {
            list.add(retainedValue) // This retained value is non-empty.
          }
        }
        if (list.isNotEmpty()) list else null
      }

      !markSet.contains(type!!) -> null // Prune this type.

      else -> o
    }
  }

  /** Returns true if these options assigns a value to [protoMember].  */
  fun assignsMember(protoMember: ProtoMember?): Boolean {
    // TODO(jwilson): remove the null check; this shouldn't be called until linking completes.
    return entries?.any { it.protoMember == protoMember } ?: false
  }

  companion object {
    @JvmField val FILE_OPTIONS = ProtoType.get("google.protobuf.FileOptions")
    @JvmField val MESSAGE_OPTIONS = ProtoType.get("google.protobuf.MessageOptions")
    @JvmField val FIELD_OPTIONS = ProtoType.get("google.protobuf.FieldOptions")
    @JvmField val ENUM_OPTIONS = ProtoType.get("google.protobuf.EnumOptions")
    @JvmField val ENUM_VALUE_OPTIONS = ProtoType.get("google.protobuf.EnumValueOptions")
    @JvmField val SERVICE_OPTIONS = ProtoType.get("google.protobuf.ServiceOptions")
    @JvmField val METHOD_OPTIONS = ProtoType.get("google.protobuf.MethodOptions")
    val GOOGLE_PROTOBUF_OPTION_TYPES = arrayOf(
        FILE_OPTIONS,
        MESSAGE_OPTIONS,
        FIELD_OPTIONS,
        ENUM_OPTIONS,
        ENUM_VALUE_OPTIONS,
        SERVICE_OPTIONS,
        METHOD_OPTIONS
    )

    /**
     * Given a path like `a.b.c.d` and a set of paths like `{a.b.c, a.f.g, h.j}`, this returns the
     * original path split on dots such that the first element is in the set. For the above example
     * it would return the array `[a.b.c, d]`.
     *
     * Typically the input path is a package name like `a.b`, followed by a dot and a sequence of
     * field names. The first field name is an extension field; subsequent field names make a path
     * within that extension.
     *
     * Note that a single input may yield multiple possible answers, such as when package names
     * and field names collide. This method prefers shorter package names though that is an
     * implementation detail.
     */
    fun resolveFieldPath(
      name: String,
      fullyQualifiedNames: Set<String?>
    ): Array<String>? { // Try to resolve a local name.
      var pos = 0
      while (pos < name.length) {
        pos = name.indexOf('.', pos)
        if (pos == -1) pos = name.length
        val candidate = name.substring(0, pos)
        if (fullyQualifiedNames.contains(candidate)) {
          val path = name.substring(pos).split('.').toTypedArray()
          path[0] = name.substring(0, pos)
          return path
        }
        pos++
      }
      return null
    }
  }

  private fun List<LinkedOptionEntry>.toMap(): Map<ProtoMember, Any?> {
    return map { it.protoMember to it.value }.toMap()
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.Extend.Companion.fromElements
import com.squareup.wire.schema.Service.Companion.fromElements
import com.squareup.wire.schema.Type.Companion.fromElements
import com.squareup.wire.schema.internal.parser.ProtoFileElement

class ProtoFile private constructor(
  val location: Location,
  val imports: List<String>,
  val publicImports: List<String>,
  val packageName: String?,
  val types: List<Type>,
  val services: List<Service>,
  val extendList: List<Extend>,
  val options: Options,
  val syntax: Syntax?
) {
  private var javaPackage: Any? = null

  fun toElement(): ProtoFileElement {
    return ProtoFileElement(
        location,
        packageName,
        syntax,
        imports,
        publicImports,
        Type.toElements(types),
        Service.toElements(services),
        Extend.toElements(extendList),
        options.elements
    )
  }

  /**
   * Returns the name of this proto file, like `simple_message` for
   * `squareup/protos/person/simple_message.proto`.
   */
  fun name(): String {
    var result = location.path

    val slashIndex = result.lastIndexOf('/')
    if (slashIndex != -1) {
      result = result.substring(slashIndex + 1)
    }

    if (result.endsWith(".proto")) {
      result = result.substring(0, result.length - ".proto".length)
    }

    return result
  }

  /**
   * Returns all types and subtypes which are found in the proto file.
   */
  fun typesAndNestedTypes(): List<Type> {
    val typesAndNestedTypes = mutableListOf<Type>()
    for (type in types) {
      typesAndNestedTypes.addAll(type.typesAndNestedTypes())
    }

    return typesAndNestedTypes
  }

  fun javaPackage(): String? {
    return javaPackage?.toString()
  }

  /**
   * Returns a new proto file that omits types, services, extensions, and options not in
   * `pruningRules`.
   */
  fun retainAll(schema: Schema, markSet: MarkSet): ProtoFile {
    val retainedTypes = types.mapNotNull { it.retainAll(schema, markSet) }

    val retainedServices = services.mapNotNull { it.retainAll(schema, markSet) }

    val retainedExtends = extendList.mapNotNull { it.retainAll(schema, markSet) }

    val retainedOptions = options.retainAll(schema, markSet)

    val result = ProtoFile(location, imports, publicImports, packageName, retainedTypes,
        retainedServices, retainedExtends, retainedOptions, syntax)
    result.javaPackage = javaPackage
    return result
  }

  /** Return a copy of this file with only the marked types. */
  fun retainLinked(linkedTypes: Set<ProtoType>): ProtoFile {
    val retainedTypes = types.mapNotNull { it.retainLinked(linkedTypes) }

    // Other .proto files can't link to our services so strip them unconditionally.
    val retainedServices = emptyList<Service>()

    // Strip the extends. They've already been applied to their target types.
    val retainedExtends = emptyList<Extend>()

    val retainedOptions = options.retainLinked()

    val result = ProtoFile(location, imports, publicImports, packageName, retainedTypes,
        retainedServices, retainedExtends, retainedOptions, syntax)
    result.javaPackage = javaPackage
    return result
  }

  /** Returns a new proto file that omits unnecessary imports. */
  fun retainImports(retained: List<ProtoFile>): ProtoFile {
    val retainedImports = mutableListOf<String>()
    for (path in imports) {
      val importedProtoFile = findProtoFile(retained, path) ?: continue

      if (importedProtoFile.types.isEmpty() &&
          importedProtoFile.services.isEmpty() &&
          importedProtoFile.extendList.isEmpty()) {

        // If we extend a google protobuf type, we should keep the import.
        if (path == "google/protobuf/descriptor.proto") {
          for (extend in extendList) {
            if (extend.name.startsWith("google.protobuf.")) {
              retainedImports.add(path)
              break
            }
          }
        }

      } else {
        retainedImports.add(path)
      }
    }

    return if (imports.size != retainedImports.size) {
      val result = ProtoFile(location, retainedImports, publicImports, packageName, types, services,
          extendList, options, syntax)
      result.javaPackage = javaPackage
      result
    } else {
      this
    }
  }

  fun linkOptions(linker: Linker) {
    options.link(linker)
    javaPackage = options.get(JAVA_PACKAGE)
  }

  override fun toString(): String {
    return location.path
  }

  fun toSchema(): String {
    return toElement().toSchema()
  }

  fun validate(linker: Linker) {
    linker.validateEnumConstantNameUniqueness(types)
  }

  /** Syntax version. */
  enum class Syntax(private val string: String) {
    PROTO_2("proto2"),
    PROTO_3("proto3");

    override fun toString(): String = string

    companion object {
      operator fun get(string: String): Syntax {
        for (syntax in values()) {
          if (syntax.string == string) return syntax
        }
        throw IllegalArgumentException("unexpected syntax: $string")
      }
    }

  }

  companion object {
    val JAVA_PACKAGE = ProtoMember.get(Options.FILE_OPTIONS, "java_package")

    fun get(protoFileElement: ProtoFileElement): ProtoFile {
      val packageName = protoFileElement.packageName

      val types = fromElements(packageName, protoFileElement.types)

      val services = fromElements(packageName, protoFileElement.services)

      val wireExtends = fromElements(packageName, protoFileElement.extendDeclarations)

      val options = Options(Options.FILE_OPTIONS, protoFileElement.options)

      return ProtoFile(protoFileElement.location, protoFileElement.imports,
          protoFileElement.publicImports, packageName, types, services, wireExtends, options,
          protoFileElement.syntax)
    }

    private fun findProtoFile(protoFiles: List<ProtoFile>, path: String): ProtoFile? {
      return protoFiles.find { it.location.path == path }
    }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import kotlin.jvm.JvmStatic

/**
 * Identifies a field, enum or RPC on a declaring type. Members are encoded as strings containing a
 * type name, a hash, and a member name, like `squareup.dinosaurs.Dinosaur#length_meters`.
 *
 * A member's name is typically a simple name like "length_meters" or "packed". If the member
 * field is an extension to its type, that name is prefixed with its enclosing package. This yields
 * a member name with two packages, like `google.protobuf.FieldOptions#squareup.units.unit`.
 */
class ProtoMember private constructor(
  val type: ProtoType,
  val member: String
) {
  override fun equals(other: Any?) =
      other is ProtoMember && type == other.type && member == other.member

  override fun hashCode() = type.hashCode() * 37 + member.hashCode()

  override fun toString() = "$type#$member"

  companion object {
    @JvmStatic
    fun get(typeAndMember: String): ProtoMember {
      val hash = typeAndMember.indexOf('#')
      require(hash != -1) { "expected a '#' in $typeAndMember" }
      val type = ProtoType.get(typeAndMember.substring(0, hash))
      val member = typeAndMember.substring(hash + 1)
      return ProtoMember(type, member)
    }

    @JvmStatic
    fun get(type: ProtoType, member: String) = ProtoMember(type, member)

    @JvmStatic
    fun get(type: ProtoType, field: Field): ProtoMember {
      val member = if (field.isExtension) field.qualifiedName else field.name
      return ProtoMember(type, member)
    }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import kotlin.jvm.JvmField
import kotlin.jvm.JvmStatic

/**
 * Names a protocol buffer message, enumerated type, service, map, or a scalar. This class models a
 * fully-qualified name using the protocol buffer package.
 */
class ProtoType {
  val isScalar: Boolean

  private val string: String

  val isMap: Boolean

  /** The type of the map's keys. Only present when [isMap] is true.  */
  val keyType: ProtoType?

  /** The type of the map's values. Only present when [isMap] is true.  */
  val valueType: ProtoType?

  val simpleName: String
    get() {
      val dot = string.lastIndexOf('.')
      return string.substring(dot + 1)
    }

  /** Returns the enclosing type, or null if this type is not nested in another type.  */
  val enclosingTypeOrPackage: String?
    get() {
      val dot = string.lastIndexOf('.')
      return if (dot == -1) null else string.substring(0, dot)
    }

  /**
   * Returns a string like "type.googleapis.com/packagename.messagename" or null if this type is
   * a scalar or a map. Note that this returns a non-null string for enums because it doesn't know
   * if the named type is a message or an enum.
   */
  val typeUrl: String?
    get() {
      return when {
        isScalar || isMap -> null
        else -> "type.googleapis.com/$string"
      }
    }

  /** Creates a scalar or message type.  */
  private constructor(isScalar: Boolean, string: String) {
    this.isScalar = isScalar
    this.string = string
    this.isMap = false
    this.keyType = null
    this.valueType = null
  }

  /** Creates a map type.  */
  private constructor(keyType: ProtoType, valueType: ProtoType, string: String) {
    require(keyType.isScalar && keyType != BYTES && keyType != DOUBLE && keyType != FLOAT) {
      "map key must be non-byte, non-floating point scalar: $keyType"
    }
    this.isScalar = false
    this.string = string
    this.isMap = true
    this.keyType = keyType // TODO restrict what's allowed here
    this.valueType = valueType
  }

  fun nestedType(name: String?): ProtoType {
    check(!isScalar) { "scalar cannot have a nested type" }
    check(!isMap) { "map cannot have a nested type" }
    require(name != null && !name.contains(".") && name.isNotEmpty()) { "unexpected name: $name" }
    return ProtoType(false, "$string.$name")
  }

  override fun equals(other: Any?) = other is ProtoType && string == other.string

  override fun hashCode() = string.hashCode()

  override fun toString() = string

  companion object {
    @JvmField val BOOL = ProtoType(true, "bool")
    @JvmField val BYTES = ProtoType(true, "bytes")
    @JvmField val DOUBLE = ProtoType(true, "double")
    @JvmField val FLOAT = ProtoType(true, "float")
    @JvmField val FIXED32 = ProtoType(true, "fixed32")
    @JvmField val FIXED64 = ProtoType(true, "fixed64")
    @JvmField val INT32 = ProtoType(true, "int32")
    @JvmField val INT64 = ProtoType(true, "int64")
    @JvmField val SFIXED32 = ProtoType(true, "sfixed32")
    @JvmField val SFIXED64 = ProtoType(true, "sfixed64")
    @JvmField val SINT32 = ProtoType(true, "sint32")
    @JvmField val SINT64 = ProtoType(true, "sint64")
    @JvmField val STRING = ProtoType(true, "string")
    @JvmField val UINT32 = ProtoType(true, "uint32")
    @JvmField val UINT64 = ProtoType(true, "uint64")
    @JvmField val ANY = ProtoType(false, "google.protobuf.Any")

    private val SCALAR_TYPES: Map<String, ProtoType> = listOf(
        BOOL,
        BYTES,
        DOUBLE,
        FLOAT,
        FIXED32,
        FIXED64,
        INT32,
        INT64,
        SFIXED32,
        SFIXED64,
        SINT32,
        SINT64,
        STRING,
        UINT32,
        UINT64
    ).associateBy { it.string }

    internal val NUMERIC_SCALAR_TYPES = listOf(
        DOUBLE,
        FLOAT,
        FIXED32,
        FIXED64,
        INT32,
        INT64,
        SFIXED32,
        SFIXED64,
        SINT32,
        SINT64,
        UINT32,
        UINT64
    )

    @JvmStatic
    fun get(enclosingTypeOrPackage: String?, typeName: String): ProtoType {
      return when {
        enclosingTypeOrPackage != null -> get("$enclosingTypeOrPackage.$typeName")
        else -> get(typeName)
      }
    }

    @JvmStatic
    fun get(name: String?): ProtoType {
      val scalar = SCALAR_TYPES[name]
      if (scalar != null) return scalar

      require(name != null && name.isNotEmpty() && !name.contains("#")) { "unexpected name: $name" }

      if (name.startsWith("map<") && name.endsWith(">")) {
        val comma = name.indexOf(',')
        require(comma != -1) { "expected ',' in map type: $name" }
        val key = get(name.substring(4, comma).trim())
        val value = get(name.substring(comma + 1, name.length - 1).trim())
        return ProtoType(key, value, name)
      }

      return ProtoType(false, name)
    }

    @JvmStatic fun get(keyType: ProtoType, valueType: ProtoType, name: String) =
        ProtoType(keyType, valueType, name)
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.ProtoMember.Companion.get
import com.squareup.wire.schema.internal.mutableQueueOf

/**
 * Creates a new schema that contains only the types selected by the pruning rules, including their
 * transitive dependencies.
 */
internal class Pruner(
  private val schema: Schema,
  private val pruningRules: PruningRules
) {
  private val marks = MarkSet(pruningRules)
  /**
   * [types][ProtoType] and [members][ProtoMember] whose immediate dependencies have not
   * yet been visited.
   */
  private val queue = mutableQueueOf<Any>()

  fun prune(): Schema {
    markRoots()
    markReachable()

    val retained = retainImports(retainAll(schema, marks))

    return Schema(retained)
  }

  private fun retainAll(
    schema: Schema,
    marks: MarkSet
  ): List<ProtoFile> {
    return schema.protoFiles.map { protoFile ->
      protoFile.retainAll(schema, marks)
    }
  }

  private fun retainImports(protoFiles: List<ProtoFile>): List<ProtoFile> {
    return protoFiles.map { protoFile ->
      protoFile.retainImports(protoFiles)
    }
  }

  private fun markRoots() {
    for (protoFile in schema.protoFiles) {
      markRoots(protoFile)
    }
  }

  private fun markRoots(protoFile: ProtoFile) {
    for (type in protoFile.types) {
      markRootsIncludingNested(type)
    }
    for (service in protoFile.services) {
      markRoots(service.type())
    }
  }

  private fun markRootsIncludingNested(type: Type) {
    markRoots(type.type)

    for (nested in type.nestedTypes) {
      markRootsIncludingNested(nested)
    }
  }

  private fun markRoots(protoType: ProtoType) {
    if (pruningRules.isRoot(protoType)) {
      marks.root(protoType)
      queue.add(protoType)
      return
    }

    // The top-level type isn't a root, search for root members inside.
    for (reachable in reachableObjects(protoType)) {
      if (reachable !is ProtoMember) continue
      if (!isRetainedVersion(reachable)) continue
      if (pruningRules.isRoot(reachable)) {
        marks.root(reachable)
        marks.mark(reachable.type) // Consider this type as visited.
        queue.add(reachable)
      }
    }
  }

  /** Returns true if this member survives `since` and `until` pruning. */
  private fun isRetainedVersion(protoMember: ProtoMember): Boolean {
    val member = protoMember.member
    return when (val type = schema.getType(protoMember.type)) {
      is MessageType -> {
        val field = type.field(member) ?: type.extensionField(member)!!
        pruningRules.isFieldRetainedVersion(field.options)
      }
      is EnumType -> {
        val enumConstant = type.constant(member)!!
        pruningRules.isEnumConstantRetainedVersion(enumConstant.options)
      }
      else -> true
    }
  }

  /**
   * Mark everything transitively reachable from the queue, adding to the queue whenever a reachable
   * object brings along more reachable objects.
   */
  private fun markReachable() {
    while (true) {
      val root: Any = queue.poll() ?: break
      val reachableMembers: List<Any?> = reachableObjects(root)

      for (reachable in reachableMembers) {
        when (reachable) {
          is ProtoType -> {
            if (root is ProtoMember) {
              if (marks.mark(reachable, root)) {
                queue.add(reachable)
              }
            } else {
              if (marks.mark(reachable)) {
                queue.add(reachable)
              }
            }
          }

          is ProtoMember -> {
            if (isRetainedVersion(reachable) && marks.mark(reachable)) {
              queue.add(reachable)
            }
          }

          null -> {
            // Skip nulls.
            // TODO(jwilson): create a dedicated UNLINKED type as a placeholder.
          }

          else -> {
            throw IllegalStateException("unexpected object: $reachable")
          }
        }
      }
    }
  }

  /**
   * Returns everything reachable from `root` when traversing the graph. The returned list
   * contains instances of type [ProtoMember] and [ProtoType].
   *
   * @param root either a [ProtoMember] or [ProtoType].
   */
  private fun reachableObjects(root: Any): List<Any?> {
    val result = mutableListOf<Any?>()
    val options: Options
    var fileOptions: Options? = null

    when (root) {
      is ProtoMember -> {
        val member = root.member
        val type = schema.getType(root.type)
        val service = schema.getService(root.type)

        if (type is MessageType) {
          val field = type.field(member) ?: type.extensionField(member)
          checkNotNull(field) { "unexpected member: $member" }
          result.add(field.type)
          options = field.options

        } else if (type is EnumType) {
          val constant = type.constant(member)
              ?: throw IllegalStateException("unexpected member: $member")
          options = constant.options

        } else if (service != null) {
          val rpc = service.rpc(member) ?: throw IllegalStateException("unexpected rpc: $member")
          result.add(rpc.requestType)
          result.add(rpc.responseType)
          options = rpc.options

        } else {
          throw IllegalStateException("unexpected member: $member")
        }
      }

      is ProtoType -> {
        if (root.isMap) {
          result.add(root.keyType)
          result.add(root.valueType)
          return result
        }

        if (root.isScalar) {
          return result // Skip scalar types.
        }

        val type = schema.getType(root)
        val service = schema.getService(root)
        fileOptions = schema.protoFile(root)!!.options

        if (type is MessageType) {
          options = type.options
          for (field in type.declaredFields) {
            result.add(get(root, field.name))
          }
          for (field in type.extensionFields) {
            result.add(get(root, field.qualifiedName))
          }
          for (oneOf in type.oneOfs) {
            for (field in oneOf.fields) {
              result.add(get(root, field.name))
            }
          }

        } else if (type is EnumType) {
          options = type.options
          for (constant in type.constants) {
            result.add(get(type.type, constant.name))
          }

        } else if (type is EnclosingType) {
          options = type.options

        } else if (service != null) {
          options = service.options()
          for (rpc in service.rpcs()) {
            result.add(get(service.type(), rpc.name))
          }

        } else {
          throw IllegalStateException("unexpected type: $root")
        }
      }
      else -> {
        throw IllegalStateException("unexpected root: $root")
      }
    }

    addOptions(options.fields(pruningRules).values(), result)
    if (fileOptions != null) {
      addOptions(fileOptions.fields(pruningRules).values(), result)
    }

    return result
  }

  private fun addOptions(options: Collection<ProtoMember>, result: MutableList<Any?>) {
    for (member in options) {
      // If it's an extension, don't consider the entire enclosing type to be reachable.
      if (!schema.isExtensionField(member)) {
        result.add(member.type)
      }
      result.add(member)
    }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.SemVer.Companion.toLowerCaseSemVer

/**
 * A set of rules that describes which types and members to retain and which to remove.
 *
 * Members may be pruned using either their identifier (package, type name, member name) or their
 * version (since and until options).
 *
 * Despite the builder, instances of this class are not safe for concurrent use.
 *
 * ### Identifier Matching
 *
 * If a member is a root in the set, its type is implicitly also considered a root. A type that is
 * a root without a specific member implicitly set all of that type's members as roots, but not its
 * nested types.
 *
 * Identifiers in this set may be in the following forms:
 *
 *  * Package names, followed by `.*`, like `squareup.protos.person.*`. This matches types and
 *    services defined in the package and its descendant packages.
 *
 *  * Fully qualified type and service names, like `squareup.protos.person.Person`.
 *
 *  * Fully qualified member names, which are type names followed by a '#', followed by a member
 *    name, like `squareup.protos.person.Person#address`. Members may be fields, enum constants or
 *    RPCs.
 *
 * An identifier set populated with `Movie` and `Actor#name` contains all members of `Movie` (such
 * as `Movie#name` and `Movie#release_date`). It contains the type `Actor` and one member
 * `Actor#name`, but not `Actor#birth_date` or `Actor#oscar_count`.
 *
 * This set has *root identifiers* and *prune identifiers*, with the most precise identifier
 * taking precedence over the other ones. For instance, if there is one root identifier
 * `a.Movie` along a pruning identifier `a.*`, the type `a.Movie` is considered a root.
 *
 * If the roots set is empty, that implies that all elements are considered roots. Use this to
 * prune unwanted types and members without also marking everything else as roots.
 *
 * ### Version Matching
 *
 * Members may be declared with `wire.since` and `wire.until` options. For example, these options
 * declare a field `age` that was replaced with `birth_date` in version "5.0":
 *
 * ```
 *   optional int32 age = 3 [(wire.until) = "5.0"];
 *   optional Date birth_date = 4 [(wire.since) = "5.0"];
 * ```
 *
 * Client code should typically target a single version. In this example, versions <= "4.0" will
 * have the `age` field only and versions >= "5.0" will have the `birth_date` field only.
 *
 * Service code that supports many clients should support the union of versions of all supported
 * clients. Such code will have both the `age` and `birth_date` fields.
 */
class PruningRules private constructor(builder: Builder) {
  private val roots = builder.roots.toSet()
  private val prunes = builder.prunes.toSet()
  private val oldest = builder.oldest
  private val newest = builder.newest
  private val usedRoots = mutableSetOf<String>()
  private val usedPrunes = mutableSetOf<String>()

  val isEmpty: Boolean
    get() = roots.isEmpty() && prunes.isEmpty() && oldest == null && newest == null

  /** Returns true unless [options] specifies a version that is outside of the configured range. */
  fun isFieldRetainedVersion(options: Options) =
      isRetainedVersion(options, FIELD_SINCE, FIELD_UNTIL)

  /** Returns true unless [options] specifies a version that is outside of the configured range. */
  fun isEnumConstantRetainedVersion(options: Options) =
      isRetainedVersion(options, ENUM_CONSTANT_SINCE, ENUM_CONSTANT_UNTIL)

  private fun isRetainedVersion(
    options: Options,
    sinceMember: ProtoMember,
    untilMember: ProtoMember
  ): Boolean {
    if (newest != null) {
      val sinceOption = options.get(sinceMember)
      val since = (sinceOption as? String)?.toLowerCaseSemVer()
      if (since != null && since > newest) return false
    }

    if (oldest != null) {
      val untilOption = options.get(untilMember)
      val until = (untilOption as? String)?.toLowerCaseSemVer()
      if (until != null && until <= oldest) return false
    }

    return true
  }

  /** Returns true if [type] is a root. */
  fun isRoot(type: ProtoType) = isRoot(type.toString())

  /** Returns true if [protoMember] is a root. */
  fun isRoot(protoMember: ProtoMember) = isRoot(protoMember.toString())

  /**
   * Returns true if [identifier] or any of its enclosing identifiers is a root. If any enclosing
   * identifier is pruned, that takes precedence and this returns false unless the root identifier
   * is more precise.
   */
  private fun isRoot(identifier: String): Boolean {
    if (roots.isEmpty()) return !prunes(identifier)

    var rootMatch: String? = null
    var pruneMatch: String? = null
    var rule: String? = identifier
    while (rule != null) {
      if (pruneMatch == null && prunes.contains(rule)) {
        pruneMatch = rule
      }
      if (rootMatch == null && roots.contains(rule)) {
        rootMatch = rule
      }
      rule = enclosing(rule)
    }

    val isRoot = when {
      pruneMatch != null && rootMatch != null -> pruneMatch.length < rootMatch.length
      pruneMatch != null -> false
      rootMatch != null -> true
      else -> false
    }
    if (isRoot) {
      usedRoots.add(rootMatch!!)
    } else if (pruneMatch != null) {
      usedPrunes.add(pruneMatch)
    }
    return isRoot
  }

  /**
   * Returns true if [type] should be pruned, even if it is a transitive dependency of a root. In
   * that case, the referring member is also pruned.
   */
  fun prunes(type: ProtoType) = prunes(type.toString())

  /** Returns true if [protoMember] should be pruned. */
  fun prunes(protoMember: ProtoMember) = prunes(protoMember.toString())

  /** Returns true if [identifier] or any of its enclosing identifiers is pruned.  */
  private fun prunes(identifier: String): Boolean {
    var rootMatch: String? = null
    var pruneMatch: String? = null
    var rule: String? = identifier
    while (rule != null) {
      if (pruneMatch == null && prunes.contains(rule)) {
        pruneMatch = rule
      }
      if (rootMatch == null && roots.contains(rule)) {
        rootMatch = rule
      }
      rule = enclosing(rule)
    }

    val pruned = when {
      pruneMatch != null && rootMatch != null -> pruneMatch.length >= rootMatch.length
      pruneMatch != null -> true
      else -> false
    }
    if (pruned) {
      usedPrunes.add(pruneMatch!!)
    }
    return pruned
  }

  fun unusedRoots() = roots - usedRoots

  fun unusedPrunes() = prunes - usedPrunes

  class Builder {
    internal val roots = mutableSetOf<String>()
    internal val prunes = mutableSetOf<String>()
    internal var oldest: SemVer? = null
    internal var newest: SemVer? = null

    fun addRoot(identifier: String) = apply {
      roots.add(identifier)
    }

    fun addRoot(identifiers: Iterable<String>) = apply {
      roots.addAll(identifiers)
    }

    fun prune(identifier: String) = apply {
      prunes.add(identifier)
    }

    fun prune(identifiers: Iterable<String>) = apply {
      prunes.addAll(identifiers)
    }

    /**
     * The exclusive lower bound of the version range. Fields with `until` values greater than this
     * are retained.
     */
    fun oldest(oldest: String?) = apply {
      this.oldest = oldest?.toLowerCaseSemVer()
    }

    /**
     * The inclusive upper bound of the version range. Fields with `since` values less than or equal
     * to this are retained.
     */
    fun newest(newest: String?) = apply {
      this.newest = newest?.toLowerCaseSemVer()
    }

    fun build(): PruningRules {
      check(oldest == null || newest == null || oldest!! <= newest!!) {
        "expected oldest $oldest <= newest $newest"
      }
      val conflictingRules = roots.intersect(prunes)
      check(conflictingRules.isEmpty()) {
        "same rule(s) defined in both roots and prunes: ${conflictingRules.joinToString()}"
      }
      return PruningRules(this)
    }
  }

  companion object {
    internal val FIELD_SINCE = ProtoMember.get(Options.FIELD_OPTIONS, "wire.since")
    internal val FIELD_UNTIL = ProtoMember.get(Options.FIELD_OPTIONS, "wire.until")
    internal val ENUM_CONSTANT_SINCE = ProtoMember.get(Options.ENUM_VALUE_OPTIONS, "wire.since")
    internal val ENUM_CONSTANT_UNTIL = ProtoMember.get(Options.ENUM_VALUE_OPTIONS, "wire.until")

    /**
     * Returns the identifier or wildcard that encloses [identifier], or null if it is not enclosed.
     *
     *  * If [identifier] is a member this returns the enclosing type.
     *
     *  * If it is a type it returns the enclosing package with a wildcard, like
     *    `squareup.dinosaurs.*`.
     *
     *  * If it is a package with a wildcard, it returns the parent package with a wildcard, like
     *    `squareup.*`. The root wildcard is a lone asterisk, `*`.
     *
     */
    internal fun enclosing(identifier: String): String? {
      val hash = identifier.lastIndexOf('#')
      if (hash != -1) return identifier.substring(0, hash)

      val from = if (identifier.endsWith(".*")) identifier.length - 3 else identifier.length - 1
      val dot = identifier.lastIndexOf('.', from)
      if (dot != -1) return identifier.substring(0, dot) + ".*"

      if (identifier != "*") return "*"
      return null
    }
  }
}

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.internal.parser.ReservedElement
import kotlin.jvm.JvmStatic

class Reserved(
  val location: Location,
  val documentation: String,
  val values: List<Any>
) {
  fun matchesTag(tag: Int) = values.any {
    it is Int && tag == it || (it as? IntRange)?.contains(tag) == true
  }

  fun matchesName(name: String) = values.any { it is String && name == it }

  companion object {
    @JvmStatic
    fun fromElements(elements: List<ReservedElement>) =
      elements.map { Reserved(it.location, it.documentation, it.values) }

    @JvmStatic
    fun toElements(reserveds: List<Reserved>) =
      reserveds.map { ReservedElement(it.location, it.documentation, it.values) }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.internal.parser.RpcElement
import kotlin.jvm.JvmStatic

class Rpc private constructor(
  val location: Location,
  val name: String,
  val documentation: String,
  private val requestTypeElement: String,
  private val responseTypeElement: String,
  val requestStreaming: Boolean,
  val responseStreaming: Boolean,
  val options: Options
) {
  // Null until this RPC is linked.
  var requestType: ProtoType? = null
    private set

  // Null until this RPC is linked.
  var responseType: ProtoType? = null
    private set

  fun link(linker: Linker) {
    val linker = linker.withContext(this)
    requestType = linker.resolveMessageType(requestTypeElement)
    responseType = linker.resolveMessageType(responseTypeElement)
  }

  fun linkOptions(linker: Linker) {
    val linker = linker.withContext(this)
    options.link(linker)
  }

  fun validate(linker: Linker) {
    val linker = linker.withContext(this)
    linker.validateImport(location, requestType!!)
    linker.validateImport(location, responseType!!)
  }

  fun retainAll(schema: Schema, markSet: MarkSet): Rpc? {
    if (requestType!! !in markSet || responseType!! !in markSet) return null
    val result = Rpc(
        location = location,
        name = name,
        documentation = documentation,
        requestTypeElement = requestTypeElement,
        responseTypeElement = responseTypeElement,
        requestStreaming = requestStreaming,
        responseStreaming = responseStreaming,
        options = options.retainAll(schema, markSet)
    )
    result.requestType = requestType
    result.responseType = responseType
    return result
  }

  companion object {
    @JvmStatic
    fun fromElements(elements: List<RpcElement>): List<Rpc> {
      return elements.map { element ->
        Rpc(
            location = element.location,
            name = element.name,
            documentation = element.documentation,
            requestTypeElement = element.requestType,
            responseTypeElement = element.responseType,
            requestStreaming = element.requestStreaming,
            responseStreaming = element.responseStreaming,
            options = Options(Options.METHOD_OPTIONS, element.options)
        )
      }
    }

    @JvmStatic
    fun toElements(rpcs: List<Rpc>): List<RpcElement> {
      return rpcs.map { rpc ->
        RpcElement(
            location = rpc.location,
            name = rpc.name,
            documentation = rpc.documentation,
            requestType = rpc.requestTypeElement,
            responseType = rpc.responseTypeElement,
            requestStreaming = rpc.requestStreaming,
            responseStreaming = rpc.responseStreaming,
            options = rpc.options.elements
        )
      }
    }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.ProtoAdapter
import kotlin.collections.set
import kotlin.jvm.JvmOverloads

/**
 * A collection of .proto files that describe a set of messages. A schema is *linked*: each
 * field's type name is resolved to the corresponding type definition.
 *
 * Use [SchemaLoader] to load a schema from source files.
 */
class Schema internal constructor(protoFiles: Iterable<ProtoFile>) {
  val protoFiles: List<ProtoFile> = protoFiles.sortedBy { it.location.path }

  private val protoFilesIndex: Map<ProtoType?, ProtoFile?>
  private val typesIndex: Map<String, Type>
  private val servicesIndex: Map<String, Service>
  init {
    val index = mutableMapOf<ProtoType?, ProtoFile?>()
    typesIndex = buildTypesIndex(protoFiles, index)
    servicesIndex = buildServicesIndex(protoFiles, index)
    protoFilesIndex = index
  }

  /** Returns the proto file at [path], or null if this schema has no such file.  */
  fun protoFile(path: String): ProtoFile? = protoFiles.first { it.location.path == path }

  /** Returns the proto file containing this [protoType], or null if there isn't such file.  */
  fun protoFile(protoType: ProtoType): ProtoFile? = protoFilesIndex[protoType]

  /**
   * Returns a copy of this schema that retains only the types and services selected by
   * [pruningRules], plus their transitive dependencies.
   */
  fun prune(pruningRules: PruningRules): Schema = Pruner(this, pruningRules).prune()

  /**
   * Returns the service with the fully qualified name [name], or null if this schema defines
   * no such service.
   */
  fun getService(name: String): Service? = servicesIndex[name]

  /** Returns the service for [protoType], or null if this schema defines no such service. */
  fun getService(protoType: ProtoType): Service? = getService(protoType.toString())

  /**
   * Returns the type with the fully qualified name [name], or null if this schema defines no
   * such type.
   */
  fun getType(name: String): Type? = typesIndex[name]

  /** Returns the type for [protoType], or null if this schema defines no such type. */
  fun getType(protoType: ProtoType): Type? = getType(protoType.toString())

  /** Returns the field for [protoMember], or null if this schema defines no such field. */
  fun getField(protoMember: ProtoMember): Field? {
    val type = getType(protoMember.type) as? MessageType ?: return null
    return type.field(protoMember.member) ?: type.extensionField(protoMember.member)
  }

  /**
   * Returns the field with the fully qualified [typeName] and [memberName], or null if this schema
   * defines no such field.
   */
  fun getField(typeName: String, memberName: String): Field? {
    return getField(ProtoType.get(typeName), memberName)
  }

  /**
   * Returns the field for [protoType] and [memberName], or null if this schema defines no such
   * field.
   */
  fun getField(protoType: ProtoType, memberName: String): Field? {
    return getField(ProtoMember.get(protoType, memberName))
  }

  /**
   * Returns a wire adapter for the message or enum type named [typeName]. The returned type
   * adapter doesn't have model classes to encode and decode from, so instead it uses scalar types
   * ([String], [ByteString][okio.ByteString], [Integer], etc.),
   * [maps][Map], and [lists][List]. It can both encode and decode
   * these objects. Map keys are field names.
   *
   * @param includeUnknown true to include values for unknown tags in the returned model. Map keys
   * for such values is the unknown value's tag name as a string. Unknown values are decoded to
   * [Long], [Long], [Integer], or [ByteString][okio.ByteString] for
   * [VARINT][com.squareup.wire.FieldEncoding.VARINT],
   * [FIXED64][com.squareup.wire.FieldEncoding.FIXED64],
   * [FIXED32][com.squareup.wire.FieldEncoding.FIXED32], or
   * [LENGTH_DELIMITED][com.squareup.wire.FieldEncoding.LENGTH_DELIMITED], respectively.
   */
  fun protoAdapter(
    typeName: String,
    includeUnknown: Boolean
  ): ProtoAdapter<Any> {
    val type = requireNotNull(getType(typeName)) { "unexpected type $typeName" }
    return SchemaProtoAdapterFactory(this, includeUnknown)[type.type]
  }

  fun isExtensionField(protoMember: ProtoMember): Boolean {
    val type = getType(protoMember.type)
    return type is MessageType && type.extensionField(protoMember.member) != null
  }

  companion object {
    @JvmOverloads
    fun fromFiles(
      sourceProtoFiles: Iterable<ProtoFile>,
      pathFilesLoader: Loader = CoreLoader
    ): Schema {
      return Linker(pathFilesLoader).link(sourceProtoFiles)
    }

    private fun buildTypesIndex(
      protoFiles: Iterable<ProtoFile>,
      protoFilesIndex: MutableMap<ProtoType?, ProtoFile?>
    ): Map<String, Type> {
      val typesByName = mutableMapOf<String, Type>()

      fun index(type: Type, protoFile: ProtoFile) {
        val protoType = type.type
        if (protoType !in protoFilesIndex) {
          protoFilesIndex[protoType] = protoFile
        }
        typesByName[protoType.toString()] = type
        for (nested in type.nestedTypes) {
          index(nested, protoFile)
        }
      }

      for (protoFile in protoFiles) {
        for (type in protoFile.types) {
          index(type, protoFile)
        }
      }
      return typesByName
    }

    private fun buildServicesIndex(
      protoFiles: Iterable<ProtoFile>,
      protoFilesIndex: MutableMap<ProtoType?, ProtoFile?>
    ): Map<String, Service> {
      val result = mutableMapOf<String, Service>()
      for (protoFile in protoFiles) {
        for (service in protoFile.services) {
          result[service.type().toString()] = service
          protoFilesIndex[service.type()] = protoFile
        }
      }
      return result
    }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

class SchemaException(errors: List<String>) : RuntimeException(
    errors.joinToString(separator = "\n"))

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.FieldEncoding.LENGTH_DELIMITED
import com.squareup.wire.FieldEncoding.VARINT
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.ProtoReader
import com.squareup.wire.ProtoWriter
import kotlin.jvm.JvmField

/**
 * Creates type adapters to read and write protocol buffer data from a schema model. This doesn't
 * require an intermediate code gen step.
 */
internal class SchemaProtoAdapterFactory(
  val schema: Schema,
  private val includeUnknown: Boolean
) {
  private val adapterMap = mutableMapOf<ProtoType?, ProtoAdapter<*>>(
      ProtoType.BOOL to ProtoAdapter.BOOL,
      ProtoType.BYTES to ProtoAdapter.BYTES,
      ProtoType.DOUBLE to ProtoAdapter.DOUBLE,
      ProtoType.FLOAT to ProtoAdapter.FLOAT,
      ProtoType.FIXED32 to ProtoAdapter.FIXED32,
      ProtoType.FIXED64 to ProtoAdapter.FIXED64,
      ProtoType.INT32 to ProtoAdapter.INT32,
      ProtoType.INT64 to ProtoAdapter.INT64,
      ProtoType.SFIXED32 to ProtoAdapter.SFIXED32,
      ProtoType.SFIXED64 to ProtoAdapter.SFIXED64,
      ProtoType.SINT32 to ProtoAdapter.SINT32,
      ProtoType.SINT64 to ProtoAdapter.SINT64,
      ProtoType.STRING to ProtoAdapter.STRING,
      ProtoType.UINT32 to ProtoAdapter.UINT32,
      ProtoType.UINT64 to ProtoAdapter.UINT64
  )

  operator fun get(protoType: ProtoType): ProtoAdapter<Any> {
    if (protoType.isMap) throw UnsupportedOperationException("map types not supported")
    val result = adapterMap[protoType]
    if (result != null) {
      return result as ProtoAdapter<Any>
    }
    val type = requireNotNull(schema.getType(protoType)) { "unknown type: $protoType" }
    if (type is EnumType) {
      val enumAdapter = EnumAdapter(type)
      adapterMap[protoType] = enumAdapter
      return enumAdapter
    }
    if (type is MessageType) {
      val messageAdapter = MessageAdapter(type.type.typeUrl, includeUnknown)
      // Put the adapter in the map early to mitigate the recursive calls to get() made below.
      adapterMap[protoType] = messageAdapter
      for (field in type.fields()) {
        val fieldAdapter = Field(field.name, field.tag, field.isRepeated, get(field.type!!))
        messageAdapter.fieldsByName[field.name] = fieldAdapter
        messageAdapter.fieldsByTag[field.tag] = fieldAdapter
      }
      return messageAdapter as ProtoAdapter<Any>
    }
    throw IllegalArgumentException("unexpected type: $protoType")
  }

  internal class EnumAdapter(
    private val enumType: EnumType
  ) : ProtoAdapter<Any>(VARINT, Any::class, null) {
    override fun encodedSize(value: Any): Int {
      throw UnsupportedOperationException()
    }

    override fun encode(writer: ProtoWriter, value: Any) {
      if (value is String) {
        val constant = enumType.constant(value)
        writer.writeVarint32(constant!!.tag)
      } else if (value is Int) {
        writer.writeVarint32(value)
      } else {
        throw IllegalArgumentException("unexpected " + enumType.type + ": " + value)
      }
    }

    override fun decode(reader: ProtoReader): Any {
      val value = UINT32.decode(reader)
      val constant = enumType.constant(value)
      return constant?.name ?: value
    }

    override fun redact(value: Any): Any {
      throw UnsupportedOperationException()
    }
  }

  internal class MessageAdapter(
    typeUrl: String?,
    private val includeUnknown: Boolean
  ) : ProtoAdapter<Map<String, Any>>(LENGTH_DELIMITED, MutableMap::class, typeUrl) {
    @JvmField val fieldsByTag = mutableMapOf<Int, Field>()
    @JvmField val fieldsByName = mutableMapOf<String, Field>()

    override fun redact(value: Map<String, Any>): Map<String, Any> {
      throw UnsupportedOperationException()
    }

    override fun encodedSize(value: Map<String, Any>): Int {
      var size = 0
      for ((key, value1) in value) {
        val field = fieldsByName[key] ?: continue
        // Ignore unknown values!
        val protoAdapter = field.protoAdapter as ProtoAdapter<Any>
        if (field.repeated) {
          for (o in value1 as List<*>) {
            size += protoAdapter.encodedSizeWithTag(field.tag, o)
          }
        } else {
          size += protoAdapter.encodedSizeWithTag(field.tag, value1)
        }
      }
      return size
    }

    override fun encode(writer: ProtoWriter, value: Map<String, Any>) {
      for ((key, value1) in value) {
        val field = fieldsByName[key] ?: continue
        // Ignore unknown values!
        val protoAdapter = field.protoAdapter as ProtoAdapter<Any>
        if (field.repeated) {
          for (o in value1 as List<*>) {
            protoAdapter.encodeWithTag(writer, field.tag, o)
          }
        } else {
          protoAdapter.encodeWithTag(writer, field.tag, value1)
        }
      }
    }

    override fun decode(reader: ProtoReader): Map<String, Any> {
      val result = mutableMapOf<String, Any>()
      val token = reader.beginMessage()
      while (true) {
        val tag = reader.nextTag()
        if (tag == -1) break

        var field = fieldsByTag[tag]
        if (field == null) {
          field = if (includeUnknown) {
            val name = tag.toString()
            Field(name, tag, true, reader.peekFieldEncoding()!!.rawProtoAdapter())
          } else {
            reader.skip()
            continue
          }
        }
        val value = field.protoAdapter.decode(reader)!!
        if (field.repeated) {
          val values = result.getOrPut(field.name) { mutableListOf<Any>() } as MutableList<Any>
          values.add(value)
        } else {
          result[field.name] = value
        }
      }
      reader.endMessageAndGetUnknownFields(token) // Ignore return value
      return result
    }

    override fun toString(value: Map<String, Any>): String {
      throw UnsupportedOperationException()
    }
  }

  internal class Field(
    val name: String,
    val tag: Int,
    val repeated: Boolean,
    val protoAdapter: ProtoAdapter<*>
  )
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.internal.isDigit
import com.squareup.wire.schema.internal.toEnglishLowerCase

/**
 * A version string as specified by [semver.org][semver_org]. This is used to order versions for
 * sorting since and until.
 *
 * If there is a `+` in the version string it delimits the build metadata. The plus character and
 * the section that follows are unused for version comparisons.
 *
 * The rest of the string is split into two sections:
 *
 *  * Release (preceding the first dash). Typically like "1.0.0".
 *
 *  * Pre-release (optional, following the first dash). Typically like "alpha", "snapshot", or
 *    "rc1".
 *
 * Each section has one or more segments separated by dots and dashes. Within a segment comparisons
 * are either numeric (if both segments are strictly digits), lexicographic (if both contain
 * non-digit characters) or numbers-first (if only one segment is strictly digits).
 *
 * This class requires lowercase versions to defend against potentially surprising behavior in the
 * semver.org spec ("1.0-alpha" comes after "1.0-BETA" due to case-sensitive sorting).
 *
 * [semver_org]: https://semver.org/
 */
internal data class SemVer(val version: String) : Comparable<SemVer> {

  init {
    require(version.toEnglishLowerCase() == version) { "version must be lowercase: $version" }
  }

  override fun toString() = version

  override fun compareTo(other: SemVer): Int {
    val a = version
    val b = other.version
    var aPos = 0
    var bPos = 0

    for (terminators in listOf(RELEASE_TERMINATORS, PRERELEASE_TERMINATORS)) {
      val aSize = a.find(terminators, startIndex = aPos)
      val bSize = b.find(terminators, startIndex = bPos)

      // A version that lacks a pre-release section comes after a version that has one.
      if (terminators === PRERELEASE_TERMINATORS) {
        val aNoPrerelease = aPos == aSize
        val bNoPrerelease = bPos == bSize
        if (aNoPrerelease && !bNoPrerelease) return 1
        if (bNoPrerelease && !aNoPrerelease) return -1
      }

      while (aPos < aSize && bPos < bSize) {
        val aLimit = a.find(SEPARATORS, startIndex = aPos, endIndex = aSize)
        val bLimit = b.find(SEPARATORS, startIndex = bPos, endIndex = bSize)

        val result = compareSegment(
            a.substring(aPos, aLimit),
            b.substring(bPos, bLimit)
        )

        if (result != 0) return result

        // The next position starts just past the delimiter.
        aPos = if (aLimit < aSize) aLimit + 1 else aSize
        bPos = if (bLimit < bSize) bLimit + 1 else bSize
      }

      // If there are more segments in this section, it's later.
      if (aPos < aSize) return 1
      if (bPos < bSize) return -1
    }

    return 0
  }

  private fun compareSegment(a: String, b: String): Int {
    val aAllDigits = a.all(Char::isDigit)
    val bAllDigits = b.all(Char::isDigit)
    return when {
      aAllDigits && bAllDigits -> compareNumber(a, b)
      aAllDigits -> -1
      bAllDigits -> 1
      else -> a.compareTo(b)
    }
  }

  /** Compare two strings as non-negative integral values without decoding. */
  private fun compareNumber(a: String, b: String): Int {
    val aStart = a.indexOfFirst { it != '0' }
    val bStart = b.indexOfFirst { it != '0' }
    return when {
      aStart == -1 && bStart == -1 -> 0 // a and b are 0
      aStart == -1 -> -1 // a is 0, b is not
      bStart == -1 -> 1 // b is 0, a is not
      else -> {
        val aLength = a.length - aStart
        val bLength = b.length - bStart
        when {
          aLength < bLength -> -1 // a is shorter than b, thus smaller
          aLength > bLength -> 1 // b is shorter than a, thus smaller
          else -> a.substring(aStart).compareTo(b.substring(bStart)) // compare lexicographically
        }
      }
    }
  }

  /**
   * Returns the index of a char in [chars] in `[startIndex..endIndex)`. Returns [endIndex] if the
   * value is not found.
   */
  private fun String.find(
    chars: CharArray,
    startIndex: Int = 0,
    endIndex: Int = length
  ): Int {
    val result = indexOfAny(chars, startIndex = startIndex)
    if (result != -1 && result < endIndex) return result
    return endIndex
  }

  companion object {
    /** Characters that signal the end of the release section. */
    private val RELEASE_TERMINATORS = charArrayOf('+', '-')
    /** Characters that signal the end of the pre-release section. */
    private val PRERELEASE_TERMINATORS = charArrayOf('+')
    /** Characters that separate segments within a section. */
    private val SEPARATORS = charArrayOf('.', '-')

    fun String.toLowerCaseSemVer() = SemVer(toEnglishLowerCase())
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
@file:Suppress("NAME_SHADOWING")

package com.squareup.wire.schema

import com.squareup.wire.schema.ProtoMember.Companion.get
import com.squareup.wire.schema.ProtoType.Companion.get
import com.squareup.wire.schema.Rpc.Companion.fromElements
import com.squareup.wire.schema.internal.parser.ServiceElement
import kotlin.jvm.JvmStatic

class Service private constructor(
  private val protoType: ProtoType,
  private val location: Location,
  private val documentation: String,
  private val name: String,
  private val rpcs: List<Rpc>,
  private val options: Options
) {
  fun location(): Location {
    return location
  }

  fun type(): ProtoType {
    return protoType
  }

  fun documentation(): String {
    return documentation
  }

  fun name(): String {
    return name
  }

  fun rpcs(): List<Rpc> {
    return rpcs
  }

  /** Returns the RPC named `name`, or null if this service has no such method.  */
  fun rpc(name: String): Rpc? {
    return rpcs.find { it.name == name }
  }

  fun options(): Options {
    return options
  }

  fun link(linker: Linker) {
    var linker = linker
    linker = linker.withContext(this)
    for (rpc in rpcs) {
      rpc.link(linker)
    }
  }

  fun linkOptions(linker: Linker) {
    var linker = linker
    linker = linker.withContext(this)
    for (rpc in rpcs) {
      rpc.linkOptions(linker)
    }
    options.link(linker)
  }

  fun validate(linker: Linker) {
    var linker = linker
    linker = linker.withContext(this)
    for (rpc in rpcs) {
      rpc.validate(linker)
    }
  }

  fun retainAll(
    schema: Schema,
    markSet: MarkSet
  ): Service? {
    // If this service is not retained, prune it.
    if (!markSet.contains(protoType)) {
      return null
    }

    val retainedRpcs = mutableListOf<Rpc>()
    for (rpc in rpcs) {
      val retainedRpc = rpc.retainAll(schema, markSet)
      if (retainedRpc != null && markSet.contains(get(protoType, rpc.name))) {
        retainedRpcs.add(retainedRpc)
      }
    }

    return Service(protoType, location, documentation, name, retainedRpcs,
        options.retainAll(schema, markSet))
  }

  companion object {
    internal fun fromElement(
      protoType: ProtoType,
      element: ServiceElement
    ): Service {
      val rpcs = fromElements(element.rpcs)
      val options = Options(Options.SERVICE_OPTIONS, element.options)

      return Service(protoType, element.location, element.documentation, element.name, rpcs,
          options)
    }

    @JvmStatic internal fun fromElements(
      packageName: String?,
      elements: List<ServiceElement>
    ): List<Service> {
      return elements.map { service ->
        val protoType = get(packageName, service.name)
        fromElement(protoType, service)
      }
    }

    @JvmStatic internal fun toElements(services: List<Service>): List<ServiceElement> {
      return services.map { service ->
        ServiceElement(
            service.location,
            service.name,
            service.documentation,
            Rpc.toElements(service.rpcs),
            service.options.elements
        )
      }
    }
  }
}

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.ProtoFile.Syntax
import com.squareup.wire.schema.ProtoFile.Syntax.PROTO_2
import com.squareup.wire.schema.ProtoFile.Syntax.PROTO_3

/** A set of rules which defines schema requirements for a specific [Syntax]. */
interface SyntaxRules {
  fun allowUserDefinedDefaultValue(): Boolean
  fun canExtend(protoType: ProtoType): Boolean
  fun enumRequiresZeroValueAtFirstPosition(): Boolean
  fun isPackedByDefault(type: ProtoType, label: Field.Label?): Boolean

  companion object {
    fun get(syntax: Syntax?): SyntaxRules {
      return when (syntax) {
        PROTO_3 -> PROTO_3_SYNTAX_RULES
        PROTO_2,
        null -> PROTO_2_SYNTAX_RULES
      }
    }

    internal val PROTO_2_SYNTAX_RULES = object : SyntaxRules {
      override fun allowUserDefinedDefaultValue(): Boolean = true
      override fun canExtend(protoType: ProtoType): Boolean = true
      override fun enumRequiresZeroValueAtFirstPosition(): Boolean = false
      override fun isPackedByDefault(
        type: ProtoType,
        label: Field.Label?
      ): Boolean = false
    }

    internal val PROTO_3_SYNTAX_RULES = object : SyntaxRules {
      override fun allowUserDefinedDefaultValue(): Boolean = false
      override fun canExtend(protoType: ProtoType): Boolean {
        return protoType in Options.GOOGLE_PROTOBUF_OPTION_TYPES
      }
      override fun enumRequiresZeroValueAtFirstPosition(): Boolean = true
      override fun isPackedByDefault(
        type: ProtoType,
        label: Field.Label?
      ): Boolean {
        return label == Field.Label.REPEATED && type in ProtoType.NUMERIC_SCALAR_TYPES
      }
    }
  }
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.EnumType.Companion.fromElement
import com.squareup.wire.schema.MessageType.Companion.fromElement
import com.squareup.wire.schema.internal.parser.EnumElement
import com.squareup.wire.schema.internal.parser.MessageElement
import com.squareup.wire.schema.internal.parser.TypeElement
import kotlin.jvm.JvmStatic

abstract class Type {
  abstract val location: Location
  abstract val type: ProtoType
  abstract val documentation: String
  abstract val options: Options
  abstract val nestedTypes: List<Type>
  abstract fun linkMembers(linker: Linker)
  abstract fun linkOptions(linker: Linker, syntaxRules: SyntaxRules)
  abstract fun validate(linker: Linker, syntaxRules: SyntaxRules)
  abstract fun retainAll(schema: Schema, markSet: MarkSet): Type?

  /**
   * Returns a copy of this containing only the types in [linkedTypes], or null if that set is
   * empty. This will return an [EnclosingType] if it is itself not linked, but its nested types are
   * linked.
   *
   * The returned type is a shadow of its former self. It it useful for linking against, but lacks
   * most of the members of the original type.
   */
  abstract fun retainLinked(linkedTypes: Set<ProtoType>): Type?

  /**
   * Returns all types and subtypes which are linked to the type.
   */
  fun typesAndNestedTypes(): List<Type> {
    val typesAndNestedTypes = mutableListOf<Type>()
    typesAndNestedTypes.add(this)
    for (type in nestedTypes) {
      typesAndNestedTypes.addAll(type.typesAndNestedTypes())
    }
    return typesAndNestedTypes
  }

  companion object {
    operator fun get(packageName: String?, protoType: ProtoType, type: TypeElement): Type {
      return when (type) {
        is EnumElement -> fromElement(protoType, type)
        is MessageElement -> fromElement(packageName, protoType, type)
        else -> throw IllegalArgumentException("unexpected type: $type")
      }
    }

    @JvmStatic
    fun fromElements(
      packageName: String?,
      elements: List<TypeElement>
    ) = elements.map {
      val protoType = ProtoType.get(packageName, it.name)
      return@map this[packageName, protoType, it]
    }

    private fun toElement(type: Type): TypeElement {
      return when (type) {
        is EnumType -> type.toElement()
        is MessageType -> type.toElement()
        is EnclosingType -> type.toElement()
        else -> throw IllegalArgumentException("unexpected type: $type")
      }
    }

    @JvmStatic
    fun toElements(types: List<Type>) = types.map { toElement(it) }
  }
}

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

actual object CoreLoader : Loader {
  override fun load(path: String): ProtoFile {
    TODO("Not yet implemented in JS")
  }
}

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

actual interface Multimap<K, V> {
  actual fun size(): Int
  actual fun isEmpty(): Boolean
  actual fun containsKey(key: Any?): Boolean
  actual fun containsValue(value: Any?): Boolean
  actual operator fun get(key: K?): Collection<V>
  actual fun values(): Collection<V>
  actual fun asMap(): Map<K, Collection<V>>
}

private class SimpleMultimap<K, V>(private val map: Map<K, Collection<V>>) : Multimap<K, V> {
  override fun size() = map.values.sumBy { it.size }
  override fun isEmpty() = map.isEmpty()
  override fun containsKey(key: Any?) = map.containsKey(key)
  override fun containsValue(value: Any?) = map.values.any { it.contains(value) }
  override fun get(key: K?) = map[key] ?: emptyList()
  override fun values() = map.values.flatten()
  override fun asMap() = map
}

internal actual fun <K, V> Map<K, Collection<V>>.toMultimap(): Multimap<K, V> {
  return SimpleMultimap(this)
}

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

class ClaimedDefinitions {
  private val set = mutableSetOf<ProtoType>()

  fun claim(type: ProtoType) {
    set.add(type)
  }

  fun claim(type: Type) {
    claim(type.type)
  }

  fun claim(service: Service) {
    claim(service.type())
  }

  operator fun contains(type: ProtoType) = set.contains(type)

  operator fun contains(type: Type) = contains(type.type)

  operator fun contains(service: Service) = contains(service.type())
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.squareup.wire.schema.internal.parser.ProtoParser
import okio.buffer
import okio.source

/**
 * A loader that can only load built-in `.proto` files:
 *
 *  * Google's protobuf descriptor, which defines standard options like `default`, `deprecated`, and
 *    `java_package`.
 *
 *  * Wire's extensions, which defines since and until options.
 *
 * If the user has provided their own version of these protos, those are preferred.
 */
actual object CoreLoader : Loader {
  const val ANY_PROTO = "google/protobuf/any.proto"
  const val DESCRIPTOR_PROTO = "google/protobuf/descriptor.proto"
  const val WIRE_EXTENSIONS_PROTO = "wire/extensions.proto"

  override fun load(path: String): ProtoFile {
    if (path == ANY_PROTO ||
        path == DESCRIPTOR_PROTO ||
        path == WIRE_EXTENSIONS_PROTO) {
      val resourceAsStream = SchemaLoader::class.java.getResourceAsStream("/$path")
      resourceAsStream.source().buffer().use { source ->
        val data = source.readUtf8()
        val location = Location.get(path)
        val element = ProtoParser.parse(location, data)
        return ProtoFile.get(element)
      }
    }

    throw error("unexpected load: $path")
  }
}

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

/**
 * A set of rules that describes which types to generate.
 *
 * Despite the builder, instances of this class are not safe for concurrent use.
 *
 * ### Identifier Matching
 *
 * Identifiers in this set may be in the following forms:
 *
 *  * Package names, followed by `.*`, like `squareup.protos.person.*`. This matches types and
 *    services defined in the package and its descendant packages.
 *
 *  * Fully qualified type and service names, like `squareup.protos.person.Person`.
 *
 * Identifiers should not contain member names.
 *
 * This set has *included identifiers* and *excluded identifiers*, with the most precise identifier
 * taking precedence over the other ones. For instance, if there is one included identifier
 * `a.Movie` along an excluded identifier `a.*`, the type `a.Movie` is considered included in the
 * set.
 *
 * If the includes set is empty, that implies that all elements should be included. Use this to
 * exclude unwanted types and members without also including everything else.
 */
class EmittingRules private constructor(builder: Builder) {
  private val includes = builder.includes.toSet()
  private val excludes = builder.excludes.toSet()
  private val usedIncludes = mutableSetOf<String>()
  private val usedExcludes = mutableSetOf<String>()

  val isEmpty: Boolean
    get() = includes.isEmpty() && excludes.isEmpty()

  /** Returns true if `type` should be generated. */
  fun includes(type: ProtoType) = includes(type.toString())

  private fun includes(identifier: String): Boolean {
    if (includes.isEmpty()) return !exclude(identifier)

    var includeMatch: String? = null
    var excludeMatch: String? = null
    var rule: String? = identifier
    while (rule != null) {
      if (excludeMatch == null && excludes.contains(rule)) {
        excludeMatch = rule
      }
      if (includeMatch == null && includes.contains(rule)) {
        includeMatch = rule
      }
      rule = enclosing(rule)
    }

    val isIncluded = when {
      excludeMatch != null && includeMatch != null -> excludeMatch.length < includeMatch.length
      excludeMatch != null -> false
      includeMatch != null -> true
      else -> false
    }
    if (isIncluded) {
      usedIncludes.add(includeMatch!!)
    } else if (excludeMatch != null) {
      usedExcludes.add(excludeMatch)
    }
    return isIncluded
  }

  private fun exclude(identifier: String): Boolean {
    var includeMatch: String? = null
    var excludeMatch: String? = null
    var rule: String? = identifier
    while (rule != null) {
      if (excludeMatch == null && excludes.contains(rule)) {
        excludeMatch = rule
      }
      if (includeMatch == null && includes.contains(rule)) {
        includeMatch = rule
      }
      rule = enclosing(rule)
    }

    val excluded = when {
      excludeMatch != null && includeMatch != null -> excludeMatch.length >= includeMatch.length
      excludeMatch != null -> true
      else -> false
    }
    if (excluded) {
      usedExcludes.add(excludeMatch!!)
    }
    return excluded
  }

  fun unusedIncludes() = includes - usedIncludes

  fun unusedExcludes() = excludes - usedExcludes

  class Builder {
    internal val includes = mutableSetOf<String>()
    internal val excludes = mutableSetOf<String>()

    fun include(identifier: String) = apply {
      includes.add(identifier)
    }

    fun include(identifiers: Iterable<String>) = apply {
      includes.addAll(identifiers)
    }

    fun exclude(identifiers: Iterable<String>) = apply {
      excludes.addAll(identifiers)
    }

    fun exclude(identifier: String) = apply {
      excludes.add(identifier)
    }

    fun build(): EmittingRules {
      val conflictingRules = includes.intersect(excludes)
      check(conflictingRules.isEmpty()) {
        "same rule(s) defined in both includes and excludes: ${conflictingRules.joinToString()}"
      }
      return EmittingRules(this)
    }
  }

  companion object {
    /**
     * Returns the identifier or wildcard that encloses `identifier`, or null if it is not enclosed.
     *
     *  * If it is a type it returns the enclosing package with a wildcard, like
     *    `squareup.dinosaurs.*`.
     *
     *  * If it is a package with a wildcard, it returns the parent package with a wildcard, like
     *    `squareup.*`. The root wildcard is a lone asterisk, `*`.
     *
     */
    internal fun enclosing(identifier: String): String? {
      val from = if (identifier.endsWith(".*")) identifier.length - 3 else identifier.length - 1
      val dot = identifier.lastIndexOf('.', from)
      if (dot != -1) return identifier.substring(0, dot) + ".*"

      if (identifier != "*") return "*"
      return null
    }
  }
}

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.google.common.collect.LinkedHashMultimap

actual typealias Multimap<K, V> = com.google.common.collect.Multimap<K, V>

internal actual fun <K, V> Map<K, Collection<V>>.toMultimap(): Multimap<K, V> {
  val multimap = LinkedHashMultimap.create<K, V>()
  forEach { key, value ->
    multimap.putAll(key, value)
  }
  return multimap
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.google.common.io.Closer
import com.squareup.wire.schema.internal.parser.ProtoFileElement
import com.squareup.wire.schema.internal.parser.ProtoParser
import okio.Source
import okio.buffer
import okio.source
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.nio.file.FileSystems
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.util.ArrayDeque
import kotlin.DeprecationLevel.ERROR

/**
 * Load proto files and their transitive dependencies, parse them, and link them together.
 *
 * To find proto files to load, a non-empty set of sources are searched. Each source is either a
 * regular directory or a ZIP file. Within ZIP files, proto files are expected to be found relative
 * to the root of the archive.
 */
class SchemaLoader {
  /** Returns a mutable list of the sources that this loader will load from. */
  @get:JvmName("sources")
  val sources = mutableListOf<Path>()

  /** Returns a mutable list of the proto paths that this loader will load. */
  @get:JvmName("protos")
  val protos = mutableListOf<String>()

  /** Add directory or zip file source from which proto files will be loaded. */
  fun addSource(file: File) = apply {
    addSource(file.toPath())
  }

  /** Add directory or zip file source from which proto files will be loaded. */
  fun addSource(path: Path) = apply {
    sources.add(path)
  }

  @Deprecated("Moved to property", ReplaceWith("this.sources"), level = ERROR)
  @JvmName("-deprecated_sources")
  fun sources(): List<Path> = sources

  /**
   * Add a proto file path to load. Dependencies will be loaded automatically from the configured
   * sources.
   *

   * Examples:
   *

   *
{@code tea.proto}

   *
{@code coffee/espresso.proto}

   *

   */
  fun addProto(proto: String) = apply {
    protos.add(proto)
  }

  @Deprecated("Moved to property", ReplaceWith("this.protos"), level = ERROR)
  @JvmName("-deprecated_protos")
  fun protos(): List<String> = protos

  @Throws(IOException::class)
  fun load(): Schema {
    check(sources.isNotEmpty()) { "No sources added." }

    Closer.create().use { closer ->
      // Map the physical path to the file system root. For regular directories the key and the
      // value are equal. For ZIP files the key is the path to the .zip, and the value is the root
      // of the file system within it.
      val directories = mutableMapOf<Path, Path>()
      for (source in sources) {
        directories[source] = when {
          Files.isRegularFile(source) -> {
            val sourceFs = FileSystems.newFileSystem(source, javaClass.classLoader)
                .also { closer.register(it) }
            sourceFs.rootDirectories.single()
          }
          else -> {
            source
          }
        }
      }
      return loadFromDirectories(directories)
    }
  }

  @Throws(IOException::class)
  private fun loadFromDirectories(directories: Map<Path, Path>): Schema {
    val protos = ArrayDeque(this.protos)
    if (protos.isEmpty()) {
      for (value in directories.values) {
        Files.walkFileTree(value, object : SimpleFileVisitor<Path>() {
          @Throws(IOException::class)
          override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
            if (file.fileName.toString().endsWith(".proto")) {
              protos.add(value.relativize(file).toString().replace(File.separator, "/"))
            }
            return FileVisitResult.CONTINUE
          }
        })
      }
    }

    val loaded = mutableMapOf<String, ProtoFile>()
    loaded[DESCRIPTOR_PROTO] = loadSpecialProto(DESCRIPTOR_PROTO)
    loaded[ANY_PROTO] = loadSpecialProto(ANY_PROTO)

    while (!protos.isEmpty()) {
      val proto = protos.removeFirst()
      if (loaded.containsKey(proto)) continue

      var element: ProtoFileElement? = null
      for ((base, value) in directories) {
        val source = source(value, proto) ?: continue

        try {
          val location = Location.get(base.toString(), proto)
          val data = source.buffer().readUtf8()
          element = ProtoParser.parse(location, data)
          break
        } catch (e: IOException) {
          throw IOException("Failed to load $proto from $base", e)
        } finally {
          source.close()
        }
      }
      if (element == null) {
        throw FileNotFoundException("Failed to locate $proto in $sources")
      }

      loaded[proto] = ProtoFile.get(element)

      // Queue dependencies to be loaded.
      for (importPath in element.imports) {
        protos.addLast(importPath)
      }
    }

    return Linker(CoreLoader).link(loaded.values)
  }

  /**
   * Loads a special proto like "google/protobuf/descriptor.proto" from the Wire resources. If the
   * user has provided their own version of this proto, that is preferred.
   */
  @Throws(IOException::class)
  private fun loadSpecialProto(path: String): ProtoFile {
    SchemaLoader::class.java.getResourceAsStream("/$path").source().buffer().use {
      val data = it.readUtf8()
      val location = Location.get("", path)
      val element = ProtoParser.parse(location, data)
      return ProtoFile.get(element)
    }
  }

  companion object {
    private const val ANY_PROTO = "google/protobuf/any.proto"
    private const val DESCRIPTOR_PROTO = "google/protobuf/descriptor.proto"

    @Throws(IOException::class)
    private fun source(base: Path, path: String): Source? {
      val resolvedPath = base.resolve(path)
      return when {
        Files.exists(resolvedPath) -> resolvedPath.source()
        else -> null
      }
    }
  }
}

/*
 * Copyright 2020 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.json

import com.squareup.moshi.JsonReader
import okio.Buffer
import org.assertj.core.api.Assertions.assertThat

fun assertJsonEquals(a: String, b: String) {
  assertThat(JsonReader.of(Buffer().writeUtf8(a)).readJsonValue())
      .isEqualTo(JsonReader.of(Buffer().writeUtf8(b)).readJsonValue())
}

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema

import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs
import com.squareup.javapoet.JavaFile
import com.squareup.kotlinpoet.FileSpec
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.java.JavaGenerator
import com.squareup.wire.kotlin.KotlinGenerator
import com.squareup.wire.kotlin.RpcCallStyle
import com.squareup.wire.kotlin.RpcRole
import okio.buffer
import okio.source
import java.io.File
import java.io.IOException
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.Files

/**
 * Builds a repository of `.proto` and `.wire` files to create schemas, profiles, and adapters for
 * testing.
 */
class RepoBuilder {
  private val fs = Jimfs.newFileSystem(Configuration.unix())
  private val root = fs.getPath("/source")
  private val schemaLoader = NewSchemaLoader(fs)
  private var schema: Schema? = null

  fun add(name: String, protoFile: String): RepoBuilder {
    require(name.endsWith(".proto") || name.endsWith(".wire")) {
      "unexpected file extension: $name"
    }

    val relativePath = fs.getPath(name)
    try {
      val resolvedPath = root.resolve(relativePath)
      val parent = resolvedPath.parent
      if (parent != null) {
        Files.createDirectories(parent)
      }
      Files.write(resolvedPath, protoFile.toByteArray(UTF_8))
    } catch (e: IOException) {
      throw AssertionError(e)
    }

    return this
  }

  @Throws(IOException::class)
  fun add(path: String): RepoBuilder {
    val file = File("../wire-tests/src/commonTest/proto/java/$path")
    file.source().use { source ->
      val protoFile = source.buffer().readUtf8()
      return add(path, protoFile)
    }
  }

  @Throws(IOException::class)
  fun schema(): Schema {
    var result = schema
    if (result == null) {
      schemaLoader.initRoots(sourcePath = listOf(Location.get("/source")))
      result = schemaLoader.loadSchema()
      schema = result
    }
    return result
  }

  @Throws(IOException::class)
  fun profile(name: String) = schemaLoader.loadProfile(name, schema())

  @Throws(IOException::class)
  fun protoAdapter(messageTypeName: String): ProtoAdapter<Any> {
    return schema().protoAdapter(messageTypeName, true)
  }

  @Throws(IOException::class)
  @JvmOverloads fun generateCode(typeName: String, profile: String? = null): String {
    val schema = schema()
    var javaGenerator = JavaGenerator.get(schema)
    if (profile != null) {
      javaGenerator = javaGenerator.withProfile(profile(profile))
    }
    val type = schema.getType(typeName)
    val typeSpec = javaGenerator.generateType(type)
    val packageName = javaGenerator.generatedTypeName(type).packageName()
    val javaFile = JavaFile.builder(packageName, typeSpec)
        .build()
    return javaFile.toString()
  }

  fun generateKotlin(typeName: String): String {
    val schema = schema()
    val kotlinGenerator = KotlinGenerator(schema)
    val type = schema.getType(typeName)!!
    val typeSpec = kotlinGenerator.generateType(type)
    val packageName = kotlinGenerator.generatedTypeName(type).packageName
    val fileSpec = FileSpec.builder(packageName, "_")
        .addType(typeSpec)
        .addImport("com.squareup.wire.kotlin", "decodeMessage")
        .build()
    return fileSpec.toString()
  }

  fun generateGrpcKotlin(
    serviceName: String,
    rpcName: String? = null,
    rpcCallStyle: RpcCallStyle = RpcCallStyle.SUSPENDING,
    rpcRole: RpcRole = RpcRole.CLIENT
  ): String {
    val schema = schema()
    val grpcGenerator = KotlinGenerator(
        schema = schema,
        emitAndroid = false,
        javaInterop = false,
        rpcCallStyle = rpcCallStyle,
        rpcRole = rpcRole
    )
    val service = schema.getService(serviceName)!!
    val rpc = rpcName?.let { service.rpc(rpcName)!! }
    val typeSpec = grpcGenerator.generateService(service, rpc)
    val packageName = grpcGenerator.generatedServiceName(service).packageName
    val fileSpec = FileSpec.builder(packageName, "_")
        .addType(typeSpec)
        .build()
    return fileSpec.toString()
  }
}

/*
 * Copyright 2018 Square Inc.
 *
 * 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.
 */
package com.squareup.wire.testing

import okio.buffer
import okio.sink
import java.nio.file.FileSystem
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

fun FileSystem.add(pathString: String, contents: String) {
  val path = getPath(pathString)
  if (path.parent != null) {
    Files.createDirectories(path.parent)
  }
  Files.write(path, contents.toByteArray(Charsets.UTF_8))
}

fun FileSystem.symlink(linkPathString: String, targetPathString: String) {
  val linkPath = getPath(linkPathString)
  if (linkPath.parent != null) {
    Files.createDirectories(linkPath.parent)
  }
  val targetPath = getPath(targetPathString)
  Files.createSymbolicLink(linkPath, targetPath)
}

fun FileSystem.get(pathString: String): String {
  val path = getPath(pathString)
  return String(Files.readAllBytes(path), Charsets.UTF_8)
}

fun FileSystem.exists(pathString: String): Boolean {
  val path = getPath(pathString)
  return Files.exists(path)
}

fun FileSystem.find(path: String): Set<String> {
  val result = mutableSetOf<String>()
  Files.walkFileTree(getPath(path), object : SimpleFileVisitor<Path>() {
    override fun visitFile(path: Path, attrs: BasicFileAttributes): FileVisitResult {
      if (!Files.isDirectory(path)) {
        result.add(path.toString())
      }
      return FileVisitResult.CONTINUE
    }
  })
  return result
}

fun FileSystem.addZip(pathString: String, vararg contents: Pair<String, String>) {
  val path = getPath(pathString)
  if (path.parent != null) {
    Files.createDirectories(path.parent)
  }

  ZipOutputStream(Files.newOutputStream(path)).use { zipOut ->
    val zipSink = zipOut.sink().buffer()
    for ((elementPath, elementContents) in contents) {
      zipOut.putNextEntry(ZipEntry(elementPath))
      zipSink.writeUtf8(elementContents)
      zipSink.flush()
    }
  }
}

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.java.internal

import com.squareup.wire.schema.Location

/**
 * A single `.wire` file. This file is structured similarly to a `.proto` file, but with
 * different elements.
 *
 * File Structure
 * --------------
 *
 * A project may have 0 or more `.wire` files. These files should be in the same directory as
 * the `.proto` files so they may be automatically discovered by Wire.
 *
 *
 * Each file starts with a syntax declaration. The syntax must be "wire2". This is followed by an
 * optional package declaration, which should match to the package declarations of the `.proto` files in the directory.
 *
 *
 * Profiles may import any number of proto files. Note that it is an error to import `.wire` files.
 * These imports are used to resolve types specified later in the file.
 *
 *
 * Profiles may specify any number of type configurations. These specify a fully qualified
 * type, its target Java type, and an adapter to do the encoding and decoding.
 *
 * ```
 * syntax = "wire2";
 * package squareup.dinosaurs;
 *
 * import "squareup/geology/period.proto";
 *
 * // Roar!
 * type squareup.dinosaurs.Dinosaur {
 * target com.squareup.dino.Dinosaur using com.squareup.dino.Dinosaurs#DINO_ADAPTER;
 * }
 * ```
 */
data class ProfileFileElement(
  val location: Location,
  val packageName: String? = null,
  val imports: List<String> = emptyList(),
  val typeConfigs: List<TypeConfigElement> = emptyList()
) {

  fun toSchema() = buildString {
    append("// $location\n")
    append("syntax = \"wire2\";\n")
    if (packageName != null) {
      append("package $packageName;\n")
    }
    if (!imports.isEmpty()) {
      append('\n')
      for (file in imports) {
        append("import \"$file\";\n")
      }
    }
    if (!typeConfigs.isEmpty()) {
      append('\n')
      for (typeConfigElement in typeConfigs) {
        append(typeConfigElement.toSchema())
      }
    }
  }
}

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.java.internal

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.internal.parser.OptionElement
import com.squareup.wire.schema.internal.parser.OptionReader
import com.squareup.wire.schema.internal.parser.SyntaxReader

/** Parses `.wire` files.  */
class ProfileParser(
  private val location: Location,
  data: String
) {
  private val reader = SyntaxReader(data.toCharArray(), location)
  private val imports = mutableListOf<String>()
  private val typeConfigs = mutableListOf<TypeConfigElement>()

  /** Output package name, or null if none yet encountered.  */
  private var packageName: String? = null

  fun read(): ProfileFileElement {
    val label = reader.readWord()
    reader.expect(label == "syntax") { "expected 'syntax'" }
    reader.require('=')
    val syntaxString = reader.readQuotedString()
    reader.expect(syntaxString == "wire2") { "expected 'wire2'" }
    reader.require(';')

    while (true) {
      val documentation = reader.readDocumentation()
      if (reader.exhausted()) {
        return ProfileFileElement(
            location = location,
            packageName = packageName,
            imports = imports,
            typeConfigs = typeConfigs
        )
      }

      readDeclaration(documentation)
    }
  }

  private fun readDeclaration(documentation: String) {
    val location = reader.location()
    val label = reader.readWord()

    when (label) {
      "package" -> {
        reader.expect(packageName == null, location) { "too many package names" }
        packageName = reader.readName()
        reader.require(';')
      }
      "import" -> {
        val importString = reader.readString()
        imports.add(importString)
        reader.require(';')
      }
      "type" -> typeConfigs.add(readTypeConfig(location, documentation))
      else -> throw reader.unexpected("unexpected label: $label", location)
    }
  }

  /** Reads a type config and returns it.  */
  private fun readTypeConfig(
    location: Location,
    documentation: String
  ): TypeConfigElement {
    val name = reader.readDataType()
    val withOptions = mutableListOf<OptionElement>()
    var target: String? = null
    var adapter: String? = null

    reader.require('{')
    while (!reader.peekChar('}')) {
      val wordLocation = reader.location()
      val word = reader.readWord()
      when (word) {
        "target" -> {
          reader.expect(target == null, wordLocation) { "too many targets" }
          target = reader.readWord()
          reader.expect(reader.readWord() == "using") { "expected 'using'" }
          val adapterType = reader.readWord()
          reader.require('#')
          val adapterConstant = reader.readWord()
          reader.require(';')
          adapter = "$adapterType#$adapterConstant"
        }

        "with" -> {
          withOptions += OptionReader(reader).readOption('=')
          reader.require(';')
        }

        else -> throw reader.unexpected("unexpected label: $word", wordLocation)
      }
    }

    return TypeConfigElement(
        location = location,
        type = name,
        documentation = documentation,
        with = withOptions,
        target = target,
        adapter = adapter
    )
  }
}

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.java.internal

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendIndented
import com.squareup.wire.schema.internal.parser.OptionElement

/**
 * Configures how Wire will generate code for a specific type. This configuration belongs in a
 * `build.wire` file that is in the same directory as the configured type.
 */
data class TypeConfigElement(
  val location: Location,
  val type: String? = null,
  val documentation: String = "",
  val with: List<OptionElement> = emptyList(),
  val target: String? = null,
  val adapter: String? = null
) {
  fun toSchema() = buildString {
    appendDocumentation(documentation)
    append("type $type {\n")
    for (option in with) {
      appendIndented("with ${option.toSchema()};\n")
    }
    append("  target $target using $adapter;\n")
    append("}\n")
  }
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal

import com.squareup.wire.ProtoAdapter
import com.squareup.wire.schema.Schema
import com.squareup.wire.schema.internal.parser.OptionElement

// TODO internal and friend for wire-java-generator: https://youtrack.jetbrains.com/issue/KT-20760
fun StringBuilder.appendDocumentation(
  documentation: String
) {
  if (documentation.isEmpty()) {
    return
  }
  var lines = documentation.split("\n")
  if (lines.count() > 1 && lines.last().isEmpty()) {
    lines = lines.dropLast(1)
  }
  for (line in lines) {
    append("// ")
        .append(line)
        .append('\n')
  }
}

internal fun StringBuilder.appendOptions(
  options: List<OptionElement>
) {
  val count = options.size
  if (count == 1) {
    append('[')
        .append(options[0].toSchema())
        .append(']')
    return
  }
  append("[\n")
  for (i in 0 until count) {
    val endl = if (i < count - 1) "," else ""
    appendIndented(options[i].toSchema() + endl)
  }
  append(']')
}

// TODO internal and friend for wire-java-generator: https://youtrack.jetbrains.com/issue/KT-20760
fun StringBuilder.appendIndented(
  value: String
) {
  var lines = value.split("\n")
  if (lines.count() > 1 && lines.last().isEmpty()) {
    lines = lines.dropLast(1)
  }
  for (line in lines) {
    append("  ")
        .append(line)
        .append('\n')
  }
}

internal const val MIN_TAG_VALUE = 1
internal const val MAX_TAG_VALUE = (1 shl 29) - 1 // 536,870,911

private const val RESERVED_TAG_VALUE_START = 19000
private const val RESERVED_TAG_VALUE_END = 19999

/** True if the supplied value is in the valid tag range and not reserved.  */
internal fun Int.isValidTag() =
  this in MIN_TAG_VALUE until RESERVED_TAG_VALUE_START ||
      this in (RESERVED_TAG_VALUE_END + 1) until MAX_TAG_VALUE + 1

internal expect fun Char.isDigit(): Boolean

internal expect fun String.toEnglishLowerCase(): String

expect interface MutableQueue<T : Any> : MutableCollection<T> {
  fun poll(): T?
}

internal expect fun <T : Any> mutableQueueOf(): MutableQueue<T>

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal

internal actual fun Char.isDigit() = this in '0'..'9'

internal actual fun String.toEnglishLowerCase() = toLowerCase()

actual interface MutableQueue<T : Any> : MutableCollection<T> {
  actual fun poll(): T?
}

internal actual fun <T : Any> mutableQueueOf(): MutableQueue<T> {
  val queue = mutableListOf<T>()
  return object : MutableQueue<T>, MutableCollection<T> by queue {
    override fun poll() = firstOrNull()
  }
}

/*
 * Copyright (C) 2019 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal

import java.util.ArrayDeque
import java.util.Locale
import java.util.Queue

@Suppress("NOTHING_TO_INLINE") // Aliasing to platform method.
internal actual inline fun Char.isDigit() = Character.isDigit(this)

@Suppress("NOTHING_TO_INLINE") // Aliasing to platform method.
internal actual inline fun String.toEnglishLowerCase() = toLowerCase(Locale.US)

actual typealias MutableQueue<T> = Queue<T>

internal actual fun <T : Any> mutableQueueOf(): MutableQueue<T> = ArrayDeque()

/*
 * Copyright (C) 2014 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendOptions

data class EnumConstantElement(
  val location: Location,
  val name: String,
  val tag: Int,
  val documentation: String = "",
  val options: List<OptionElement> = emptyList()
) {

  fun toSchema() = buildString {
    appendDocumentation(documentation)
    append("$name = $tag")

    if (options.isNotEmpty()) {
      append(" ")
      appendOptions(options)
    }
    append(";\n")
  }
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.SyntaxRules
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendIndented

data class EnumElement(
  override val location: Location,
  override val name: String,
  override val documentation: String = "",
  override val options: List<OptionElement> = emptyList(),
  val constants: List<EnumConstantElement> = emptyList()
) : TypeElement {
  // Enums do not allow nested type declarations.
  override val nestedTypes: List<TypeElement> = emptyList()

  override fun toSchema(syntaxRules: SyntaxRules) = buildString {
    appendDocumentation(documentation)
    append("enum $name {")

    if (options.isNotEmpty() || constants.isNotEmpty()) {
      append('\n')
    }

    if (options.isNotEmpty()) {
      for (option in options) {
        appendIndented(option.toSchemaDeclaration())
      }
    }
    if (constants.isNotEmpty()) {
      for (constant in constants) {
        appendIndented(constant.toSchema())
      }
    }
    append("}\n")
  }
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.SyntaxRules
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendIndented

data class ExtendElement(
  val location: Location,
  val name: String,
  val documentation: String = "",
  val fields: List<FieldElement> = emptyList()
) {
  fun toSchema(syntaxRules: SyntaxRules = SyntaxRules.get(syntax = null)) = buildString {
    appendDocumentation(documentation)
    append("extend $name {")

    if (fields.isNotEmpty()) {
      append('\n')
      for (field in fields) {
        appendIndented(field.toSchema(syntaxRules))
      }
    }

    append("}\n")
  }
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.internal.MAX_TAG_VALUE
import com.squareup.wire.schema.internal.appendDocumentation

data class ExtensionsElement(
  val location: Location,
  val documentation: String = "",
  /** An [Int] or [IntRange] tag. */
  val values: List<Any>
) {
  fun toSchema() = buildString {
    appendDocumentation(documentation)
    append("extensions ")

    values.forEachIndexed { index, value ->
      if (index > 0) append(", ")

      when (value) {
        is Int -> append(value)
        is IntRange -> {
          append("${value.first} to ")
          if (value.last < MAX_TAG_VALUE) {
            append(value.last)
          } else {
            append("max")
          }
        }
        else -> throw AssertionError()
      }
    }
    append(";\n")
  }
}

/*
 * Copyright (C) 2014 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Field
import com.squareup.wire.schema.Location
import com.squareup.wire.schema.ProtoType
import com.squareup.wire.schema.SyntaxRules
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendOptions
import com.squareup.wire.schema.internal.toEnglishLowerCase

data class FieldElement(
  val location: Location,
  val label: Field.Label? = null,
  val type: String,
  val name: String,
  val defaultValue: String? = null,
  val tag: Int = 0,
  val documentation: String = "",
  val options: List<OptionElement> = emptyList()
) {
  fun toSchema(syntaxRules: SyntaxRules = SyntaxRules.get(syntax = null)) = buildString {
    appendDocumentation(documentation)

    if (label != null) {
      append("${label.name.toEnglishLowerCase()} ")
    }
    append("$type $name = $tag")

    val optionsWithDefault = optionsWithDefaultValue(syntaxRules)
    if (optionsWithDefault.isNotEmpty()) {
      append(' ')
      appendOptions(optionsWithDefault)
    }

    append(";\n")
  }

  private fun optionsWithDefaultValue(syntaxRules: SyntaxRules): List<OptionElement> {
    if (defaultValue == null || !syntaxRules.allowUserDefinedDefaultValue()) {
      return options
    }

    val protoType = ProtoType.get(type)

    return options + OptionElement.create("default", protoType.toKind(), defaultValue)
  }

  // Only non-repeated scalar types and Enums support default values.
  private fun ProtoType.toKind(): OptionElement.Kind {
    return when (simpleName) {
      "bool" -> OptionElement.Kind.BOOLEAN
      "string" -> OptionElement.Kind.STRING
      "bytes",
      "double",
      "float",
      "fixed32",
      "fixed64",
      "int32",
      "int64",
      "sfixed32",
      "sfixed64",
      "sint32",
      "sint64",
      "uint32",
      "uint64" -> OptionElement.Kind.NUMBER
      else -> OptionElement.Kind.ENUM
    }
  }
}

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Field
import com.squareup.wire.schema.Location
import com.squareup.wire.schema.SyntaxRules
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendIndented
import com.squareup.wire.schema.internal.toEnglishLowerCase

data class GroupElement(
  val label: Field.Label? = null,
  val location: Location,
  val name: String,
  val tag: Int,
  val documentation: String = "",
  val fields: List<FieldElement> = emptyList()
) {
  fun toSchema(syntaxRules: SyntaxRules = SyntaxRules.get(syntax = null)) = buildString {
    appendDocumentation(documentation)
    if (label != null) {
      append("${label.name.toEnglishLowerCase()} ")
    }
    append("group $name = $tag {")
    if (fields.isNotEmpty()) {
      append('\n')
      for (field in fields) {
        appendIndented(field.toSchema(syntaxRules))
      }
    }
    append("}\n")
  }
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.SyntaxRules
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendIndented

data class MessageElement(
  override val location: Location,
  override val name: String,
  override val documentation: String = "",
  override val nestedTypes: List<TypeElement> = emptyList(),
  override val options: List<OptionElement> = emptyList(),
  val reserveds: List<ReservedElement> = emptyList(),
  val fields: List<FieldElement> = emptyList(),
  val oneOfs: List<OneOfElement> = emptyList(),
  val extensions: List<ExtensionsElement> = emptyList(),
  val groups: List<GroupElement> = emptyList()
) : TypeElement {
  override fun toSchema(syntaxRules: SyntaxRules) = buildString {
    appendDocumentation(documentation)
    append("message $name {")

    if (reserveds.isNotEmpty()) {
      append('\n')
      for (reserved in reserveds) {
        appendIndented(reserved.toSchema())
      }
    }
    if (options.isNotEmpty()) {
      append('\n')
      for (option in options) {
        appendIndented(option.toSchemaDeclaration())
      }
    }
    if (fields.isNotEmpty()) {
      append('\n')
      for (field in fields) {
        appendIndented(field.toSchema(syntaxRules))
      }
    }
    if (oneOfs.isNotEmpty()) {
      append('\n')
      for (oneOf in oneOfs) {
        appendIndented(oneOf.toSchema(syntaxRules))
      }
    }
    if (groups.isNotEmpty()) {
      append('\n')
      for (group in groups) {
        appendIndented(group.toSchema(syntaxRules))
      }
    }
    if (extensions.isNotEmpty()) {
      append('\n')
      for (extension in extensions) {
        appendIndented(extension.toSchema())
      }
    }
    if (nestedTypes.isNotEmpty()) {
      append('\n')
      for (type in nestedTypes) {
        appendIndented(type.toSchema(syntaxRules))
      }
    }
    append("}\n")
  }
}

/*
 * Copyright (C) 2014 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.SyntaxRules
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendIndented

data class OneOfElement(
  val name: String,
  val documentation: String = "",
  val fields: List<FieldElement> = emptyList(),
  val groups: List<GroupElement> = emptyList()
) {
  fun toSchema(syntaxRules: SyntaxRules = SyntaxRules.get(syntax = null)) = buildString {
    appendDocumentation(documentation)
    append("oneof $name {")

    if (fields.isNotEmpty()) {
      append('\n')
      for (field in fields) {
        appendIndented(field.toSchema(syntaxRules))
      }
    }
    if (groups.isNotEmpty()) {
      append('\n')
      for (group in groups) {
        appendIndented(group.toSchema(syntaxRules))
      }
    }
    append("}\n")
  }
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.internal.appendIndented
import com.squareup.wire.schema.internal.appendOptions
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.BOOLEAN
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.ENUM
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.LIST
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.MAP
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.NUMBER
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.OPTION
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.STRING
import kotlin.jvm.JvmOverloads

data class OptionElement(
  val name: String,
  val kind: Kind,
  val value: Any,
  /** If true, this [OptionElement] is a custom option. */
  val isParenthesized: Boolean
) {
  enum class Kind {
    STRING,
    BOOLEAN,
    NUMBER,
    ENUM,
    MAP,
    LIST,
    OPTION
  }
  private val formattedName = if (isParenthesized) "($name)" else name

  fun toSchema(): String = buildString {
    when (kind) {
      STRING -> append("""$formattedName = "$value"""")
      BOOLEAN,
      NUMBER,
      ENUM -> append("$formattedName = $value")
      OPTION -> {
        // Treat nested options as non-parenthesized always, prevents double parentheses.
        val optionValue = (value as OptionElement).copy()
        append("$formattedName.${optionValue.toSchema()}")
      }
      MAP -> {
        append("$formattedName = {\n")
        formatOptionMap(this, value as Map<String, *>)
        append('}')
      }
      LIST -> {
        append("$formattedName = ")
        appendOptions(value as List<OptionElement>)
      }
    }
  }

  fun toSchemaDeclaration() = "option ${toSchema()};\n"

  private fun formatOptionMap(
    builder: StringBuilder,
    valueMap: Map<String, *>
  ) {
    val lastIndex = valueMap.size - 1
    valueMap.entries.forEachIndexed { index, entry ->
      val endl = if (index != lastIndex) "," else ""
      builder.appendIndented("${entry.key}: ${formatOptionMapValue(entry.value!!)}$endl")
    }
  }

  private fun formatOptionMapValue(value: Any): String = buildString {
    when (value) {
      is String -> {
        append(""""$value"""")
      }
      is Map<*, *> -> {
        append("{\n")
        formatOptionMap(this, value as Map<String, *>)
        append('}')
      }
      is List<*> -> {
        append("[\n")
        val lastIndex = value.size - 1
        value.forEachIndexed { index, item ->
          val endl = if (index != lastIndex) "," else ""
          appendIndented("${formatOptionMapValue(item!!)}$endl")
        }
        append("]")
      }
      else -> {
        append(value)
      }
    }
  }

  companion object {
    internal val PACKED_OPTION_ELEMENT =
        OptionElement("packed", BOOLEAN, value = "true", isParenthesized = false)

    @JvmOverloads
    fun create(
      name: String,
      kind: Kind,
      value: Any,
      isParenthesized: Boolean = false
    ) = OptionElement(name, kind, value, isParenthesized)
  }
}

/*
 * Copyright (C) 2017 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.internal.isDigit
import com.squareup.wire.schema.internal.parser.OptionElement.Kind
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.BOOLEAN
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.ENUM
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.LIST
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.MAP
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.NUMBER
import com.squareup.wire.schema.internal.parser.OptionElement.Kind.STRING

class OptionReader(internal val reader: SyntaxReader) {

  /**
   * Reads options enclosed in '[' and ']' if they are present and returns them. Returns an empty
   * list if no options are present.
   */
  fun readOptions(): List<OptionElement> {
    if (!reader.peekChar('[')) return emptyList()

    val result = mutableListOf<OptionElement>()
    while (true) {
      result += readOption('=')

      // Check for closing ']'
      if (reader.peekChar(']')) break

      // Discard optional ','.
      reader.expect(reader.peekChar(',')) { "Expected ',' or ']" }
    }
    return result
  }

  /** Reads a option containing a name, an '=' or ':', and a value.  */
  fun readOption(keyValueSeparator: Char): OptionElement {
    val isExtension = reader.peekChar() == '['
    val isParenthesized = reader.peekChar() == '('
    var name = reader.readName() // Option name.
    if (isExtension) name = "[$name]"

    var subName: String? = null
    var c = reader.readChar()
    if (c == '.') {
      // Read nested field name. For example "baz" in "(foo.bar).baz = 12".
      subName = reader.readName()
      c = reader.readChar()
    }
    if (keyValueSeparator == ':' && c == '{') {
      // In text format, values which are maps can omit a separator. Backtrack so it can be re-read.
      reader.pushBack('{')
    } else {
      reader.expect(c == keyValueSeparator) { "expected '$keyValueSeparator' in option" }
    }
    val kindAndValue = readKindAndValue()
    var kind = kindAndValue.kind
    var value = kindAndValue.value
    if (subName != null) {
      value = OptionElement.create(subName, kind, value)
      kind = Kind.OPTION
    }
    return OptionElement.create(name, kind, value, isParenthesized)
  }

  /** Reads a value that can be a map, list, string, number, boolean or enum.  */
  private fun readKindAndValue(): KindAndValue {
    when (val peeked = reader.peekChar()) {
      '{' -> return KindAndValue(MAP, readMap('{', '}', ':'))
      '[' -> return KindAndValue(LIST, readList())
      '"', '\'' -> return KindAndValue(STRING, reader.readString())
      else -> {
        if (peeked.isDigit() || peeked == '-') {
          return KindAndValue(NUMBER, reader.readWord())
        }
        return when (val word = reader.readWord()) {
          "true" -> KindAndValue(BOOLEAN, "true")
          "false" -> KindAndValue(BOOLEAN, "false")
          else -> KindAndValue(ENUM, word)
        }
      }
    }
  }

  /**
   * Returns a map of string keys and values. This is similar to a JSON object, with '{' and '}'
   * surrounding the map, ':' separating keys from values, and ',' separating entries.
   */
  private fun readMap(
    openBrace: Char,
    closeBrace: Char,
    keyValueSeparator: Char
  ): Map<String, Any> {
    if (reader.readChar() != openBrace) throw AssertionError()
    val result = mutableMapOf<String, Any>()
    while (true) {
      if (reader.peekChar(closeBrace)) {
        // If we see the close brace, finish immediately. This handles {}/[] and ,}/,] cases.
        return result
      }

      val option = readOption(keyValueSeparator)
      val name = option.name
      val value = option.value
      if (value is OptionElement) {
        var nested = result[name] as? MutableMap<String, Any>
        if (nested == null) {
          nested = LinkedHashMap()
          result[name] = nested
        }
        nested[value.name] = value.value
      } else {
        // Add the value(s) to any previous values with the same key
        val previous = result[name]
        when (previous) {
          null -> result[name] = value
          is List<*> -> // Add to previous List
            addToList(previous as MutableList<Any>, value)
          else -> {
            val newList = ArrayList<Any>()
            newList.add(previous)
            addToList(newList, value)
            result[name] = newList
          }
        }
      }

      // Discard optional ',' separator.
      reader.peekChar(',')
    }
  }

  /** Adds an object or objects to a List.  */
  private fun addToList(
    list: MutableList<Any>,
    value: Any
  ) {
    if (value is List<*>) {
      list.addAll(value as Collection<Any>)
    } else {
      list.add(value)
    }
  }

  /**
   * Returns a list of values. This is similar to JSON with '[' and ']' surrounding the list and ','
   * separating values.
   */
  private fun readList(): List<Any> {
    reader.require('[')
    val result = mutableListOf<Any>()
    while (true) {
      // If we see the close brace, finish immediately. This handles [] and ,] cases.
      if (reader.peekChar(']')) return result

      result.add(readKindAndValue().value)

      if (reader.peekChar(',')) continue
      reader.expect(reader.peekChar() == ']') { "expected ',' or ']'" }
    }
  }

  internal data class KindAndValue(
    internal val kind: Kind,
    internal val value: Any
  )
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.ProtoFile
import com.squareup.wire.schema.SyntaxRules
import kotlin.jvm.JvmStatic

/** A single `.proto` file.  */
data class ProtoFileElement(
  val location: Location,
  val packageName: String? = null,
  val syntax: ProtoFile.Syntax? = null,
  val imports: List<String> = emptyList(),
  val publicImports: List<String> = emptyList(),
  val types: List<TypeElement> = emptyList(),
  val services: List<ServiceElement> = emptyList(),
  val extendDeclarations: List<ExtendElement> = emptyList(),
  val options: List<OptionElement> = emptyList()
) {
  fun toSchema() = buildString {
    val syntaxRules = SyntaxRules.get(syntax)

    append("// ")
    append(location.withPathOnly())
    append('\n')

    if (syntax != null) {
       append("syntax = \"$syntax\";\n")
    }
    if (packageName != null) {
       append("package $packageName;\n")
    }
    if (imports.isNotEmpty() || publicImports.isNotEmpty()) {
      append('\n')
      for (file in imports) {
        append("import \"$file\";\n")
      }
      for (file in publicImports) {
        append("import public \"$file\";\n")
      }
    }
    if (options.isNotEmpty()) {
      append('\n')
      for (option in options) {
        append(option.toSchemaDeclaration())
      }
    }
    if (types.isNotEmpty()) {
      append('\n')
      for (typeElement in types) {
        append(typeElement.toSchema(syntaxRules))
      }
    }
    if (extendDeclarations.isNotEmpty()) {
      append('\n')
      for (extendDeclaration in extendDeclarations) {
        append(extendDeclaration.toSchema(syntaxRules))
      }
    }
    if (services.isNotEmpty()) {
      append('\n')
      for (service in services) {
        append(service.toSchema())
      }
    }
  }

  companion object {
    /** Returns an empty proto file to serve as a null object when a file cannot be found. */
    @JvmStatic
    fun empty(path: String): ProtoFileElement {
      return ProtoFileElement(location = Location.get(path))
    }
  }
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Field
import com.squareup.wire.schema.Field.Label.REQUIRED
import com.squareup.wire.schema.Location
import com.squareup.wire.schema.ProtoFile
import com.squareup.wire.schema.ProtoFile.Syntax.PROTO_3
import com.squareup.wire.schema.internal.MAX_TAG_VALUE

/** Basic parser for `.proto` schema declarations. */
class ProtoParser internal constructor(
  private val location: Location,
  data: CharArray
) {
  private val reader: SyntaxReader = SyntaxReader(data, location)
  private val publicImports = mutableListOf<String>()
  private val imports = mutableListOf<String>()
  private val nestedTypes = mutableListOf<TypeElement>()
  private val services = mutableListOf<ServiceElement>()
  private val extendsList = mutableListOf<ExtendElement>()
  private val options = mutableListOf<OptionElement>()

  /** The number of declarations defined in the current file. */
  private var declarationCount = 0

  /** The syntax of the file, or null if none is defined. */
  private var syntax: ProtoFile.Syntax? = null

  /** Output package name, or null if none yet encountered. */
  private var packageName: String? = null

  /** The current package name + nested type names, separated by dots. */
  private var prefix = ""

  fun readProtoFile(): ProtoFileElement {
    while (true) {
      val documentation = reader.readDocumentation()
      if (reader.exhausted()) {
        return ProtoFileElement(
            location = location,
            packageName = packageName,
            syntax = syntax,
            imports = imports,
            publicImports = publicImports,
            types = nestedTypes,
            services = services,
            extendDeclarations = extendsList,
            options = options
        )
      }

      when (val declaration = readDeclaration(documentation, Context.FILE)) {
        is TypeElement -> nestedTypes.add(declaration)
        is ServiceElement -> services.add(declaration)
        is OptionElement -> options.add(declaration)
        is ExtendElement -> extendsList.add(declaration)
      }
    }
  }

  private fun readDeclaration(documentation: String, context: Context): Any? {
    val index = declarationCount++

    // Skip unnecessary semicolons, occasionally used after a nested message declaration.
    if (reader.peekChar(';')) return null

    val location = reader.location()
    val label = reader.readWord()

    return when {
      label == "package" -> {
        reader.expect(context.permitsPackage(), location) { "'package' in $context" }
        reader.expect(packageName == null, location) { "too many package names" }
        packageName = reader.readName()
        prefix = "$packageName."
        reader.require(';')
        null
      }

      label == "import" -> {
        reader.expect(context.permitsImport(), location) { "'import' in $context" }
        when (val importString = reader.readString()) {
          "public" -> publicImports.add(reader.readString())
          else -> imports.add(importString)
        }
        reader.require(';')
        null
      }

      label == "syntax" -> {
        reader.expect(context.permitsSyntax(), location) { "'syntax' in $context" }
        reader.require('=')
        reader.expect(index == 0, location) {
          "'syntax' element must be the first declaration in a file"
        }
        val syntaxString = reader.readQuotedString()
        try {
          syntax = ProtoFile.Syntax[syntaxString]
        } catch (e: IllegalArgumentException) {
          throw reader.unexpected(e.message!!, location)
        }
        reader.require(';')
        null
      }

      label == "option" -> {
        OptionReader(reader).readOption('=').also {
          reader.require(';')
        }
      }

      label == "reserved" -> readReserved(location, documentation)
      label == "message" -> readMessage(location, documentation)
      label == "enum" -> readEnumElement(location, documentation)
      label == "service" -> readService(location, documentation)
      label == "extend" -> readExtend(location, documentation)

      label == "rpc" -> {
        reader.expect(context.permitsRpc(), location) { "'rpc' in $context" }
        readRpc(location, documentation)
      }

      label == "oneof" -> {
        reader.expect(context.permitsOneOf(), location) { "'oneof' must be nested in message" }
        readOneOf(documentation)
      }

      label == "extensions" -> {
        reader.expect(context.permitsExtensions(), location) { "'extensions' must be nested" }
        readExtensions(location, documentation)
      }

      context == Context.MESSAGE || context == Context.EXTEND -> {
        readField(documentation, location, label)
      }

      context == Context.ENUM -> {
        readEnumConstant(documentation, location, label)
      }

      else -> throw reader.unexpected("unexpected label: $label", location)
    }
  }

  /** Reads a message declaration. */
  private fun readMessage(
    location: Location,
    documentation: String
  ): MessageElement {
    val name = reader.readName()
    val fields = mutableListOf<FieldElement>()
    val oneOfs = mutableListOf<OneOfElement>()
    val nestedTypes = mutableListOf<TypeElement>()
    val extensions = mutableListOf<ExtensionsElement>()
    val options = mutableListOf<OptionElement>()
    val reserveds = mutableListOf<ReservedElement>()
    val groups = mutableListOf<GroupElement>()

    val previousPrefix = prefix
    prefix = "$prefix$name."

    reader.require('{')
    while (true) {
      val nestedDocumentation = reader.readDocumentation()
      if (reader.peekChar('}')) break

      when (val declared = readDeclaration(nestedDocumentation, Context.MESSAGE)) {
        is FieldElement -> fields.add(declared)
        is OneOfElement -> oneOfs.add(declared)
        is GroupElement -> groups.add(declared)
        is TypeElement -> nestedTypes.add(declared)
        is ExtensionsElement -> extensions.add(declared)
        is OptionElement -> options.add(declared)
        // Extend declarations always add in a global scope regardless of nesting.
        is ExtendElement -> extendsList.add(declared)
        is ReservedElement -> reserveds.add(declared)
      }
    }

    prefix = previousPrefix

    return MessageElement(
        location = location,
        name = name,
        documentation = documentation,
        nestedTypes = nestedTypes,
        options = options,
        reserveds = reserveds,
        fields = fields,
        oneOfs = oneOfs,
        extensions = extensions,
        groups = groups
    )
  }

  /** Reads an extend declaration. */
  private fun readExtend(location: Location, documentation: String): ExtendElement {
    val name = reader.readName()
    val fields = mutableListOf<FieldElement>()

    reader.require('{')
    while (true) {
      val nestedDocumentation = reader.readDocumentation()
      if (reader.peekChar('}')) break

      when (val declared = readDeclaration(nestedDocumentation, Context.EXTEND)) {
        is FieldElement -> fields.add(declared)
        // TODO: add else clause to catch unexpected declarations.
      }
    }

    return ExtendElement(
        location = location,
        name = name,
        documentation = documentation,
        fields = fields
    )
  }

  /** Reads a service declaration and returns it. */
  private fun readService(location: Location, documentation: String): ServiceElement {
    val name = reader.readName()
    val rpcs = mutableListOf<RpcElement>()
    val options = mutableListOf<OptionElement>()

    reader.require('{')
    while (true) {
      val rpcDocumentation = reader.readDocumentation()
      if (reader.peekChar('}')) break

      when (val declared = readDeclaration(rpcDocumentation, Context.SERVICE)) {
        is RpcElement -> rpcs.add(declared)
        is OptionElement -> options.add(declared)
        // TODO: add else clause to catch unexpected declarations.
      }
    }

    return ServiceElement(
        location = location,
        name = name,
        documentation = documentation,
        rpcs = rpcs,
        options = options
    )
  }

  /** Reads an enumerated type declaration and returns it. */
  private fun readEnumElement(
    location: Location,
    documentation: String
  ): EnumElement {
    val name = reader.readName()
    val constants = mutableListOf<EnumConstantElement>()
    val options = mutableListOf<OptionElement>()

    reader.require('{')
    while (true) {
      val valueDocumentation = reader.readDocumentation()
      if (reader.peekChar('}')) break

      when (val declared = readDeclaration(valueDocumentation, Context.ENUM)) {
        is EnumConstantElement -> constants.add(declared)
        is OptionElement -> options.add(declared)
        // TODO: add else clause to catch unexpected declarations.
      }
    }

    return EnumElement(location, name, documentation, options, constants)
  }

  private fun readField(documentation: String, location: Location, word: String): Any {
    val label: Field.Label?
    val type: String
    when (word) {
      "required" -> {
        reader.expect(syntax != PROTO_3, location) {
          "'required' label forbidden in proto3 field declarations"
        }
        label = REQUIRED
        type = reader.readDataType()
      }

      "optional" -> {
        reader.expect(syntax != PROTO_3, location) {
          "'optional' label forbidden in proto3 field declarations"
        }
        label = Field.Label.OPTIONAL
        type = reader.readDataType()
      }

      "repeated" -> {
        label = Field.Label.REPEATED
        type = reader.readDataType()
      }

      else -> {
        reader.expect(syntax == PROTO_3 ||
            (word == "map" && reader.peekChar() == '<'), location) {
          "unexpected label: $word"
        }
        label = null
        type = reader.readDataType(word)
      }
    }

    reader.expect(!type.startsWith("map<") || label == null, location) {
      "'map' type cannot have label"
    }

    return when (type) {
      "group" -> readGroup(location, documentation, label)
      else -> readField(location, documentation, label, type)
    }
  }

  /** Reads an field declaration and returns it. */
  private fun readField(
    location: Location,
    documentation: String,
    label: Field.Label?,
    type: String
  ): FieldElement {
    var documentation = documentation

    val name = reader.readName()
    reader.require('=')
    val tag = reader.readInt()

    // Mutable copy to extract the default value, and add packed if necessary.
    val options: MutableList<OptionElement> = OptionReader(reader).readOptions().toMutableList()

    val defaultValue = stripDefault(options)
    reader.require(';')

    documentation = reader.tryAppendTrailingDocumentation(documentation)

    return FieldElement(
        location = location,
        label = label,
        type = type,
        name = name,
        defaultValue = defaultValue,
        tag = tag,
        documentation = documentation,
        options = options.toList()
    )
  }

  /**
   * Defaults aren't options. This finds an option named "default", removes, and returns it. Returns
   * null if no default option is present.
   */
  private fun stripDefault(options: MutableList<OptionElement>): String? {
    var result: String? = null
    val i = options.iterator()
    while (i.hasNext()) {
      val element = i.next()
      if (element.name == "default") {
        i.remove()
        result = element.value.toString() // Defaults aren't options!
      }
    }
    return result
  }

  private fun readOneOf(documentation: String): OneOfElement {
    val name = reader.readName()
    val fields = mutableListOf<FieldElement>()
    val groups = mutableListOf<GroupElement>()

    reader.require('{')
    while (true) {
      val nestedDocumentation = reader.readDocumentation()
      if (reader.peekChar('}')) break

      val location = reader.location()
      when (val type = reader.readDataType()) {
        "group" -> groups.add(readGroup(location, nestedDocumentation, null))
        else -> fields.add(readField(location, nestedDocumentation, null, type))
      }
    }

    return OneOfElement(
        name = name,
        documentation = documentation,
        fields = fields,
        groups = groups
    )
  }

  private fun readGroup(
    location: Location,
    documentation: String,
    label: Field.Label?
  ): GroupElement {
    val name = reader.readWord()
    reader.require('=')
    val tag = reader.readInt()
    val fields = mutableListOf<FieldElement>()

    reader.require('{')
    while (true) {
      val nestedDocumentation = reader.readDocumentation()
      if (reader.peekChar('}')) break

      val fieldLocation = reader.location()
      val fieldLabel = reader.readWord()
      when (val field = readField(nestedDocumentation, fieldLocation, fieldLabel)) {
        is FieldElement -> fields.add(field)
        else -> throw reader.unexpected("expected field declaration, was $field")
      }
    }

    return GroupElement(
        label = label,
        location = location,
        name = name,
        tag = tag,
        documentation = documentation,
        fields = fields
    )
  }

  /** Reads a reserved tags and names list like "reserved 10, 12 to 14, 'foo';". */
  private fun readReserved(location: Location, documentation: String): ReservedElement {
    var documentation = documentation
    val values = mutableListOf<Any>()

    loop@ while (true) {
      when (reader.peekChar()) {
        '"', '\'' -> values.add(reader.readQuotedString())

        else -> {
          val tagStart = reader.readInt()
          when (reader.peekChar()) {
            ',', ';' -> values.add(tagStart)

            else -> {
              reader.expect(reader.readWord() == "to", location) { "expected ',', ';', or 'to'" }
              val tagEnd = reader.readInt()
              values.add(tagStart..tagEnd)
            }
          }
        }
      }

      when (reader.readChar()) {
        ';' -> break@loop
        ',' -> continue@loop
        else -> throw reader.unexpected("expected ',' or ';'")
      }
    }

    reader.expect(values.isNotEmpty(), location) {
      "'reserved' must have at least one field name or tag"
    }

    documentation = reader.tryAppendTrailingDocumentation(documentation)

    return ReservedElement(
        location = location,
        documentation = documentation,
        values = values
    )
  }

  /** Reads extensions like "extensions 101;" or "extensions 101 to max;". */
  private fun readExtensions(
    location: Location,
    documentation: String
  ): ExtensionsElement {
    val values = mutableListOf<Any>()
    loop@ while (true) {
      val start = reader.readInt()

      when (reader.peekChar()) {
        ',', ';' -> values.add(start)
        else -> {
          reader.expect(reader.readWord() == "to", location) { "expected ',', ';' or 'to'" }
          val end = when (val s = reader.readWord()) {
            "max" -> MAX_TAG_VALUE
            else -> s.toInt()
          }
          values.add(start..end)
        }
      }
      when (reader.readChar()) {
        ';' -> break@loop
        ',' -> continue@loop
        else -> throw reader.unexpected("expected ',' or ';'")
      }
    }

    return ExtensionsElement(
        location = location,
        documentation = documentation,
        values = values
    )
  }

  /** Reads an enum constant like "ROCK = 0;". The label is the constant name. */
  private fun readEnumConstant(
    documentation: String, location: Location, label: String
  ): EnumConstantElement {
    var documentation = documentation

    reader.require('=')
    val tag = reader.readInt()

    val options = OptionReader(reader).readOptions()
    reader.require(';')

    documentation = reader.tryAppendTrailingDocumentation(documentation)

    return EnumConstantElement(
        location = location,
        name = label,
        tag = tag,
        documentation = documentation,
        options = options
    )
  }

  /** Reads an rpc and returns it. */
  private fun readRpc(location: Location, documentation: String): RpcElement {
    val name = reader.readName()

    reader.require('(')
    var requestStreaming = false
    val requestType = when (val word = reader.readWord()) {
      "stream" -> reader.readDataType().also { requestStreaming = true }
      else -> reader.readDataType(word)
    }
    reader.require(')')

    reader.expect(reader.readWord() == "returns", location) { "expected 'returns'" }

    reader.require('(')
    var responseStreaming = false
    val responseType = when (val word = reader.readWord()) {
      "stream" -> reader.readDataType().also { responseStreaming = true }
      else -> reader.readDataType(word)
    }
    reader.require(')')

    val options = mutableListOf<OptionElement>()
    if (reader.peekChar('{')) {
      while (true) {
        val rpcDocumentation = reader.readDocumentation()
        if (reader.peekChar('}')) break

        when (val declared = readDeclaration(rpcDocumentation, Context.RPC)) {
          is OptionElement -> options.add(declared)
          // TODO: add else clause to catch unexpected declarations.
        }
      }
    } else {
      reader.require(';')
    }

    return RpcElement(
        location = location,
        name = name,
        documentation = documentation,
        requestType = requestType,
        responseType = responseType,
        requestStreaming = requestStreaming,
        responseStreaming = responseStreaming,
        options = options
    )
  }

  internal enum class Context {
    FILE,
    MESSAGE,
    ENUM,
    RPC,
    EXTEND,
    SERVICE;

    fun permitsPackage() = this == FILE

    fun permitsSyntax() = this == FILE

    fun permitsImport() = this == FILE

    fun permitsExtensions() = this != FILE

    fun permitsRpc() = this == SERVICE

    fun permitsOneOf() = this == MESSAGE
  }

  companion object {
    /** Parse a named `.proto` schema. */
    fun parse(location: Location, data: String): ProtoFileElement {
      // TODO Migrate to data.toCharArray() once stable in common code.
      val chars = CharArray(data.length, data::get)
      return ProtoParser(location, chars).readProtoFile()
    }
  }
}

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.internal.appendDocumentation

data class ReservedElement(
  val location: Location,
  val documentation: String = "",
  /** A [String] name or [Int] or [IntRange] tag. */
  val values: List<Any>
) {
  fun toSchema() = buildString {
    appendDocumentation(documentation)
    append("reserved ")

    values.forEachIndexed { index, value ->
      if (index > 0) append(", ")

      when (value) {
        is String -> append("\"$value\"")
        is Int -> append(value)
        is IntRange -> append("${value.first} to ${value.last}")
        else -> throw AssertionError()
      }
    }
    append(";\n")
  }
}

/*
 * Copyright (C) 2014 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendIndented

data class RpcElement(
  val location: Location,
  val name: String,
  val documentation: String = "",
  val requestType: String,
  val responseType: String,
  val requestStreaming: Boolean = false,
  val responseStreaming: Boolean = false,
  val options: List<OptionElement> = emptyList()
) {
  fun toSchema() = buildString {
    appendDocumentation(documentation)
    append("rpc $name (")

    if (requestStreaming) {
      append("stream ")
    }
    append("$requestType) returns (")

    if (responseStreaming) {
      append("stream ")
    }
    append("$responseType)")

    if (options.isNotEmpty()) {
      append(" {\n")
      for (option in options) {
        appendIndented(option.toSchemaDeclaration())
      }
      append('}')
    }

    append(";\n")
  }
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.internal.appendDocumentation
import com.squareup.wire.schema.internal.appendIndented

data class ServiceElement(
  val location: Location,
  val name: String,
  val documentation: String = "",
  val rpcs: List<RpcElement> = emptyList(),
  val options: List<OptionElement> = emptyList()
) {
  fun toSchema() = buildString{
    appendDocumentation(documentation)
    append("service $name {")
    if (options.isNotEmpty()) {
      append('\n')
      for (option in options) {
        appendIndented(option.toSchemaDeclaration())
      }
    }
    if (rpcs.isNotEmpty()) {
      append('\n')
      for (rpc in rpcs) {
        appendIndented(rpc.toSchema())
      }
    }
    append("}\n")
  }
}

/*
 * Copyright (C) 2016 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location

/** A general purpose reader for formats like `.proto`. */
class SyntaxReader(
  private val data: CharArray,
  private val location: Location
) {
  /** Our cursor within the document. `data[pos]` is the next character to be read. */
  private var pos = 0

  /** The number of newline characters encountered thus far. */
  private var line = 0

  /** The index of the most recent newline character. */
  private var lineStart = 0

  fun exhausted(): Boolean = pos == data.size

  /** Reads a non-whitespace character and returns it. */
  fun readChar(): Char = peekChar().also { pos++ }

  /** Reads a non-whitespace character 'c', or throws an exception. */
  fun require(c: Char) {
    expect(readChar() == c) { "expected '$c'" }
  }

  /**
   * Peeks a non-whitespace character and returns it. The only difference between this and
   * [readChar] is that this doesn't consume the char.
   */
  fun peekChar(): Char {
    skipWhitespace(skipComments = true)
    expect(pos < data.size) { "unexpected end of file" }
    return data[pos]
  }

  fun peekChar(c: Char): Boolean {
    return when (peekChar()) {
      c -> {
        pos++
        true
      }
      else -> false
    }
  }

  /** Push back the most recently read character. */
  fun pushBack(c: Char) {
    require(data[pos - 1] == c)
    pos--
  }

  /** Reads a quoted or unquoted string and returns it. */
  fun readString(): String {
    skipWhitespace(skipComments = true)
    return when (peekChar()) {
      '"', '\'' -> readQuotedString()
      else -> readWord()
    }
  }

  fun readQuotedString(): String {
    var startQuote = readChar()
    check(startQuote == '"' || startQuote == '\'')
    val result = StringBuilder()
    while (pos < data.size) {
      var c = data[pos++]
      if (c == startQuote) {
        // Adjacent strings are concatenated. Consume new quote and continue reading.
        if (peekChar() == '"' || peekChar() == '\'') {
          startQuote = readChar()
          continue
        }
        return result.toString()
      }
      if (c == '\\') {
        expect(pos < data.size) { "unexpected end of file" }
        c = data[pos++]
        when (c) {
          'a' -> c = '\u0007' // Alert.
          'b' -> c = '\b'     // Backspace.
          'f' -> c = '\u000c' // Form feed.
          'n' -> c = '\n'     // Newline.
          'r' -> c = '\r'     // Carriage return.
          't' -> c = '\t'     // Horizontal tab.
          'v' -> c = '\u000b' // Vertical tab.
          'x', 'X' -> c = readNumericEscape(16, 2)
          '0', '1', '2', '3', '4', '5', '6', '7' -> {
            --pos
            c = readNumericEscape(8, 3)
          }
        }
      }
      result.append(c)
      if (c == '\n') newline()
    }
    throw unexpected("unterminated string")
  }

  private fun readNumericEscape(radix: Int, len: Int): Char {
    var value = -1
    val endPos = minOf(pos + len, data.size)
    while (pos < endPos) {
      val digit = hexDigit(data[pos])
      if (digit == -1 || digit >= radix) break
      value = when {
        value < 0 -> digit
        else -> value * radix + digit
      }
      pos++
    }
    expect(value >= 0) { "expected a digit after \\x or \\X" }
    return value.toChar()
  }

  private fun hexDigit(c: Char): Int {
    return when (c) {
      in '0'..'9' -> c - '0'
      in 'a'..'f' -> c - 'a' + 10
      in 'A'..'F' -> c - 'A' + 10
      else -> -1
    }
  }

  /** Reads a (paren-wrapped), [square-wrapped] or naked symbol name. */
  fun readName(): String {
    return when (peekChar()) {
      '(' -> {
        pos++
        readWord().also {
          expect(readChar() == ')') { "expected ')'" }
        }
      }

      '[' -> {
        pos++
        readWord().also {
          expect(readChar() == ']') { "expected ']'" }
        }
      }

      else -> readWord()
    }
  }

  /** Reads a scalar, map, or type name. */
  fun readDataType(): String {
    val name = readWord()
    return readDataType(name)
  }

  /** Reads a scalar, map, or type name with `name` as a prefix word. */
  fun readDataType(name: String): String {
    return when (name) {
      "map" -> {
        expect(readChar() == '<') { "expected '<'" }
        val keyType = readDataType()

        expect(readChar() == ',') { "expected ','" }
        val valueType = readDataType()

        expect(readChar() == '>') { "expected '>'" }
        "map<$keyType, $valueType>"
      }

      else -> name
    }
  }

  /** Reads a non-empty word and returns it. */
  fun readWord(): String {
    skipWhitespace(skipComments = true)
    val start = pos
    loop@ while (pos < data.size) {
      when (data[pos]) {
        in 'a'..'z', in 'A'..'Z', in '0'..'9', '_', '-', '.' -> pos++
        else -> break@loop
      }
    }
    expect(start < pos) { "expected a word" }
    return String(data, start, pos - start)
  }

  /** Reads an integer and returns it. */
  fun readInt(): Int {
    var tag = readWord()
    try {
      var radix = 10
      if (tag.startsWith("0x") || tag.startsWith("0X")) {
        tag = tag.substring("0x".length)
        radix = 16
      }
      return tag.toInt(radix)
    } catch (_: Exception) {
      throw unexpected("expected an integer but was $tag")
    }
  }

  /**
   * Like [skipWhitespace], but this returns a string containing all comment text. By convention,
   * comments before a declaration document that declaration.
   */
  fun readDocumentation(): String {
    var result: String? = null
    while (true) {
      skipWhitespace(skipComments = false)
      if (pos == data.size || data[pos] != '/') return result ?: ""
      val comment = readComment()
      result = when (result) {
        null -> comment
        else -> "$result\n$comment"
      }
    }
  }

  /** Reads a comment and returns its body. */
  private fun readComment(): String {
    check(pos != data.size && data[pos] == '/')
    pos++
    when (if (pos < data.size) data[pos++].toInt() else -1) {
      '*'.toInt() -> {
        val result = StringBuilder()
        var startOfLine = true
        while (pos + 1 < data.size) {
          val c = data[pos]
          when {
            c == '*' && data[pos + 1] == '/' -> {
              pos += 2
              return result.toString().trim()
            }

            c == '\n' -> {
              result.append('\n')
              newline()
              startOfLine = true
            }

            !startOfLine -> {
              result.append(c)
            }

            c == '*' -> {
              if (data[pos + 1] == ' ') {
                pos += 1 // Skip a single leading space, if present.
              }
              startOfLine = false
            }

            !c.isWhitespace() -> {
              result.append(c)
              startOfLine = false
            }
          }
          pos++
        }
        throw unexpected("unterminated comment")
      }

      '/'.toInt() -> {
        if (pos < data.size && data[pos] == ' ') {
          pos++ // Skip a single leading space, if present.
        }
        val start = pos
        while (pos < data.size) {
          val c = data[pos++]
          if (c == '\n') {
            newline()
            break
          }
        }
        return String(data, start, pos - 1 - start)
      }

      else -> throw unexpected("unexpected '/'")
    }
  }

  fun tryAppendTrailingDocumentation(documentation: String): String {
    // Search for a '/' character ignoring spaces and tabs.
    loop@ while (pos < data.size) {
      when (data[pos]) {
        ' ', '\t' -> pos++

        '/' -> {
          pos++
          break@loop
        }

        // Not a whitespace or comment-starting character. Return original documentation.
        else -> return documentation
      }
    }

    expect(pos < data.size && (data[pos] == '/' || data[pos] == '*')) {
      pos-- // Backtrack to start of comment.
      "expected '//' or '/*'"
    }

    val isStar = data[pos] == '*'
    pos++

    // Skip a single leading space, if present.
    if (pos < data.size && data[pos] == ' ') pos++

    val start = pos
    var end: Int
    when {
      isStar -> {
        // Consume star comment until it closes on the same line.
        while (true) {
          expect(pos < data.size) { "trailing comment must be closed" }
          if (data[pos] == '*' && pos + 1 < data.size && data[pos + 1] == '/') {
            end = pos - 1 // The character before '*'.
            pos += 2 // Skip to the character after '/'.
            break
          }
          pos++
        }

        // Ensure nothing follows a trailing star comment.
        while (pos < data.size) {
          val c = data[pos++]
          if (c == '\n') {
            newline()
            break
          }
          expect(c == ' ' || c == '\t') { "no syntax may follow trailing comment" }
        }
      }

      else -> {
        // Consume comment until newline.
        while (true) {
          if (pos == data.size) {
            end = pos - 1
            break
          }
          val c = data[pos++]
          if (c == '\n') {
            newline()
            end = pos - 2 // Account for stepping past the newline.
            break
          }
        }
      }
    }

    // Remove trailing whitespace.
    while (end > start && (data[end] == ' ' || data[end] == '\t')) {
      end--
    }

    if (end == start) return documentation

    val trailingDocumentation = String(data, start, end - start + 1)
    if (documentation.isEmpty()) return trailingDocumentation
    return "$documentation\n$trailingDocumentation"
  }

  /**
   * Skips whitespace characters and optionally comments. When this returns, either
   * `pos == data.length` or a non-whitespace character.
   */
  private fun skipWhitespace(skipComments: Boolean) {
    while (pos < data.size) {
      val c = data[pos]
      when {
        c == ' ' || c == '\t' || c == '\r' || c == '\n' -> {
          pos++
          if (c == '\n') newline()
        }

        skipComments && c == '/' -> readComment()

        else -> return
      }
    }
  }

  /** Call this every time a '\n' is encountered. */
  private fun newline() {
    line++
    lineStart = pos
  }

  fun location() = location.at(line + 1, pos - lineStart + 1)

  inline fun expect(condition: Boolean, location: Location = location(), message: () -> String) {
    if (!condition) throw unexpected(message(), location)
  }

  fun unexpected(
    message: String,
    location: Location? = location()
  ): RuntimeException = throw IllegalStateException("Syntax error in $location: $message")
}

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * 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.
 */
package com.squareup.wire.schema.internal.parser

import com.squareup.wire.schema.Location
import com.squareup.wire.schema.SyntaxRules

/** A message type or enum type declaration.  */
interface TypeElement {
  val location: Location
  val name: String
  val documentation: String
  val options: List<OptionElement>
  val nestedTypes: List<TypeElement>
  fun toSchema(syntaxRules: SyntaxRules = SyntaxRules.get(syntax = null)): String
}