Inconsistent fromValue() implementation for enums in Kotlin and Java

Java returns null for unknown tags:
```java
public static NestedEnum fromValue(int value) {
  switch (value) {
    case 1: return A;
    default: return null;
  }
}
```
While Kotlin throws:
```kotlin
fun fromValue(value: Int): NestedEnum = when (value) {
  1 -> A
  else -> throw IllegalArgumentException("""Unexpected value: $value""")
}
```
Kotlin implementation should be changed to mirror Java.

diff --git a/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt b/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
index 61338be0e..a7d0acabb 100644
--- a/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
+++ b/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
@@ -1136,13 +1136,13 @@ class KotlinGenerator private constructor(
     val fromValue = FunSpec.builder("fromValue")
         .jvmStatic()
         .addParameter(valueName, Int::class)
-        .returns(parentClassName)
+        .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 -> throw IllegalArgumentException(%P)", "Unexpected value: \\$value")
+          addCode("else -> null")
           addCode("\\n⇤}\\n") // close the block
         }
         .build()
@@ -1175,7 +1175,7 @@ class KotlinGenerator private constructor(
         .addFunction(FunSpec.builder("fromValue")
             .addModifiers(OVERRIDE)
             .addParameter(valueName, Int::class)
-            .returns(parentClassName)
+            .returns(parentClassName.copy(nullable = true))
             .addStatement("return %T.fromValue(value)", parentClassName)
             .build())
         .build()
diff --git a/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt b/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt
index cdf9ad00a..8b6c87fc7 100644
--- a/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt
+++ b/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt
@@ -49,6 +49,7 @@ class KotlinGeneratorTest {
     assertTrue(code.contains("PhoneNumber::class"))
     assertTrue(code.contains("override fun encode(writer: ProtoWriter, value: Person)"))
     assertTrue(code.contains("enum class PhoneType(    override val value: Int  ) : WireEnum"))
+    assertTrue(code.contains("fun fromValue(value: Int): PhoneType?"))
     assertTrue(code.contains("WORK(1),"))
   }

diff --git a/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/geology/javainteropkotlin/Period.kt b/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/geology/javainteropkotlin/Period.kt
index a9020269d..595433734 100644
--- a/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/geology/javainteropkotlin/Period.kt
+++ b/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/geology/javainteropkotlin/Period.kt
@@ -32,15 +32,15 @@ enum class Period(
     val ADAPTER: ProtoAdapter<Period> = object : EnumAdapter<Period>(
       Period::class
     ) {
-      override fun fromValue(value: Int): Period = Period.fromValue(value)
+      override fun fromValue(value: Int): Period? = Period.fromValue(value)
     }

     @JvmStatic
-    fun fromValue(value: Int): Period = when (value) {
+    fun fromValue(value: Int): Period? = when (value) {
       1 -> CRETACEOUS
       2 -> JURASSIC
       3 -> TRIASSIC
-      else -> throw IllegalArgumentException("""Unexpected value: $value""")
+      else -> null
     }
   }
 }
diff --git a/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/geology/kotlin/Period.kt b/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/geology/kotlin/Period.kt
index 17f7c031f..f65473ac3 100644
--- a/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/geology/kotlin/Period.kt
+++ b/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/geology/kotlin/Period.kt
@@ -32,15 +32,15 @@ enum class Period(
     val ADAPTER: ProtoAdapter<Period> = object : EnumAdapter<Period>(
       Period::class
     ) {
-      override fun fromValue(value: Int): Period = Period.fromValue(value)
+      override fun fromValue(value: Int): Period? = Period.fromValue(value)
     }

     @JvmStatic
-    fun fromValue(value: Int): Period = when (value) {
+    fun fromValue(value: Int): Period? = when (value) {
       1 -> CRETACEOUS
       2 -> JURASSIC
       3 -> TRIASSIC
-      else -> throw IllegalArgumentException("""Unexpected value: $value""")
+      else -> null
     }
   }
 }
diff --git a/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/person/javainteropkotlin/Person.kt b/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/person/javainteropkotlin/Person.kt
index be9cde5f2..ea04406ef 100644
--- a/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/person/javainteropkotlin/Person.kt
+++ b/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/person/javainteropkotlin/Person.kt
@@ -244,15 +244,15 @@ class Person(
       val ADAPTER: ProtoAdapter<PhoneType> = object : EnumAdapter<PhoneType>(
         PhoneType::class
       ) {
-        override fun fromValue(value: Int): PhoneType = PhoneType.fromValue(value)
+        override fun fromValue(value: Int): PhoneType? = PhoneType.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): PhoneType = when (value) {
+      fun fromValue(value: Int): PhoneType? = when (value) {
         0 -> MOBILE
         1 -> HOME
         2 -> WORK
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/person/kotlin/Person.kt b/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/person/kotlin/Person.kt
index a28a9f0ab..11973387b 100644
--- a/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/person/kotlin/Person.kt
+++ b/wire-moshi-adapter/src/test/java/com/squareup/wire/protos/person/kotlin/Person.kt
@@ -184,15 +184,15 @@ class Person(
       val ADAPTER: ProtoAdapter<PhoneType> = object : EnumAdapter<PhoneType>(
         PhoneType::class
       ) {
-        override fun fromValue(value: Int): PhoneType = PhoneType.fromValue(value)
+        override fun fromValue(value: Int): PhoneType? = PhoneType.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): PhoneType = when (value) {
+      fun fromValue(value: Int): PhoneType? = when (value) {
         0 -> MOBILE
         1 -> HOME
         2 -> WORK
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/DeprecatedEnum.kt b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/DeprecatedEnum.kt
index 358e617b1..d2b0be5e0 100644
--- a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/DeprecatedEnum.kt
+++ b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/DeprecatedEnum.kt
@@ -28,16 +28,16 @@ enum class DeprecatedEnum(
     val ADAPTER: ProtoAdapter<DeprecatedEnum> = object : EnumAdapter<DeprecatedEnum>(
       DeprecatedEnum::class
     ) {
-      override fun fromValue(value: Int): DeprecatedEnum = DeprecatedEnum.fromValue(value)
+      override fun fromValue(value: Int): DeprecatedEnum? = DeprecatedEnum.fromValue(value)
     }

     @JvmStatic
-    fun fromValue(value: Int): DeprecatedEnum = when (value) {
+    fun fromValue(value: Int): DeprecatedEnum? = when (value) {
       1 -> DISABLED
       2 -> ENABLED
       3 -> ON
       4 -> OFF
-      else -> throw IllegalArgumentException("""Unexpected value: $value""")
+      else -> null
     }
   }
 }
diff --git a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/MessageWithStatus.kt b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/MessageWithStatus.kt
index a2e83bc3d..a68cf2848 100644
--- a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/MessageWithStatus.kt
+++ b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/MessageWithStatus.kt
@@ -79,13 +79,13 @@ class MessageWithStatus(
       val ADAPTER: ProtoAdapter<Status> = object : EnumAdapter<Status>(
         Status::class
       ) {
-        override fun fromValue(value: Int): Status = Status.fromValue(value)
+        override fun fromValue(value: Int): Status? = Status.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): Status = when (value) {
+      fun fromValue(value: Int): Status? = when (value) {
         1 -> A
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/OptionalEnumUser.kt b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/OptionalEnumUser.kt
index 553d9f627..0467f3645 100644
--- a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/OptionalEnumUser.kt
+++ b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/OptionalEnumUser.kt
@@ -110,14 +110,14 @@ class OptionalEnumUser(
       val ADAPTER: ProtoAdapter<OptionalEnum> = object : EnumAdapter<OptionalEnum>(
         OptionalEnum::class
       ) {
-        override fun fromValue(value: Int): OptionalEnum = OptionalEnum.fromValue(value)
+        override fun fromValue(value: Int): OptionalEnum? = OptionalEnum.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): OptionalEnum = when (value) {
+      fun fromValue(value: Int): OptionalEnum? = when (value) {
         1 -> FOO
         2 -> BAR
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/OtherMessageWithStatus.kt b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/OtherMessageWithStatus.kt
index 7e01fa55b..9d9762397 100644
--- a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/OtherMessageWithStatus.kt
+++ b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/OtherMessageWithStatus.kt
@@ -80,13 +80,13 @@ class OtherMessageWithStatus(
       val ADAPTER: ProtoAdapter<Status> = object : EnumAdapter<Status>(
         Status::class
       ) {
-        override fun fromValue(value: Int): Status = Status.fromValue(value)
+        override fun fromValue(value: Int): Status? = Status.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): Status = when (value) {
+      fun fromValue(value: Int): Status? = when (value) {
         1 -> A
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/alltypes/AllTypes.kt b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/alltypes/AllTypes.kt
index 405811ce6..abf7cad39 100644
--- a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/alltypes/AllTypes.kt
+++ b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/alltypes/AllTypes.kt
@@ -2206,13 +2206,13 @@ class AllTypes(
       val ADAPTER: ProtoAdapter<NestedEnum> = object : EnumAdapter<NestedEnum>(
         NestedEnum::class
       ) {
-        override fun fromValue(value: Int): NestedEnum = NestedEnum.fromValue(value)
+        override fun fromValue(value: Int): NestedEnum? = NestedEnum.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): NestedEnum = when (value) {
+      fun fromValue(value: Int): NestedEnum? = when (value) {
         1 -> A
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/foreign/ForeignEnum.kt b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/foreign/ForeignEnum.kt
index 61a939ce2..a03cfc468 100644
--- a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/foreign/ForeignEnum.kt
+++ b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/foreign/ForeignEnum.kt
@@ -21,14 +21,14 @@ enum class ForeignEnum(
     val ADAPTER: ProtoAdapter<ForeignEnum> = object : EnumAdapter<ForeignEnum>(
       ForeignEnum::class
     ) {
-      override fun fromValue(value: Int): ForeignEnum = ForeignEnum.fromValue(value)
+      override fun fromValue(value: Int): ForeignEnum? = ForeignEnum.fromValue(value)
     }

     @JvmStatic
-    fun fromValue(value: Int): ForeignEnum = when (value) {
+    fun fromValue(value: Int): ForeignEnum? = when (value) {
       0 -> BAV
       1 -> BAX
-      else -> throw IllegalArgumentException("""Unexpected value: $value""")
+      else -> null
     }
   }
 }
diff --git a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt
index f42b65a48..f6d6dc128 100644
--- a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt
+++ b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt
@@ -184,15 +184,15 @@ class Person(
       val ADAPTER: ProtoAdapter<PhoneType> = object : EnumAdapter<PhoneType>(
         PhoneType::class
       ) {
-        override fun fromValue(value: Int): PhoneType = PhoneType.fromValue(value)
+        override fun fromValue(value: Int): PhoneType? = PhoneType.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): PhoneType = when (value) {
+      fun fromValue(value: Int): PhoneType? = when (value) {
         0 -> MOBILE
         1 -> HOME
         2 -> WORK
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/simple/SimpleMessage.kt b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/simple/SimpleMessage.kt
index fa596d68b..b5c9efad8 100644
--- a/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/simple/SimpleMessage.kt
+++ b/wire-tests/src/commonTest/proto-kotlin/com/squareup/wire/protos/kotlin/simple/SimpleMessage.kt
@@ -410,16 +410,16 @@ class SimpleMessage(
       val ADAPTER: ProtoAdapter<NestedEnum> = object : EnumAdapter<NestedEnum>(
         NestedEnum::class
       ) {
-        override fun fromValue(value: Int): NestedEnum = NestedEnum.fromValue(value)
+        override fun fromValue(value: Int): NestedEnum? = NestedEnum.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): NestedEnum = when (value) {
+      fun fromValue(value: Int): NestedEnum? = when (value) {
         1 -> FOO
         2 -> BAR
         3 -> BAZ
         3 -> BUZ
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/jvmKotlinAndroidTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt b/wire-tests/src/jvmKotlinAndroidTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt
index 944415ea0..e00282aaf 100644
--- a/wire-tests/src/jvmKotlinAndroidTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt
+++ b/wire-tests/src/jvmKotlinAndroidTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt
@@ -188,15 +188,15 @@ class Person(
       val ADAPTER: ProtoAdapter<PhoneType> = object : EnumAdapter<PhoneType>(
         PhoneType::class
       ) {
-        override fun fromValue(value: Int): PhoneType = PhoneType.fromValue(value)
+        override fun fromValue(value: Int): PhoneType? = PhoneType.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): PhoneType = when (value) {
+      fun fromValue(value: Int): PhoneType? = when (value) {
         0 -> MOBILE
         1 -> HOME
         2 -> WORK
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/MessageWithStatus.kt b/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/MessageWithStatus.kt
index b15397d27..cf8350467 100644
--- a/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/MessageWithStatus.kt
+++ b/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/MessageWithStatus.kt
@@ -81,13 +81,13 @@ class MessageWithStatus(
       val ADAPTER: ProtoAdapter<Status> = object : EnumAdapter<Status>(
         Status::class
       ) {
-        override fun fromValue(value: Int): Status = Status.fromValue(value)
+        override fun fromValue(value: Int): Status? = Status.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): Status = when (value) {
+      fun fromValue(value: Int): Status? = when (value) {
         1 -> A
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/OtherMessageWithStatus.kt b/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/OtherMessageWithStatus.kt
index 1ccf2bdf8..38b762575 100644
--- a/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/OtherMessageWithStatus.kt
+++ b/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/OtherMessageWithStatus.kt
@@ -82,13 +82,13 @@ class OtherMessageWithStatus(
       val ADAPTER: ProtoAdapter<Status> = object : EnumAdapter<Status>(
         Status::class
       ) {
-        override fun fromValue(value: Int): Status = Status.fromValue(value)
+        override fun fromValue(value: Int): Status? = Status.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): Status = when (value) {
+      fun fromValue(value: Int): Status? = when (value) {
         1 -> A
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/alltypes/AllTypes.kt b/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/alltypes/AllTypes.kt
index 9d1721a11..7a920678c 100644
--- a/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/alltypes/AllTypes.kt
+++ b/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/alltypes/AllTypes.kt
@@ -3780,13 +3780,13 @@ class AllTypes(
       val ADAPTER: ProtoAdapter<NestedEnum> = object : EnumAdapter<NestedEnum>(
         NestedEnum::class
       ) {
-        override fun fromValue(value: Int): NestedEnum = NestedEnum.fromValue(value)
+        override fun fromValue(value: Int): NestedEnum? = NestedEnum.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): NestedEnum = when (value) {
+      fun fromValue(value: Int): NestedEnum? = when (value) {
         1 -> A
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }
diff --git a/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt b/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt
index 5ec51e9ab..66abddff0 100644
--- a/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt
+++ b/wire-tests/src/jvmKotlinInteropTest/proto-kotlin/com/squareup/wire/protos/kotlin/person/Person.kt
@@ -244,15 +244,15 @@ class Person(
       val ADAPTER: ProtoAdapter<PhoneType> = object : EnumAdapter<PhoneType>(
         PhoneType::class
       ) {
-        override fun fromValue(value: Int): PhoneType = PhoneType.fromValue(value)
+        override fun fromValue(value: Int): PhoneType? = PhoneType.fromValue(value)
       }

       @JvmStatic
-      fun fromValue(value: Int): PhoneType = when (value) {
+      fun fromValue(value: Int): PhoneType? = when (value) {
         0 -> MOBILE
         1 -> HOME
         2 -> WORK
-        else -> throw IllegalArgumentException("""Unexpected value: $value""")
+        else -> null
       }
     }
   }

Inconsistent fromValue() implementation for enums in Kotlin and Java
Java returns null for unknown tags:
```java
public static NestedEnum fromValue(int value) {
  switch (value) {
    case 1: return A;
    default: return null;
  }
}
```
While Kotlin throws:
```kotlin
fun fromValue(value: Int): NestedEnum = when (value) {
  1 -> A
  else -> throw IllegalArgumentException("""Unexpected value: $value""")
}
```
Kotlin implementation should be changed to mirror Java.

/*
 * 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, 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 {
            val location = type.location()
            if (location != null) {
              addFileComment("\nSource file: \$L", 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()
internal data class PendingServiceFileSpec(val service: Service) : PendingFileSpec()

/*
 * 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.IdentifierSet
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 identifierSet: IdentifierSet,
  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 (!identifierSet.isEmpty) {
      log.info("Analyzing dependencies of root types.")
      schema = schema.prune(identifierSet)
      for (rule in identifierSet.unusedIncludes()) {
        log.info("Unused include: $rule")
      }
      for (rule in identifierSet.unusedExcludes()) {
        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
      }
      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"

    @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 identifierSetBuilder = IdentifierSet.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)
            identifierSetBuilder.include(includes.split(Regex(",")))
          }

          arg.startsWith(EXCLUDES_FLAG) -> {
            val excludes = arg.substring(EXCLUDES_FLAG.length)
            identifierSetBuilder.exclude(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,
          identifierSetBuilder.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, 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 okio.Timeout
import java.io.IOException

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

import com.squareup.wire.internal.GrpcMethod
import com.squareup.wire.internal.GrpcMethod.Companion.toGrpc
import okhttp3.Call
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import kotlin.reflect.KClass

class GrpcClient private constructor(
  internal val client: OkHttpClient,
  internal val baseUrl: HttpUrl
) {
  fun <T : Service> create(service: KClass<T>): T {
    val methodToService: Map<Method, GrpcMethod<*, *>> =
        service.java.methods.associate { method -> method to method.toGrpc<Any, Any>() }

    return Proxy.newProxyInstance(
        service.java.classLoader,
        arrayOf<Class<*>>(service.java),
        object : InvocationHandler {
          override fun invoke(proxy: Any, method: Method, args: Array<Any>?): Any {
            val self: InvocationHandler = this
            val args = args ?: emptyArray()

            if (method.declaringClass == Object::class.java) {
              return method.invoke(self, *args)
            }

            val grpcMethod = methodToService[method] as GrpcMethod<*, *>
            return grpcMethod.invoke(this@GrpcClient, args)
          }
        }
    ) as T
  }

  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.toHttpUrlOrNull()
      return this
    }

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

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

/*
 * 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.Timeout
import java.io.IOException

/**
 * 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 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>)
  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 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>)
  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 : JsonAdapter.Factory {
  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
    }
    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 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

  /**
   * 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 com.squareup.wire.internal.format
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<*>?
) {
  internal val fieldEncoding: FieldEncoding
  val type: KClass<*>?

  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

  /** 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>.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) {
  @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) {
  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) {
  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) {

  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
) {
  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%02x".format(value))
  }

  override fun redact(value: Boolean): Boolean = throw UnsupportedOperationException()
}
internal fun commonInt32(): ProtoAdapter<Int> = object : ProtoAdapter<Int>(
    FieldEncoding.VARINT,
    Int::class
) {
  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
) {
  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
) {
  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
) {
  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
) {
  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
) {
  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
) {
  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
) {
  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
) {
  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
) {
  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
) {
  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
) {
  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 unknownFieldsWriter = ProtoWriter(bufferStack[recursionDepth - 1])
    val fieldEncoding = peekFieldEncoding()
    val protoAdapter = fieldEncoding!!.rawProtoAdapter()
    val value = protoAdapter.decode(this)
    @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) {
  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)

  /**
   * 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<*>?
) {
  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)
  }

  /** 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) {
  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 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<*>?
) {
  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()
  }

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

  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)
  }

  @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)
    }

    /** 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 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) {
  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. */
  @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)

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

import com.squareup.wire.schema.IdentifierSet
import com.squareup.wire.schema.SchemaLoader
import java.io.File

class ProtoPruner(
  private val inPaths: List<String>,
  private val outPath: String,
  private val identifierSet: IdentifierSet
) : Runnable {
  override fun run() {
    val schema = SchemaLoader()
        .apply {
          inPaths.forEach { addSource(File(it)) }
        }
        .load()
        .prune(identifierSet)

    schema.protoFiles()
        .filter { it.types().isNotEmpty() || it.services().isNotEmpty() }
        .forEach { protoFile ->
          val relativePath = protoFile.location()?.path
              ?.substringBeforeLast("/", missingDelimiterValue = ".")
          val outFolder = File(outPath, relativePath)
          outFolder.mkdirs() // Ensure the directories to the file have been created.

          val outFile = File(outFolder, "${protoFile.name()}.proto")
          println("Writing $outFile...")
          outFile.writeText(protoFile.toSchema())
        }
  }

  companion object {
    @JvmStatic fun main(vararg args: String) {
      var outPath: String? = null
      val inPaths = mutableListOf<String>()
      val identifierSetBuilder = IdentifierSet.Builder()

      for (arg in args) {
        if (arg == "--help") {
          println("""
              |Arguments: --in=dir --out=dir [--includes=pattern] [--excludes=pattern] [file]
              |
              |Patterns must be in one of the following formats:
              |  * Partial package name followed by a `*` (e.g., `com.example.*`).
              |  * Full package name of a type or service.
              |  * Full package name, a `#`, and the name of a member.
              |
              |Multiple patterns can be supplied to a single argument by separating them by
              |a `,` (e.g., `--include=com.example.*,com.other.package.*`).
              |
              |File must contain patterns prefixed with either `+` or `-` to signify an include
              |or exclude, respectively. All other lines are ignored.
              |""".trimMargin())
          return // Don't run the program.
        }
        if (arg.startsWith("--out=")) {
          outPath = arg.substringAfter('=')
        } else if (arg.startsWith("--in=")) {
          inPaths.add(arg.substringAfter('='))
        } else if (arg.startsWith("--includes=")) {
          identifierSetBuilder.include(arg.substringAfter('=').split(',').map(String::trim))
        } else if (arg.startsWith("--excludes=")) {
          identifierSetBuilder.exclude(arg.substringAfter('=').split(',').map(String::trim))
        } else if (arg.startsWith("--")) {
          throw IllegalArgumentException("Unknown argument: $arg")
        } else {
          File(arg).forEachLine { line ->
            if (line.startsWith("+")) {
              identifierSetBuilder.include(line.substring(1))
            } else if (line.startsWith("-")) {
              identifierSetBuilder.exclude(line.substring(1))
            }
          }
        }
      }

      if (inPaths.isEmpty()) {
        throw IllegalArgumentException("No input path specified")
      }
      if (outPath == null) {
        throw IllegalArgumentException("No output path specified")
      }

      ProtoPruner(inPaths, outPath, identifierSetBuilder.build()).run()
    }
  }
}

/*
 * 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.schema.SchemaLoader.DESCRIPTOR_PROTO
import com.squareup.wire.schema.internal.parser.ProtoParser
import okio.buffer
import okio.source
import java.io.Closeable
import java.io.IOException
import java.nio.file.FileSystem
import java.util.ArrayDeque

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

  /** See [com.squareup.wire.WireRun.sourcePath]. */
  private val sourcePath: List<Location>,

  /** See [com.squareup.wire.WireRun.protoPath]. */
  private val protoPath: List<Location> = listOf()
) : Closeable {
  private val closer = Closer.create()

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

  /** Files loaded by their relative paths. This is the same path that we use in imports! */
  private val loaded = mutableMapOf<String, ProtoFile>()

  /** Source files that were loaded. Used to differentiate sources from protoPath elements. */
  lateinit var sourceLocationPaths: Set<String>

  /** Working backlog of imported .proto files to load. */
  private val imports = ArrayDeque<String>()

  @Throws(IOException::class)
  fun load(): List<ProtoFile> {
    check(loaded.isEmpty()) { "do not reuse instances of this class" }

    // Load all of the sources, discovering imports as we go.
    val mutableSourceLocationPaths = mutableSetOf<String>()
    val sourceRoots = allRoots(closer, sourcePath)
    for (sourceRoot in sourceRoots) {
      for (locationAndPath in sourceRoot.allProtoFiles()) {
        load(locationAndPath)
        mutableSourceLocationPaths += locationAndPath.location.path
      }
    }
    if (mutableSourceLocationPaths.isEmpty()) {
      errors += "no sources"
    }
    sourceLocationPaths = mutableSourceLocationPaths.toSet()

    // Load the imported files next.
    val protoPathRoots = allRoots(closer, protoPath)
    val missingImports = mutableListOf<String>()
    while (true) {
      val import = imports.poll() ?: break
      if (loaded[import] != null) continue // Already loaded.

      var loadedFrom: Location? = null
      for (protoPathRoot in protoPathRoots) {
        val locationAndPath = protoPathRoot.resolve(import) ?: continue
        if (loadedFrom != null) {
          errors += "$import is ambiguous:\n  $locationAndPath\n  $loadedFrom"
          continue
        }
        loadedFrom = locationAndPath.location
        load(locationAndPath)
      }

      if (loadedFrom == null) {
        missingImports += import
        continue
      }
    }

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

    if (errors.isNotEmpty()) {
      throw IllegalArgumentException(errors.joinToString(separator = "\n"))
    }

    return loaded.values.toList()
  }

  /**
   * Returns Google's protobuf descriptor, which defines standard options like default, deprecated,
   * and java_package. If the user has provided their own version of the descriptor proto, that is
   * preferred.
   */
  @Throws(IOException::class)
  fun loadDescriptorProto(): ProtoFile {
    val resourceAsStream = SchemaLoader::class.java.getResourceAsStream("/$DESCRIPTOR_PROTO")
    resourceAsStream.source().buffer().use { source ->
      val data = source.readUtf8()
      val location = Location.get(DESCRIPTOR_PROTO)
      val element = ProtoParser.parse(location, data)
      return ProtoFile.get(element)
    }
  }

  private fun load(protoFilePath: ProtoFilePath) {
    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"
    }

    loaded[importPath] = protoFile
    imports.addAll(protoFile.imports())
  }

  /** Convert `pathStrings` into roots that can be searched. */
  private fun allRoots(closer: Closer, locations: List<Location>): List<Root> {
    val result = mutableListOf<Root>()
    val baseToRoots = mutableMapOf<String, List<Root>>()

    for (location in locations) {
      try {
        result += location.roots(fs, closer, baseToRoots)
      } catch (e: IllegalArgumentException) {
        errors += e.message!!
      }
    }
    return result
  }

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

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.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 {
  /** 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 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() {
  init {
    check(path.endsWithDotProto()) { "expected a .proto suffix for $location" }
  }

  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)
    }
  }

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

internal class DirectoryRoot(
  /** The location of either a directory or .zip file. */
  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.ProfileLoader
import com.squareup.wire.kotlin.KotlinGenerator
import com.squareup.wire.kotlin.RpcCallStyle
import com.squareup.wire.kotlin.RpcRole
import java.io.IOException
import java.nio.file.FileSystem

sealed class Target {
  /**
   * 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
  ): SchemaHandler

  interface SchemaHandler {
    fun handle(type: Type)
    fun handle(service: 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): SchemaHandler {
    val profileName = if (android) "android" else "java"
    val profile = ProfileLoader(fs, profileName)
        .schema(schema)
        .load()

    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 {
              val location = type.location()
              if (location != null) {
                addFileComment("\nSource file: \$L", 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): 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 {
              val location = type.location()
              if (location != null) {
                addComment("\nSource file: %L", 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)
            .apply {
              service.location()?.let { addComment("\nSource file: %L", it.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)
        }
      }
    }
  }
}

/** 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): SchemaHandler {
    return object : SchemaHandler {
      override fun handle(type: Type) {
        logger.artifactSkipped(type.type())
      }

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

/*
 * 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(),

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

  fun execute(fs: FileSystem = FileSystems.getDefault(), logger: WireLogger = ConsoleWireLogger()) {
    // 1. Read source `.proto` files.
    val schemaLoader = NewSchemaLoader(fs, sourcePath, protoPath)
    val protoFiles = schemaLoader.load()

    // 2. Load descriptor proto
    val protoFilesPlusDescriptor = protoFiles.plus(schemaLoader.loadDescriptorProto())

    // 3. Validate the schema and resolve references
    val fullSchema = Schema.fromFiles(protoFilesPlusDescriptor)

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

    // 5. Call each target.
    val typesToHandle = mutableListOf<Type>()
    val servicesToHandle = mutableListOf<Service>()
    val skippedForSyntax = mutableListOf<ProtoFile>()
    for (protoFile in schema.protoFiles()) {
      if (protoFile.syntax() != ProtoFile.Syntax.PROTO_2) {
        skippedForSyntax += protoFile
        continue
      }
      if (schemaLoader.sourceLocationPaths.contains(protoFile.location().path)) {
        typesToHandle += protoFile.types()
        servicesToHandle += protoFile.services()
      }
    }
    val targetsExclusiveLast = targets.sortedBy { it.exclusive }
    for (target in targetsExclusiveLast) {
      val schemaHandler = target.newHandler(schema, fs, logger)

      val identifierSet: IdentifierSet = IdentifierSet.Builder()
          .include(target.includes)
          .exclude(target.excludes)
          .build()

      val i = typesToHandle.iterator()
      while (i.hasNext()) {
        val type = i.next()
        if (identifierSet.includes(type.type())) {
          schemaHandler.handle(type)
          // We don't let other targets handle this one.
          if (target.exclusive) i.remove()
        }
      }

      val j = servicesToHandle.iterator()
      while (j.hasNext()) {
        val service = j.next()
        if (identifierSet.includes(service.type())) {
          schemaHandler.handle(service)
          // We don't let other targets handle this one.
          if (target.exclusive) j.remove()
        }
      }

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

      if (identifierSet.unusedExcludes().isNotEmpty()) {
        logger.info("""Unused exclude in targets:
            |  ${identifierSet.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()) return schema

    val identifierSet = IdentifierSet.Builder()
        .include(treeShakingRoots)
        .exclude(treeShakingRubbish)
        .build()

    val result = schema.prune(identifierSet)

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

    for (rule in identifierSet.unusedExcludes()) {
      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.internal.file.SourceDirectorySetFactory
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>()

  @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)
  }

  /**
   * 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>()

  @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
  }

  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) {
      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.JavaTarget
import com.squareup.wire.schema.KotlinTarget
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)
    }
  }
}

/*
 * 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.internal.file.SourceDirectorySetFactory
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.tasks.SourceSetContainer
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import javax.inject.Inject

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.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.create("wireSourceDependencies"))
    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.create("wireProtoDependencies"))
    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.register("generateProtos", WireTask::class.java) { task ->
      task.source(sourceInput.configuration)
      task.sourceInput = sourceInput
      task.protoInput = protoInput
      task.roots = extension.roots.toList()
      task.prunes = extension.prunes.toList()
      task.rules = extension.rules
      task.targets = targets
      task.group = "wire"
      task.description = "Generate Wire protocol buffer implementation for .proto files"
    }

    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.SourceTask
import org.gradle.api.tasks.TaskAction

open class WireTask : SourceTask() {
  @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 rules: String? = null

  @Input
  lateinit var targets: List<Target>

  @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,
        targets = targets
    )
    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 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.BufferedSource
import okio.GzipSource
import okio.Source
import java.net.ProtocolException

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) = GzipSource(source)
  }
}

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
import okio.gzip
import java.net.ProtocolException

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.gzip().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 okhttp3.Call
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 { 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
import java.io.Closeable
import java.net.ProtocolException

/**
 * 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>, Closeable by source {
  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 {
      messageAdapter.decode(it)
    }
  }

  fun readExactlyOneAndClose(): T {
    use { 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
    }
  }
}

/*
 * 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.GrpcStreamingCall
import com.squareup.wire.ProtoAdapter
import com.squareup.wire.WireRpc
import java.lang.reflect.Method

internal abstract class GrpcMethod<S : Any, R : Any>(
  val path: String,
  val requestAdapter: ProtoAdapter<S>,
  val responseAdapter: ProtoAdapter<R>
) {
  /** Handle a dynamic proxy method call to this. */
  abstract fun invoke(grpcClient: GrpcClient, args: Array<Any>): Any

  internal companion object {
    internal fun <S : Any, R : Any> Method.toGrpc(): GrpcMethod<S, R> {
      val wireRpc = getAnnotation(WireRpc::class.java)
      val requestAdapter = ProtoAdapter.get(wireRpc.requestAdapter) as ProtoAdapter<S>
      val responseAdapter = ProtoAdapter.get(wireRpc.responseAdapter) as ProtoAdapter<R>
      val returnType = genericReturnType

      if (returnType.rawType() == GrpcCall::class.java) {
        return object : GrpcMethod<S, R>(wireRpc.path, requestAdapter, responseAdapter) {
          override fun invoke(
            grpcClient: GrpcClient,
            args: Array<Any>
          ) = RealGrpcCall(grpcClient, this)
        }

      } else if (returnType.rawType() == GrpcStreamingCall::class.java) {
        return object : GrpcMethod<S, R>(wireRpc.path, requestAdapter, responseAdapter) {
          override fun invoke(
            grpcClient: GrpcClient,
            args: Array<Any>
          ) = RealGrpcStreamingCall(grpcClient, this)
        }
      }

      error("unexpected gRPC method: $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.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 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.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 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("GrpcUtils")

package com.squareup.wire.internal

import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.WildcardType

/**
 * Returns the raw type of this type. If this is a raw type already it is returned. For example,
 * given `? extends String`, this returns `String`.
 */
internal fun Type.rawType(): Class<*> {
  return when (this) {
    is Class<*> -> this
    is WildcardType -> {
      if (lowerBounds.isNotEmpty()) lowerBounds[0].rawType()
      else upperBounds[0].rawType()
    }
    is ParameterizedType -> rawType.rawType()
    else -> throw IllegalArgumentException("no raw type: $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.
 */

/**
 * 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.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.ENUM_VALUE_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 = 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 = TypeSpec.interfaceBuilder(serviceName)
        .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 wireRpcAnnotationSpec = AnnotationSpec.builder(WireRpc::class.asClassName())
        .addMember("path = %S", "/$packageName$serviceName/${rpc.name()}")
        // TODO(oldergod|jwilson) Lets' use Profile for this.
        .addMember("requestAdapter = %S", "$packageName${rpc.requestType().simpleName()}#ADAPTER")
        .addMember("responseAdapter = %S", "$packageName${rpc.responseType().simpleName()}#ADAPTER")
        .build()
    val funSpecBuilder = FunSpec.builder(rpc.name())
        .addModifiers(KModifier.ABSTRACT)
        .addAnnotation(wireRpcAnnotationSpec)

    val requestType = rpc.requestType().typeName
    val responseType = rpc.responseType().typeName

    if (rpcRole == RpcRole.SERVER) {
      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 {
      when {
        rpc.requestStreaming() || rpc.responseStreaming() -> {
          funSpecBuilder.returns(
              GrpcStreamingCall::class.asClassName().parameterizedBy(requestType, responseType))
        }
        else -> {
          funSpecBuilder.returns(
              GrpcCall::class.asClassName().parameterizedBy(requestType, responseType))
        }
      }
    }

    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")
            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")

            if (emitAndroid) {
              newName("CREATOR", "CREATOR")
            }
            message.fieldsAndOneOfFields().forEach { field ->
              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()) }
        .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)
    }

    if (type.oneOfs().isNotEmpty()) {
      classBuilder.addInitializerBlock(generateInitializerOneOfBlock(type))
    }

    classBuilder.addType(companionBuilder.build())

    addMessageConstructor(type, classBuilder)

    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())
    }
    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())
            }
          }
          .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.qualifiedName + '#' + 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
  ) {
    type.fieldsAndOneOfFields().filter { it.default != null }.forEach { field ->
      val fieldName = "DEFAULT_" + nameAllocator[field].toUpperCase(Locale.US)
      val fieldType = field.getClass().copy(nullable = false)
      val fieldValue = defaultFieldInitializer(field.type(), field.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 {
      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 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) {
   *    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\n⇤", parentClassName)
        .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 adapterName = field.getAdapterName()
        val fieldName = nameAllocator[field]
        add("%L.%LencodedSizeWithTag(%L, value.%L) +\n",
            adapterName,
            if (field.isRepeated) "asRepeated()." else "",
            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 encodeFun(message: MessageType): FunSpec {
    val className = generatedTypeName(message)
    val body = buildCodeBlock {
      val nameAllocator = nameAllocator(message)

      message.fieldsAndOneOfFields().forEach { field ->
        val adapterName = field.getAdapterName()
        val fieldName = nameAllocator[field]
        addStatement("%L.%LencodeWithTag(writer, %L, value.%L)",
            adapterName,
            if (field.isRepeated) "asRepeated()." else "",
            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, %2S)", missingRequiredFields, 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 readerTagParamName = nameAllocator.newName("tag")
        addStatement("val unknownFields = reader.forEachTag { $readerTagParamName ->")
        addStatement("⇥when ($readerTagParamName) {⇥")

        message.fieldsAndOneOfFields().forEach { field ->
          val fieldName = nameAllocator[field]
          val adapterName = field.getAdapterName()

          val decodeBodyTemplate = when {
            field.isRepeated -> "%L -> %L.add(%L.decode(reader))"
            field.isMap -> "%L -> %L.putAll(%L.decode(reader))"
            else -> "%L -> %L = %L.decode(reader)"
          }

          addStatement(decodeBodyTemplate, field.tag(), fieldName, adapterName)
        }
        addStatement("else -> reader.readUnknownField($readerTagParamName)")
        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 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 builder = TypeSpec.enumBuilder(type.simpleName())
        .apply {
          if (message.documentation().isNotBlank()) {
            addKdoc("%L\n", message.documentation())
          }
        }
        .addSuperinterface(WireEnum::class)
        .primaryConstructor(FunSpec.constructorBuilder()
            .addParameter(valueName, Int::class, OVERRIDE)
            .build())
        .addProperty(PropertySpec.builder(valueName, Int::class)
            .initializer(valueName)
            .build())
        .addType(generateEnumCompanion(message))

    message.constants().forEach { constant ->
      builder.addEnumConstant(nameAllocator[constant], TypeSpec.anonymousClassBuilder()
          .addSuperclassConstructorParameter("%L", constant.tag)
          .apply {
            if (constant.documentation.isNotBlank()) {
              addKdoc("%L\n", constant.documentation)
            }
            if (constant.options.get(ENUM_DEPRECATED) == "true") {
              addAnnotation(AnnotationSpec.builder(Deprecated::class)
                  .addMember("message = %S", "${constant.name} is deprecated")
                  .build())
            }
          }
          .build())
    }

    return builder.build()
  }

  private fun generateEnumCompanion(message: EnumType): TypeSpec {
    val parentClassName = nameToKotlinName.getValue(message.type())
    val nameAllocator = nameAllocator(message)
    val valueName = nameAllocator["value"]
    val fromValue = FunSpec.builder("fromValue")
        .jvmStatic()
        .addParameter(valueName, Int::class)
        .returns(parentClassName)
        .apply {
          addCode("return when (value) {\n⇥")
          message.constants().forEach { constant ->
            addCode("%L -> %L\n", constant.tag, nameAllocator[constant])
          }
          addCode("else -> throw IllegalArgumentException(%P)", "Unexpected value: \$value")
          addCode("\n⇤}\n") // close the block
        }
        .build()
    return TypeSpec.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)
            .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.objectBuilder(type.typeName)

    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
    )
    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 = BUILT_IN_TYPES.toMutableMap()

      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
        }
      }

      return KotlinGenerator(schema, map, emitAndroid, javaInterop, rpcCallStyle, rpcRole)
    }
  }
}

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>

internal expect fun String.format(vararg args: Any?): String

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

internal actual fun String.format(vararg args: Any?): String = TODO("Not implemented")

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

@Suppress("NOTHING_TO_INLINE") // Syntactic sugar.
internal actual inline fun String.format(vararg args: Any?): String = String.format(this, *args)

/*
 * 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>>
) : ProtoAdapter<M>(FieldEncoding.LENGTH_DELIMITED, messageType.kotlin) {

  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>
    ): 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))
    }

    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) 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(
  private val location: Location,
  private val type: ProtoType,
  private val documentation: String,
  private val nestedTypes: List<Type>
) : Type() {
  // TODO(jrodbx): Konvert to overridden vals, once Type is konverted
  override fun location() = location
  override fun type() = type
  override fun documentation() = documentation
  override fun options() = throw UnsupportedOperationException()
  override fun nestedTypes() = nestedTypes

  internal override fun link(linker: Linker) = nestedTypes.forEach { it.link(linker) }
  internal override fun linkOptions(linker: Linker) = nestedTypes.forEach { it.linkOptions(linker) }
  internal override fun validate(linker: Linker) = nestedTypes.forEach { it.validate(linker) }

  internal override fun retainAll(
    schema: Schema,
    markSet: MarkSet
  ): Type? {
    val retainedNestedTypes = nestedTypes.mapNotNull { it.retainAll(schema, markSet) }
    return if (retainedNestedTypes.isEmpty()) null
    else EnclosingType(location, type, documentation, retainedNestedTypes)
  }

  fun toElement() = MessageElement(
      location = location,
      name = type.simpleName(),
      nestedTypes = Type.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.toElements())

  internal fun linkOptions(linker: Linker) = options.link(linker)

  internal fun retainAll(
    schema: Schema,
    markSet: MarkSet
  ) = EnumConstant(location, name, tag, documentation, options.retainAll(schema, markSet))

  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.internal.parser.EnumElement
import com.squareup.wire.schema.Options.ENUM_OPTIONS

class EnumType private constructor(
  private val protoType: ProtoType,
  private val location: Location,
  private val documentation: String,
  private val name: String,
  private val constants: List<EnumConstant>,
  private val options: Options
) : Type() {
  private var allowAlias: Any? = null

  // TODO(jrodbx): Konvert to overridden vals, once Type is konverted
  override fun location() = location

  override fun type() = protoType
  override fun documentation() = documentation
  override fun options() = options
  override fun nestedTypes() = emptyList<Type>() // 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 }

  fun constants() = constants

  internal override fun link(linker: Linker) {}

  internal override fun linkOptions(linker: Linker) {
    options.link(linker)
    for (constant in constants) {
      constant.linkOptions(linker)
    }
    allowAlias = options.get(ALLOW_ALIAS)
  }

  internal override fun validate(linker: Linker) {
    var linker = linker
    linker = linker.withContext(this)

    if ("true" != allowAlias) {
      validateTagUniqueness(linker)
    }
  }

  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("%s", error)
      }
    }
  }

  internal override fun retainAll(
    schema: Schema,
    markSet: MarkSet
  ): Type? {
    // If this type is not retained, prune it.
    if (!markSet.contains(protoType)) return null

    val retainedConstants = constants
        .filter { markSet.contains(ProtoMember.get(protoType, it.name)) }
        .map { it.retainAll(schema, markSet) }

    val result = EnumType(
        protoType, location, documentation, name,
        retainedConstants,
        options.retainAll(schema, markSet)
    )
    result.allowAlias = allowAlias
    return result
  }

  fun toElement(): EnumElement {
    return EnumElement(
        location,
        name,
        documentation,
        options.toElements(),
        EnumConstant.toElements(constants)
    )
  }

  companion object {
    internal val ALLOW_ALIAS = ProtoMember.get(ENUM_OPTIONS, "allow_alias")

    @JvmStatic
    fun fromElement(
      protoType: ProtoType,
      enumElement: EnumElement
    ): EnumType {
      val constants = EnumConstant.fromElements(enumElement.constants)
      val options = Options(Options.ENUM_OPTIONS, enumElement.options)

      return EnumType(
          protoType, enumElement.location, enumElement.documentation,
          enumElement.name, constants, 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.internal.Util
import com.squareup.wire.schema.internal.parser.ExtensionsElement

internal class Extensions private constructor(
  val location: Location,
  val documentation: String,
  val start: Int,
  val end: Int
) {
  fun validate(linker: Linker) {
    if (!Util.isValidTag(start) || !Util.isValidTag(end)) {
      linker.withContext(this)
          .addError("tags are out of range: %s to %s", start, end)
    }
  }

  companion object {
    @JvmStatic
    fun fromElements(elements: List<ExtensionsElement>) =
      elements.map { Extensions(it.location, it.documentation, it.start, it.end) }

    @JvmStatic
    fun toElements(extensions: List<Extensions>) =
      extensions.map { ExtensionsElement(it.location, it.documentation, it.start, it.end) }
  }
}

/*
 * 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.base.CharMatcher
import java.io.File

/**
 * 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.isEmpty()) {
      append(base)
      append(File.separator)
    }
    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 {
      val newBase = CharMatcher.`is`('/')
          .trimTrailingFrom(base)
      return Location(newBase, path, -1, -1)
    }
  }
}

/*
 * 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.google.common.collect.Range
import com.squareup.wire.schema.internal.parser.ReservedElement

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? Range<Int>)?.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.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.java.Profile
import com.squareup.wire.java.ProfileLoader
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 = SchemaLoader().addSource(root)

  fun add(name: String, protoFile: String): RepoBuilder {
    if (name.endsWith(".proto")) {
      schemaLoader.addProto(name)
    } else if (!name.endsWith(".wire")) {
      throw IllegalArgumentException("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)
    }
  }

  fun schema(): Schema {
    try {
      return schemaLoader.load()
    } catch (e: IOException) {
      throw RuntimeException(e)
    }

  }

  @Throws(IOException::class)
  fun profile(name: String): Profile {
    return ProfileLoader(fs, name)
        .schema(schema())
        .load()
  }

  @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 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 (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()
    if (label != "syntax") throw reader.unexpected("expected 'syntax'")
    reader.require('=')
    val syntaxString = reader.readQuotedString()
    if (syntaxString != "wire2") throw reader.unexpected("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" -> {
        if (packageName != null) throw reader.unexpected(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(location, "unexpected label: $label")
    }
  }

  /** 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" -> {
          if (target != null) throw reader.unexpected(wordLocation, "too many targets")
          target = reader.readWord()
          if (reader.readWord() != "using") throw reader.unexpected("expected 'using'")
          val adapterType = reader.readWord()
          reader.require('#')
          val adapterConstant = reader.readWord()
          reader.require(';')
          adapter = adapterType + '#'.toString() + adapterConstant
        }

        "with" -> {
          withOptions += OptionReader(reader).readOption('=')
          reader.require(';')
        }

        else -> throw reader.unexpected(wordLocation, "unexpected label: $word")
      }
    }

    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.Util.appendDocumentation
import com.squareup.wire.schema.internal.Util.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(this, documentation)
    append("type $type {\n")
    for (option in with) {
      appendIndented(this, "with ${option.toSchema()};\n")
    }
    append("  target $target using $adapter;\n")
    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.Util
import com.squareup.wire.schema.internal.Util.appendDocumentation

data class EnumConstantElement(
  val location: Location,
  val name: String,
  val tag: Int,
  val documentation: String = "",
  val options: List<OptionElement> = emptyList()
) {

  fun toSchema() = buildString {
    appendDocumentation(this, documentation)
    append("$name = $tag")

    if (options.isNotEmpty()) {
      append(" ")
      Util.appendOptions(this, 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.internal.Util.appendDocumentation
import com.squareup.wire.schema.internal.Util.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() = buildString {
    appendDocumentation(this, documentation)
    append("enum $name {")

    if (options.isNotEmpty() || constants.isNotEmpty()) {
      append('\n')
    }

    if (options.isNotEmpty()) {
      for (option in options) {
        appendIndented(this, option.toSchemaDeclaration())
      }
    }
    if (constants.isNotEmpty()) {
      for (constant in constants) {
        appendIndented(this, 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.internal.Util.appendDocumentation
import com.squareup.wire.schema.internal.Util.appendIndented

data class ExtendElement(
  val location: Location,
  val name: String,
  val documentation: String = "",
  val fields: List<FieldElement> = emptyList()
) {
  fun toSchema() = buildString {
    appendDocumentation(this, documentation)
    append("extend $name {")

    if (fields.isNotEmpty()) {
      append('\n')
      for (field in fields) {
        appendIndented(this, field.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.internal.Util
import com.squareup.wire.schema.internal.Util.appendDocumentation

data class ExtensionsElement(
  val location: Location,
  val documentation: String = "",
  val start: Int,
  val end: Int
) {
  fun toSchema() = buildString {
    appendDocumentation(this, documentation)
    append("extensions $start")

    if (start != end) {
      append(" to ")
      if (end < Util.MAX_TAG_VALUE) {
        append(end)
      } else {
        append("max")
      }
    }
    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.internal.Util
import com.squareup.wire.schema.internal.Util.appendDocumentation
import java.util.Locale

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() = buildString {
    appendDocumentation(this, documentation)

    if (label != null) {
      append("${label.name.toLowerCase(Locale.US)} ")
    }
    append("$type $name = $tag")

    if (options.isNotEmpty()) {
      append(' ')
      Util.appendOptions(this, options)
    }

    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.Field
import com.squareup.wire.schema.Location
import com.squareup.wire.schema.internal.Util.appendDocumentation
import com.squareup.wire.schema.internal.Util.appendIndented
import java.util.Locale

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() = buildString {
    appendDocumentation(this, documentation)
    if (label != null) {
      append("${label.name.toLowerCase(Locale.US)} ")
    }
    append("group $name = $tag {")
    if (fields.isNotEmpty()) {
      append('\n')
      for (field in fields) {
        appendIndented(this, field.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.internal.Util.appendDocumentation
import com.squareup.wire.schema.internal.Util.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() = buildString {
    appendDocumentation(this, documentation)
    append("message $name {")

    if (reserveds.isNotEmpty()) {
      append('\n')
      for (reserved in reserveds) {
        appendIndented(this, reserved.toSchema())
      }
    }
    if (options.isNotEmpty()) {
      append('\n')
      for (option in options) {
        appendIndented(this, option.toSchemaDeclaration())
      }
    }
    if (fields.isNotEmpty()) {
      append('\n')
      for (field in fields) {
        appendIndented(this, field.toSchema())
      }
    }
    if (oneOfs.isNotEmpty()) {
      append('\n')
      for (oneOf in oneOfs) {
        appendIndented(this, oneOf.toSchema())
      }
    }
    if (groups.isNotEmpty()) {
      append('\n')
      for (group in groups) {
        appendIndented(this, group.toSchema())
      }
    }
    if (extensions.isNotEmpty()) {
      append('\n')
      for (extension in extensions) {
        appendIndented(this, extension.toSchema())
      }
    }
    if (nestedTypes.isNotEmpty()) {
      append('\n')
      for (type in nestedTypes) {
        appendIndented(this, type.toSchema())
      }
    }
    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.internal.Util.appendDocumentation
import com.squareup.wire.schema.internal.Util.appendIndented

data class OneOfElement(
  val name: String,
  val documentation: String = "",
  val fields: List<FieldElement> = emptyList(),
  val groups: List<GroupElement> = emptyList()
) {
  fun toSchema() = buildString {
    appendDocumentation(this, documentation)
    append("oneof $name {")

    if (fields.isNotEmpty()) {
      append('\n')
      for (field in fields) {
        appendIndented(this, field.toSchema())
      }
    }
    if (groups.isNotEmpty()) {
      append('\n')
      for (group in groups) {
        appendIndented(this, group.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.internal.Util
import com.squareup.wire.schema.internal.Util.appendIndented
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

data class OptionElement(
  val name: String,
  val kind: Kind,
  val value: Any,
  private 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 = ")
        Util.appendOptions(this, 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 ""
      appendIndented(builder, "${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(this, "${formatOptionMapValue(value[index]!!)}$endl")
        }
        append("]")
      }
      else -> {
        append(value)
      }
    }
  }

  companion object {
    @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.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
import java.util.ArrayList
import java.util.LinkedHashMap

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 ','.
      if (!reader.peekChar(',')) throw reader.unexpected("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 if (c != keyValueSeparator) {
      throw reader.unexpected("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 {
    val peeked = reader.peekChar()
    when (peeked) {
      '{' -> return KindAndValue(MAP, readMap('{', '}', ':'))
      '[' -> return KindAndValue(LIST, readList())
      '"', '\'' -> return KindAndValue(STRING, reader.readString())
      else -> {
        if (Character.isDigit(peeked) || peeked == '-') {
          return KindAndValue(NUMBER, reader.readWord())
        }
        val word = reader.readWord()
        return when (word) {
          "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
      if (reader.peekChar() != ']') throw reader.unexpected("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

/** 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 {
    append("// ")
    append(location)
    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())
      }
    }
    if (extendDeclarations.isNotEmpty()) {
      append('\n')
      for (extendDeclaration in extendDeclarations) {
        append(extendDeclaration.toSchema())
      }
    }
    if (services.isNotEmpty()) {
      append('\n')
      for (service in services) {
        append(service.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.schema.internal.parser

import com.google.common.collect.Range
import com.squareup.wire.schema.Location
import com.squareup.wire.schema.internal.Util.appendDocumentation

data class ReservedElement(
  val location: Location,
  val documentation: String = "",
  /** A [String] name or [Integer] or [Range<Int>][Range] tag. */
  val values: List<Any>
) {
  fun toSchema() = buildString {
    appendDocumentation(this, documentation)
    append("reserved ")

    val value = values
    for (i in value.indices) {
      if (i > 0) append(", ")

      val reservation = value[i]
      when (reservation) {
        is String -> append("\"$reservation\"")
        is Int -> append(reservation)
        is Range<*> -> {
          val range = reservation as Range<Int>
          append("${range.lowerEndpoint()} to ${range.upperEndpoint()}")
        }
        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.Util.appendDocumentation
import com.squareup.wire.schema.internal.Util.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(this, 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(this, 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.Util.appendDocumentation
import com.squareup.wire.schema.internal.Util.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(this, documentation)
    append("service $name {")
    if (options.isNotEmpty()) {
      append('\n')
      for (option in options) {
        appendIndented(this, option.toSchemaDeclaration())
      }
    }
    if (rpcs.isNotEmpty()) {
      append('\n')
      for (rpc in rpcs) {
        appendIndented(this, rpc.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

/** 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(): String
}