diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/AdapterGenerator.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/AdapterGenerator.kt
index 7008b9b..813c3a3 100644
--- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/AdapterGenerator.kt
+++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/AdapterGenerator.kt
@@ -303,7 +303,7 @@ internal class AdapterGenerator(

     result.addStatement("%N.beginObject()", writerParam)
     propertyList.forEach { property ->
-      result.addStatement("%N.name(%S)", writerParam, property.jsonName)
+      result.addStatement("%N.name(\\"${property.jsonName}\\")", writerParam)
       result.addStatement("%N.toJson(%N, %N.%L)",
           nameAllocator.get(property.delegateKey), writerParam, valueParam, property.name)
     }
diff --git a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/TargetProperty.kt b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/TargetProperty.kt
index a0decdc..e1b14e5 100644
--- a/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/TargetProperty.kt
+++ b/kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/TargetProperty.kt
@@ -128,7 +128,7 @@ internal data class TargetProperty(
   private val Element?.jsonName: String?
     get() {
       if (this == null) return null
-      return getAnnotation(Json::class.java)?.name
+      return getAnnotation(Json::class.java)?.name?.replace("$", "\\\\$")
     }

   override fun toString() = name
diff --git a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codgen/GeneratedAdaptersTest.kt b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codgen/GeneratedAdaptersTest.kt
index f302c3e..300d827 100644
--- a/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codgen/GeneratedAdaptersTest.kt
+++ b/kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codgen/GeneratedAdaptersTest.kt
@@ -55,6 +55,27 @@ class GeneratedAdaptersTest {
   @JsonClass(generateAdapter = true)
   data class JsonAnnotation(@Json(name = "foo") val bar: String)

+  @Test
+  fun jsonAnnotationWithDollarSign() {
+    val adapter = moshi.adapter(JsonAnnotationWithDollarSign::class.java)
+
+    // Read
+    @Language("JSON")
+    val json = "{\\"\\$foo\\": \\"bar\\"}"
+
+    val instance = adapter.fromJson(json)!!
+    assertThat(instance.bar).isEqualTo("bar")
+
+    // Write
+    @Language("JSON")
+    val expectedJson = "{\\"\\$foo\\":\\"baz\\"}"
+
+    assertThat(adapter.toJson(JsonAnnotationWithDollarSign("baz"))).isEqualTo(expectedJson)
+  }
+
+  @JsonClass(generateAdapter = true)
+  data class JsonAnnotationWithDollarSign(@Json(name = "\\$foo") val bar: String)
+
   @Test
   fun defaultValues() {
     val adapter = moshi.adapter(DefaultValues::class.java)

Codegen @Json wrong name generation when using special characters
When using something like:

```Kotlin
data class TimeStamp(@Json(name = "\\$date") val millis: Long)
```

In the generated adapter the name gets converted to:

```Kotlin
JsonReader.Options.of("$date")
```

Which causes an Unresolved reference error when compiling, since the compiler is looking for a variable named 'date'

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.squareup.kotlinpoet.ARRAY
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
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.TypeSpec
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import me.eugeniomarletti.kotlin.metadata.isDataClass
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility
import me.eugeniomarletti.kotlin.metadata.visibility
import java.lang.reflect.Type
import javax.annotation.processing.Messager
import javax.lang.model.element.TypeElement

/** Generates a JSON adapter for a target type. */
internal class AdapterGenerator(
  target: TargetType,
  private val propertyList: List<PropertyGenerator>
) {
  private val className = target.name
  private val isDataClass = target.proto.isDataClass
  private val companionObjectName = target.companionObjectName
  private val visibility = target.proto.visibility!!
  private val typeVariables = target.typeVariables

  private val nameAllocator = NameAllocator()
  private val adapterName = "${className.simpleNames.joinToString(separator = "_")}JsonAdapter"
  private val originalTypeName = target.element.asType().asTypeName()

  private val moshiParam = ParameterSpec.builder(
      nameAllocator.newName("moshi"),
      Moshi::class).build()
  private val typesParam = ParameterSpec.builder(
      nameAllocator.newName("types"),
      ARRAY.parameterizedBy(Type::class.asTypeName()))
      .build()
  private val readerParam = ParameterSpec.builder(
      nameAllocator.newName("reader"),
      JsonReader::class)
      .build()
  private val writerParam = ParameterSpec.builder(
      nameAllocator.newName("writer"),
      JsonWriter::class)
      .build()
  private val valueParam = ParameterSpec.builder(
      nameAllocator.newName("value"),
      originalTypeName.asNullable())
      .build()
  private val jsonAdapterTypeName = JsonAdapter::class.asClassName().parameterizedBy(originalTypeName)

  // selectName() API setup
  private val optionsProperty = PropertySpec.builder(
      nameAllocator.newName("options"), JsonReader.Options::class.asTypeName(),
      KModifier.PRIVATE)
      .initializer("%T.of(${propertyList.map { it.jsonName }
          .joinToString(", ") { "\"$it\"" }})", JsonReader.Options::class.asTypeName())
      .build()

  fun generateFile(messager: Messager, generatedOption: TypeElement?): FileSpec {
    for (property in propertyList) {
      property.allocateNames(nameAllocator)
    }

    val result = FileSpec.builder(className.packageName, adapterName)
    result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.")
    companionObjectName?.let {
      result.addFunction(generateJsonAdapterFun(it))
    }
    result.addType(generateType(messager, generatedOption))
    return result.build()
  }

  private fun generateType(messager: Messager, generatedOption: TypeElement?): TypeSpec {
    val result = TypeSpec.classBuilder(adapterName)

    generatedOption?.let {
      result.addAnnotation(AnnotationSpec.builder(it.asClassName())
          .addMember("value = [%S]", JsonClassCodegenProcessor::class.java.canonicalName)
          .addMember("comments = %S", "https://github.com/square/moshi")
          .build())
    }

    result.superclass(jsonAdapterTypeName)

    if (typeVariables.isNotEmpty()) {
      result.addTypeVariables(typeVariables)
    }

    // TODO make this configurable. Right now it just matches the source model
    if (visibility == Visibility.INTERNAL) {
      result.addModifiers(KModifier.INTERNAL)
    }

    result.primaryConstructor(generateConstructor())

    val typeRenderer: TypeRenderer = object : TypeRenderer() {
      override fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock {
        val index = typeVariables.indexOfFirst { it == typeVariable }
        check(index != -1) { "Unexpected type variable $typeVariable" }
        return CodeBlock.of("%N[%L]", typesParam, index)
      }
    }

    result.addProperty(optionsProperty)
    for (uniqueAdapter in propertyList.distinctBy { it.delegateKey }) {
      result.addProperty(uniqueAdapter.delegateKey.generateProperty(
          nameAllocator, typeRenderer, moshiParam, messager))
    }

    result.addFunction(generateToStringFun())
    result.addFunction(generateFromJsonFun())
    result.addFunction(generateToJsonFun())

    return result.build()
  }

  private fun generateConstructor(): FunSpec {
    val result = FunSpec.constructorBuilder()
    result.addParameter(moshiParam)

    if (typeVariables.isNotEmpty()) {
      result.addParameter(typesParam)
    }

    return result.build()
  }

  private fun generateToStringFun(): FunSpec {
    return FunSpec.builder("toString")
        .addModifiers(KModifier.OVERRIDE)
        .returns(String::class)
        .addStatement("return %S",
            "GeneratedJsonAdapter(${originalTypeName.rawType().simpleNames.joinToString(".")})")
        .build()
  }

  private fun generateFromJsonFun(): FunSpec {
    val resultName = nameAllocator.newName("result")

    val result = FunSpec.builder("fromJson")
        .addModifiers(KModifier.OVERRIDE)
        .addParameter(readerParam)
        .returns(originalTypeName)

    for (property in propertyList) {
      result.addCode("%L", property.generateLocalProperty())
      if (property.differentiateAbsentFromNull) {
        result.addCode("%L", property.generateLocalIsPresentProperty())
      }
    }

    result.addStatement("%N.beginObject()", readerParam)
    result.beginControlFlow("while (%N.hasNext())", readerParam)
    result.beginControlFlow("when (%N.selectName(%N))", readerParam, optionsProperty)

    propertyList.forEachIndexed { index, property ->
      if (property.differentiateAbsentFromNull) {
        result.beginControlFlow("%L -> ", index)
        if (property.delegateKey.nullable) {
          result.addStatement("%N = %N.fromJson(%N)",
              property.localName, nameAllocator.get(property.delegateKey), readerParam)
        } else {
          result.addStatement("%N = %N.fromJson(%N)" +
              " ?: throw %T(\"Non-null value '%N' was null at \${%N.path}\")",
              property.localName, nameAllocator.get(property.delegateKey), readerParam,
              JsonDataException::class, property.localName, readerParam)
        }
        result.addStatement("%N = true", property.localIsPresentName)
        result.endControlFlow()
      } else {
        if (property.delegateKey.nullable) {
          result.addStatement("%L -> %N = %N.fromJson(%N)",
              index, property.localName, nameAllocator.get(property.delegateKey), readerParam)
        } else {
          result.addStatement("%L -> %N = %N.fromJson(%N)" +
              " ?: throw %T(\"Non-null value '%N' was null at \${%N.path}\")",
              index, property.localName, nameAllocator.get(property.delegateKey), readerParam,
              JsonDataException::class, property.localName, readerParam)
        }
      }
    }

    result.beginControlFlow("-1 ->")
    result.addComment("Unknown name, skip it.")
    result.addStatement("%N.skipName()", readerParam)
    result.addStatement("%N.skipValue()", readerParam)
    result.endControlFlow()

    result.endControlFlow() // when
    result.endControlFlow() // while
    result.addStatement("%N.endObject()", readerParam)

    // Call the constructor providing only required parameters.
    var hasOptionalParameters = false
    result.addCode("%[var %N = %T(", resultName, originalTypeName)
    var separator = "\n"
    for (property in propertyList) {
      if (!property.hasConstructorParameter) {
        continue
      }
      if (property.hasDefault) {
        hasOptionalParameters = true
        continue
      }
      result.addCode(separator)
      result.addCode("%N = %N", property.name, property.localName)
      if (property.isRequired) {
        result.addCode(" ?: throw %T(\"Required property '%L' missing at \${%N.path}\")",
            JsonDataException::class, property.localName, readerParam)
      }
      separator = ",\n"
    }
    result.addCode(")%]\n", originalTypeName)

    // Call either the constructor again, or the copy() method, this time providing any optional
    // parameters that we have.
    if (hasOptionalParameters) {
      if (isDataClass) {
        result.addCode("%[%1N = %1N.copy(", resultName)
      } else {
        result.addCode("%[%1N = %2T(", resultName, originalTypeName)
      }
      separator = "\n"
      for (property in propertyList) {
        if (!property.hasConstructorParameter) {
          continue // No constructor parameter for this property.
        }
        if (isDataClass && !property.hasDefault) {
          continue // Property already assigned.
        }

        result.addCode(separator)
        if (property.differentiateAbsentFromNull) {
          result.addCode("%2N = if (%3N) %4N else %1N.%2N",
              resultName, property.name, property.localIsPresentName, property.localName)
        } else {
          result.addCode("%2N = %3N ?: %1N.%2N", resultName, property.name, property.localName)
        }
        separator = ",\n"
      }
      result.addCode("%])\n")
    }

    // Assign properties not present in the constructor.
    for (property in propertyList) {
      if (property.hasConstructorParameter) {
        continue // Property already handled.
      }
      if (property.differentiateAbsentFromNull) {
        result.addStatement("%1N.%2N = if (%3N) %4N else %1N.%2N",
            resultName, property.name, property.localIsPresentName, property.localName)
      } else {
        result.addStatement("%1N.%2N = %3N ?: %1N.%2N",
            resultName, property.name, property.localName)
      }
    }

    result.addStatement("return %1N", resultName)
    return result.build()
  }

  private fun generateToJsonFun(): FunSpec {
    val result = FunSpec.builder("toJson")
        .addModifiers(KModifier.OVERRIDE)
        .addParameter(writerParam)
        .addParameter(valueParam)

    result.beginControlFlow("if (%N == null)", valueParam)
    result.addStatement("throw %T(%S)", NullPointerException::class,
        "${valueParam.name} was null! Wrap in .nullSafe() to write nullable values.")
    result.endControlFlow()

    result.addStatement("%N.beginObject()", writerParam)
    propertyList.forEach { property ->
      result.addStatement("%N.name(%S)", writerParam, property.jsonName)
      result.addStatement("%N.toJson(%N, %N.%L)",
          nameAllocator.get(property.delegateKey), writerParam, valueParam, property.name)
    }
    result.addStatement("%N.endObject()", writerParam)

    return result.build()
  }

  private fun generateJsonAdapterFun(name: String): FunSpec {
    val rawType = when (originalTypeName) {
      is TypeVariableName -> throw IllegalArgumentException("Cannot get raw type of TypeVariable!")
      is ParameterizedTypeName -> originalTypeName.rawType
      else -> originalTypeName as ClassName
    }

    val result = FunSpec.builder("jsonAdapter")
        .receiver(rawType.nestedClass(name))
        .returns(jsonAdapterTypeName)
        .addParameter(moshiParam)

    if (visibility == Visibility.INTERNAL) {
      result.addModifiers(KModifier.INTERNAL)
    }

    if (typeVariables.isNotEmpty()) {
      result.addParameter(typesParam)
      result.addTypeVariables(typeVariables)
      result.addStatement("return %N(%N, %N)", adapterName, moshiParam, typesParam)
    } else {
      result.addStatement("return %N(%N)", adapterName, moshiParam)
    }

    return result.build()
  }
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.asTypeName
import javax.lang.model.element.TypeElement
import javax.lang.model.type.DeclaredType
import javax.lang.model.util.Types

/**
 * A concrete type like `List<String>` with enough information to know how to resolve its type
 * variables.
 */
internal class AppliedType private constructor(
  val element: TypeElement,
  val resolver: TypeResolver,
  private val mirror: DeclaredType
) {
  /** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */
  fun supertypes(
    types: Types,
    result: MutableSet<AppliedType> = mutableSetOf()
  ): Set<AppliedType> {
    result.add(this)
    for (supertype in types.directSupertypes(mirror)) {
      val supertypeDeclaredType = supertype as DeclaredType
      val supertypeElement = supertypeDeclaredType.asElement() as TypeElement
      val appliedSupertype = AppliedType(supertypeElement,
          resolver(supertypeElement, supertypeDeclaredType), supertypeDeclaredType)
      appliedSupertype.supertypes(types, result)
    }
    return result
  }

  /** Returns a resolver that uses `element` and `mirror` to resolve type parameters. */
  private fun resolver(element: TypeElement, mirror: DeclaredType): TypeResolver {
    return object : TypeResolver() {
      override fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName {
        val index = element.typeParameters.indexOfFirst {
          it.simpleName.toString() == typeVariable.name
        }
        check(index != -1) { "Unexpected type variable $typeVariable in $mirror" }
        val argument = mirror.typeArguments[index]
        return argument.asTypeName()
      }
    }
  }

  override fun toString() = mirror.toString()

  companion object {
    fun get(typeElement: TypeElement): AppliedType {
      return AppliedType(typeElement, TypeResolver(), typeElement.asType() as DeclaredType)
    }
  }
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.google.auto.common.MoreTypes
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.KModifier
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.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Types
import java.lang.annotation.ElementType
import java.lang.annotation.RetentionPolicy
import javax.annotation.processing.Messager
import javax.lang.model.element.AnnotationMirror
import javax.tools.Diagnostic.Kind.ERROR

/** A JsonAdapter that can be used to encode and decode a particular field. */
internal data class DelegateKey(
  private val type: TypeName,
  private val jsonQualifiers: Set<AnnotationMirror>
) {
  val nullable get() = type.nullable

  /** Returns an adapter to use when encoding and decoding this property. */
  fun generateProperty(
    nameAllocator: NameAllocator,
    typeRenderer: TypeRenderer,
    moshiParameter: ParameterSpec,
    messager: Messager): PropertySpec {
    fun AnnotationMirror.validate(): AnnotationMirror {
      // Check java types since that covers both java and kotlin annotations
      val annotationElement = MoreTypes.asTypeElement(annotationType)
      annotationElement.getAnnotation(java.lang.annotation.Retention::class.java)?.let {
        if (it.value != RetentionPolicy.RUNTIME) {
          messager.printMessage(ERROR, "JsonQualifier " +
              "@${MoreTypes.asTypeElement(annotationType).simpleName} must have RUNTIME retention")
        }
      }
      annotationElement.getAnnotation(java.lang.annotation.Target::class.java)?.let {
        if (ElementType.FIELD !in it.value) {
          messager.printMessage(ERROR, "JsonQualifier " +
              "@${MoreTypes.asTypeElement(annotationType).simpleName} must support FIELD target")
        }
      }
      return this
    }
    jsonQualifiers.forEach { it.validate() }
    val qualifierNames = jsonQualifiers.joinToString("") {
      "At${it.annotationType.asElement().simpleName}"
    }
    val adapterName = nameAllocator.newName(
        "${type.toVariableName().decapitalize()}${qualifierNames}Adapter", this)

    val adapterTypeName = JsonAdapter::class.asClassName().parameterizedBy(type)
    val qualifiers = jsonQualifiers
    val standardArgs = arrayOf(moshiParameter,
        if (type is ClassName && qualifiers.isEmpty()) {
          ""
        } else {
          CodeBlock.of("<%T>", type)
        },
        typeRenderer.render(type))
    val standardArgsSize = standardArgs.size + 1
    val (initializerString, args) = when {
      qualifiers.isEmpty() -> "" to emptyArray()
      else -> {
        ", %${standardArgsSize}T.getFieldJsonQualifierAnnotations(javaClass, " +
            "%${standardArgsSize + 1}S)" to arrayOf(Types::class.asTypeName(), adapterName)
      }
    }
    val finalArgs = arrayOf(*standardArgs, *args)

    val nullModifier = if (nullable) ".nullSafe()" else ".nonNull()"

    return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE)
        .addAnnotations(qualifiers.map {
          AnnotationSpec.get(it).toBuilder().useSiteTarget(FIELD).build()
        })
        .initializer("%1N.adapter%2L(%3L$initializerString)$nullModifier", *finalArgs)
        .build()
  }
}

/**
 * Returns a suggested variable name derived from a list of type names. This just concatenates,
 * yielding types like MapOfStringLong.
 */
private fun List<TypeName>.toVariableNames() = joinToString("") { it.toVariableName() }

/** Returns a suggested variable name derived from a type name, like nullableListOfString. */
private fun TypeName.toVariableName(): String {
  val base = when (this) {
    is ClassName -> simpleName
    is ParameterizedTypeName -> rawType.simpleName + "Of" + typeArguments.toVariableNames()
    is WildcardTypeName -> (lowerBounds + upperBounds).toVariableNames()
    is TypeVariableName -> name + bounds.toVariableNames()
    else -> throw IllegalArgumentException("Unrecognized type! $this")
  }

  return if (nullable) {
    "Nullable$base"
  } else {
    base
  }
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.google.auto.service.AutoService
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.moshi.JsonClass
import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue
import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor
import java.io.File
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic.Kind.ERROR

/**
 * An annotation processor that reads Kotlin data classes and generates Moshi JsonAdapters for them.
 * This generates Kotlin code, and understands basic Kotlin language features like default values
 * and companion objects.
 *
 * The generated class will match the visibility of the given data class (i.e. if it's internal, the
 * adapter will also be internal).
 *
 * If you define a companion object, a jsonAdapter() extension function will be generated onto it.
 * If you don't want this though, you can use the runtime [JsonClass] factory implementation.
 */
@AutoService(Processor::class)
class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils {

  companion object {
    /**
     * This annotation processing argument can be specified to have a `@Generated` annotation
     * included in the generated code. It is not encouraged unless you need it for static analysis
     * reasons and not enabled by default.
     *
     * Note that this can only be one of the following values:
     *   * `"javax.annotation.processing.Generated"` (JRE 9+)
     *   * `"javax.annotation.Generated"` (JRE <9)
     */
    const val OPTION_GENERATED = "moshi.generated"
    private val POSSIBLE_GENERATED_NAMES = setOf(
        "javax.annotation.processing.Generated",
        "javax.annotation.Generated"
    )
  }

  private val annotation = JsonClass::class.java
  private var generatedType: TypeElement? = null

  override fun getSupportedAnnotationTypes() = setOf(annotation.canonicalName)

  override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()

  override fun getSupportedOptions() = setOf(OPTION_GENERATED)

  override fun init(processingEnv: ProcessingEnvironment) {
    super.init(processingEnv)
    generatedType = processingEnv.options[OPTION_GENERATED]?.let {
      if (it !in POSSIBLE_GENERATED_NAMES) {
        throw IllegalArgumentException("Invalid option value for $OPTION_GENERATED. Found $it, " +
            "allowable values are $POSSIBLE_GENERATED_NAMES.")
      }
      processingEnv.elementUtils.getTypeElement(it)
    }
  }

  override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
    for (type in roundEnv.getElementsAnnotatedWith(annotation)) {
      val jsonClass = type.getAnnotation(annotation)
      if (jsonClass.generateAdapter) {
        val generator = adapterGenerator(type) ?: continue
        generator.generateAndWrite(generatedType)
      }
    }

    return true
  }

  private fun adapterGenerator(element: Element): AdapterGenerator? {
    val type = TargetType.get(messager, elementUtils, typeUtils, element) ?: return null

    val properties = mutableMapOf<String, PropertyGenerator>()
    for (property in type.properties.values) {
      val generator = property.generator(messager)
      if (generator != null) {
        properties[property.name] = generator
      }
    }

    for ((name, parameter) in type.constructor.parameters) {
      if (type.properties[parameter.name] == null && !parameter.proto.declaresDefaultValue) {
        messager.printMessage(
            ERROR, "No property for required constructor parameter $name", parameter.element)
        return null
      }
    }

    // Sort properties so that those with constructor parameters come first.
    val sortedProperties = properties.values.sortedBy {
      if (it.hasConstructorParameter) {
        it.target.parameterIndex
      } else {
        Integer.MAX_VALUE
      }
    }

    return AdapterGenerator(type, sortedProperties)
  }

  private fun AdapterGenerator.generateAndWrite(generatedOption: TypeElement?) {
    val fileSpec = generateFile(messager, generatedOption)
    val adapterName = fileSpec.members.filterIsInstance<TypeSpec>().first().name!!
    val outputDir = generatedDir ?: mavenGeneratedDir(adapterName)
    fileSpec.writeTo(outputDir)
  }

  private fun mavenGeneratedDir(adapterName: String): File {
    // Hack since the maven plugin doesn't supply `kapt.kotlin.generated` option
    // Bug filed at https://youtrack.jetbrains.com/issue/KT-22783
    val file = filer.createSourceFile(adapterName).toUri().let(::File)
    return file.parentFile.also { file.delete() }
  }
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.NameAllocator
import com.squareup.kotlinpoet.PropertySpec

/** Generates functions to encode and decode a property as JSON. */
internal class PropertyGenerator(val target: TargetProperty) {
  val delegateKey = target.delegateKey()
  val name = target.name
  val jsonName = target.jsonName()
  val hasDefault = target.hasDefault

  lateinit var localName: String
  lateinit var localIsPresentName: String

  val isRequired get() = !delegateKey.nullable && !hasDefault

  val hasConstructorParameter get() = target.parameterIndex != -1

  /** We prefer to use 'null' to mean absent, but for some properties those are distinct. */
  val differentiateAbsentFromNull get() = delegateKey.nullable && hasDefault

  fun allocateNames(nameAllocator: NameAllocator) {
    localName = nameAllocator.newName(name)
    localIsPresentName = nameAllocator.newName("${name}Set")
  }

  fun generateLocalProperty(): PropertySpec {
    return PropertySpec.builder(localName, target.type.asNullable())
        .mutable(true)
        .initializer("null")
        .build()
  }

  fun generateLocalIsPresentProperty(): PropertySpec {
    return PropertySpec.builder(localIsPresentName, BOOLEAN)
        .mutable(true)
        .initializer("false")
        .build()
  }
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
import me.eugeniomarletti.kotlin.metadata.isPrimary
import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Constructor
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement
import javax.lang.model.util.Elements

/** A constructor in user code that should be called by generated code. */
internal data class TargetConstructor(
  val element: ExecutableElement,
  val proto: Constructor,
  val parameters: Map<String, TargetParameter>
) {
  companion object {
    fun primary(metadata: KotlinClassMetadata, elements: Elements): TargetConstructor {
      val (nameResolver, classProto) = metadata.data

      // todo allow custom constructor
      val proto = classProto.constructorList
          .single { it.isPrimary }
      val constructorJvmSignature = proto.getJvmConstructorSignature(
          nameResolver, classProto.typeTable)
      val element = classProto.fqName
          .let(nameResolver::getString)
          .replace('/', '.')
          .let(elements::getTypeElement)
          .enclosedElements
          .mapNotNull {
            it.takeIf { it.kind == ElementKind.CONSTRUCTOR }?.let { it as ExecutableElement }
          }
          .first()
      // TODO Temporary until JVM method signature matching is better
      //  .single { it.jvmMethodSignature == constructorJvmSignature }

      val parameters = mutableMapOf<String, TargetParameter>()
      for (parameter in proto.valueParameterList) {
        val name = nameResolver.getString(parameter.name)
        val index = proto.valueParameterList.indexOf(parameter)
        parameters[name] = TargetParameter(name, parameter, index, element.parameters[index])
      }

      return TargetConstructor(element, proto, parameters)
    }
  }
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.ValueParameter
import javax.lang.model.element.VariableElement

/** A parameter in user code that should be populated by generated code. */
internal data class TargetParameter(
  val name: String,
  val proto: ValueParameter,
  val index: Int,
  val element: VariableElement
)

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.google.auto.common.AnnotationMirrors
import com.squareup.kotlinpoet.TypeName
import com.squareup.moshi.Json
import com.squareup.moshi.JsonQualifier
import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue
import me.eugeniomarletti.kotlin.metadata.hasSetter
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Property
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.INTERNAL
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PROTECTED
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PUBLIC
import me.eugeniomarletti.kotlin.metadata.visibility
import javax.annotation.processing.Messager
import javax.lang.model.element.AnnotationMirror
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier
import javax.lang.model.element.VariableElement
import javax.tools.Diagnostic

/** A property in user code that maps to JSON. */
internal data class TargetProperty(
  val name: String,
  val type: TypeName,
  private val proto: Property,
  private val parameter: TargetParameter?,
  private val annotationHolder: ExecutableElement?,
  private val field: VariableElement?,
  private val setter: ExecutableElement?,
  private val getter: ExecutableElement?
) {
  val parameterIndex get() = parameter?.index ?: -1

  val hasDefault get() = parameter?.proto?.declaresDefaultValue ?: true

  private val isTransient get() = field != null && Modifier.TRANSIENT in field.modifiers

  private val element get() = field ?: setter ?: getter!!

  private val isSettable get() = proto.hasSetter || parameter != null

  private val isVisible: Boolean
    get() {
      return proto.visibility == INTERNAL
          || proto.visibility == PROTECTED
          || proto.visibility == PUBLIC
    }

  /**
   * Returns a generator for this property, or null if either there is an error and this property
   * cannot be used with code gen, or if no codegen is necessary for this property.
   */
  fun generator(messager: Messager): PropertyGenerator? {
    if (isTransient) {
      if (!hasDefault) {
        messager.printMessage(
            Diagnostic.Kind.ERROR, "No default value for transient property ${this}", element)
        return null
      }
      return null // This property is transient and has a default value. Ignore it.
    }

    if (!isVisible) {
      messager.printMessage(Diagnostic.Kind.ERROR, "property ${this} is not visible", element)
      return null
    }

    if (!isSettable) {
      return null // This property is not settable. Ignore it.
    }

    return PropertyGenerator(this)
  }

  fun delegateKey() = DelegateKey(type, jsonQualifiers())

  /** Returns the JsonQualifiers on the field and parameter of this property. */
  private fun jsonQualifiers(): Set<AnnotationMirror> {
    val elementQualifiers = element.qualifiers
    val annotationHolderQualifiers = annotationHolder.qualifiers
    val parameterQualifiers = parameter?.element.qualifiers

    // TODO(jwilson): union the qualifiers somehow?
    return when {
      elementQualifiers.isNotEmpty() -> elementQualifiers
      annotationHolderQualifiers.isNotEmpty() -> annotationHolderQualifiers
      parameterQualifiers.isNotEmpty() -> parameterQualifiers
      else -> setOf()
    }
  }

  private val Element?.qualifiers: Set<AnnotationMirror>
    get() {
      if (this == null) return setOf()
      return AnnotationMirrors.getAnnotatedAnnotations(this, JsonQualifier::class.java)
    }

  /** Returns the @Json name of this property, or this property's name if none is provided. */
  fun jsonName(): String {
    val fieldJsonName = element.jsonName
    val annotationHolderJsonName = annotationHolder.jsonName
    val parameterJsonName = parameter?.element.jsonName

    return when {
      fieldJsonName != null -> fieldJsonName
      annotationHolderJsonName != null -> annotationHolderJsonName
      parameterJsonName != null -> parameterJsonName
      else -> name
    }
  }

  private val Element?.jsonName: String?
    get() {
      if (this == null) return null
      return getAnnotation(Json::class.java)?.name
    }

  override fun toString() = name
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
import me.eugeniomarletti.kotlin.metadata.KotlinMetadata
import me.eugeniomarletti.kotlin.metadata.classKind
import me.eugeniomarletti.kotlin.metadata.getPropertyOrNull
import me.eugeniomarletti.kotlin.metadata.isInnerClass
import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
import me.eugeniomarletti.kotlin.metadata.modality
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Class
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Modality.ABSTRACT
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.INTERNAL
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.LOCAL
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PUBLIC
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver
import me.eugeniomarletti.kotlin.metadata.shadow.util.capitalizeDecapitalize.decapitalizeAsciiOnly
import me.eugeniomarletti.kotlin.metadata.visibility
import javax.annotation.processing.Messager
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import javax.tools.Diagnostic.Kind.ERROR

/** A user type that should be decoded and encoded by generated code. */
internal data class TargetType(
  val proto: Class,
  val element: TypeElement,
  val constructor: TargetConstructor,
  val properties: Map<String, TargetProperty>,
  val typeVariables: List<TypeVariableName>,
  val companionObjectName: String?
) {
  val name = element.className

  companion object {
    private val OBJECT_CLASS = ClassName("java.lang", "Object")

    /** Returns a target type for `element`, or null if it cannot be used with code gen. */
    fun get(messager: Messager, elements: Elements, types: Types, element: Element): TargetType? {
      val typeMetadata: KotlinMetadata? = element.kotlinMetadata
      if (element !is TypeElement || typeMetadata !is KotlinClassMetadata) {
        messager.printMessage(
            ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
        return null
      }

      val proto = typeMetadata.data.classProto
      when {
        proto.classKind == Class.Kind.ENUM_CLASS -> {
          messager.printMessage(
              ERROR, "@JsonClass can't be applied to $element: must not be an enum class", element)
          return null
        }
        proto.classKind != Class.Kind.CLASS -> {
          messager.printMessage(
              ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
          return null
        }
        proto.isInnerClass -> {
          messager.printMessage(
              ERROR, "@JsonClass can't be applied to $element: must not be an inner class", element)
          return null
        }
        proto.modality == ABSTRACT -> {
          messager.printMessage(
              ERROR, "@JsonClass can't be applied to $element: must not be abstract", element)
          return null
        }
        proto.visibility == LOCAL -> {
          messager.printMessage(
              ERROR, "@JsonClass can't be applied to $element: must not be local", element)
          return null
        }
      }

      val typeVariables = genericTypeNames(proto, typeMetadata.data.nameResolver)
      val appliedType = AppliedType.get(element)

      val constructor = TargetConstructor.primary(typeMetadata, elements)
      if (constructor.proto.visibility != INTERNAL && constructor.proto.visibility != PUBLIC) {
        messager.printMessage(ERROR, "@JsonClass can't be applied to $element: " +
            "primary constructor is not internal or public", element)
        return null
      }

      val properties = mutableMapOf<String, TargetProperty>()
      for (supertype in appliedType.supertypes(types)) {
        if (supertype.element.asClassName() == OBJECT_CLASS) {
          continue // Don't load properties for java.lang.Object.
        }
        if (supertype.element.kind != ElementKind.CLASS) {
          continue // Don't load properties for interface types.
        }
        if (supertype.element.kotlinMetadata == null) {
          messager.printMessage(ERROR,
              "@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type",
              element)
          return null
        }
        val supertypeProperties = declaredProperties(
            supertype.element, supertype.resolver, constructor)
        for ((name, property) in supertypeProperties) {
          properties.putIfAbsent(name, property)
        }
      }
      val companionObjectName = if (proto.hasCompanionObjectName()) {
        typeMetadata.data.nameResolver.getQualifiedClassName(proto.companionObjectName)
      } else {
        null
      }
      return TargetType(proto, element, constructor, properties, typeVariables, companionObjectName)
    }

    /** Returns the properties declared by `typeElement`. */
    private fun declaredProperties(
      typeElement: TypeElement,
      typeResolver: TypeResolver,
      constructor: TargetConstructor
    ): Map<String, TargetProperty> {
      val typeMetadata: KotlinClassMetadata = typeElement.kotlinMetadata as KotlinClassMetadata
      val nameResolver = typeMetadata.data.nameResolver
      val classProto = typeMetadata.data.classProto

      val annotationHolders = mutableMapOf<String, ExecutableElement>()
      val fields = mutableMapOf<String, VariableElement>()
      val setters = mutableMapOf<String, ExecutableElement>()
      val getters = mutableMapOf<String, ExecutableElement>()
      for (element in typeElement.enclosedElements) {
        if (element is VariableElement) {
          fields[element.name] = element
        } else if (element is ExecutableElement) {
          when {
            element.name.startsWith("get") -> {
              val name = element.name.substring("get".length).decapitalizeAsciiOnly()
              getters[name] = element
            }
            element.name.startsWith("is") -> {
              val name = element.name.substring("is".length).decapitalizeAsciiOnly()
              getters[name] = element
            }
            element.name.startsWith("set") -> {
              val name = element.name.substring("set".length).decapitalizeAsciiOnly()
              setters[name] = element
            }
          }

          val propertyProto = typeMetadata.data.getPropertyOrNull(element)
          if (propertyProto != null) {
            val name = nameResolver.getString(propertyProto.name)
            annotationHolders[name] = element
          }
        }
      }

      val result = mutableMapOf<String, TargetProperty>()
      for (property in classProto.propertyList) {
        val name = nameResolver.getString(property.name)
        val type = typeResolver.resolve(property.returnType.asTypeName(
            nameResolver, classProto::getTypeParameter, false))
        result[name] = TargetProperty(name, type, property, constructor.parameters[name],
            annotationHolders[name], fields[name], setters[name], getters[name])
      }

      return result
    }

    private val Element.className: ClassName
      get() {
        val typeName = asType().asTypeName()
        return when (typeName) {
          is ClassName -> typeName
          is ParameterizedTypeName -> typeName.rawType
          else -> throw IllegalStateException("unexpected TypeName: ${typeName::class}")
        }
      }

    private val Element.name get() = simpleName.toString()

    private fun genericTypeNames(proto: Class, nameResolver: NameResolver): List<TypeVariableName> {
      return proto.typeParameterList.map {
        val possibleBounds = it.upperBoundList
            .map { it.asTypeName(nameResolver, proto::getTypeParameter, false) }
        val typeVar = if (possibleBounds.isEmpty()) {
          TypeVariableName(
              name = nameResolver.getString(it.name),
              variance = it.varianceModifier)
        } else {
        TypeVariableName(
            name = nameResolver.getString(it.name),
            bounds = *possibleBounds.toTypedArray(),
            variance = it.varianceModifier)
        }
        return@map typeVar.reified(it.reified)
      }
    }

    private val TypeParameter.varianceModifier: KModifier?
      get() {
        return variance.asKModifier().let {
          // We don't redeclare out variance here
          if (it == KModifier.OUT) {
            null
          } else {
            it
          }
        }
      }
  }
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.squareup.kotlinpoet.ARRAY
import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.BYTE
import com.squareup.kotlinpoet.CHAR
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.DOUBLE
import com.squareup.kotlinpoet.FLOAT
import com.squareup.kotlinpoet.INT
import com.squareup.kotlinpoet.LONG
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.SHORT
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.asTypeName
import com.squareup.moshi.Types

/**
 * Renders literals like `Types.newParameterizedType(List::class.java, String::class.java)`.
 * Rendering is pluggable so that type variables can either be resolved or emitted as other code
 * blocks.
 */
abstract class TypeRenderer {
  abstract fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock

  fun render(typeName: TypeName): CodeBlock {
    if (typeName.nullable) {
      return render(typeName.asNonNullable())
    }

    return when (typeName) {
      is ClassName -> CodeBlock.of("%T::class.java", typeName)

      is ParameterizedTypeName -> {
        // If it's an Array type, we shortcut this to return Types.arrayOf()
        if (typeName.rawType == ARRAY) {
          CodeBlock.of("%T.arrayOf(%L)",
              Types::class,
              render(typeName.typeArguments[0].objectType()))
        } else {
          val placeholders = typeName.typeArguments.joinToString(", ") { "%L" }
          CodeBlock.of(
              "%T.newParameterizedType(%T::class.java, $placeholders)",
              Types::class,
              typeName.rawType.objectType(),
              *(typeName.typeArguments.map { render(it.objectType()) }.toTypedArray()))
        }
      }

      is WildcardTypeName -> {
        val target: TypeName
        val method: String
        when {
          typeName.lowerBounds.size == 1 -> {
            target = typeName.lowerBounds[0]
            method = "supertypeOf"
          }
          typeName.upperBounds.size == 1 -> {
            target = typeName.upperBounds[0]
            method = "subtypeOf"
          }
          else -> throw IllegalArgumentException(
              "Unrepresentable wildcard type. Cannot have more than one bound: $typeName")
        }
        CodeBlock.of("%T.%L(%T::class.java)", Types::class, method, target)
      }

      is TypeVariableName -> renderTypeVariable(typeName)

      else -> throw IllegalArgumentException("Unrepresentable type: $typeName")
    }
  }

  private fun TypeName.objectType(): TypeName {
    return when (this) {
      BOOLEAN -> Boolean::class.javaObjectType.asTypeName()
      BYTE -> Byte::class.javaObjectType.asTypeName()
      SHORT -> Short::class.javaObjectType.asTypeName()
      INT -> Integer::class.javaObjectType.asTypeName()
      LONG -> Long::class.javaObjectType.asTypeName()
      CHAR -> Character::class.javaObjectType.asTypeName()
      FLOAT -> Float::class.javaObjectType.asTypeName()
      DOUBLE -> Double::class.javaObjectType.asTypeName()
      else -> this
    }
  }
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy

/**
 * Resolves type parameters against a type declaration. Use this to fill in type variables with
 * their actual type parameters.
 */
open class TypeResolver {
  open fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName = typeVariable

  fun resolve(typeName: TypeName): TypeName {
    return when (typeName) {
      is ClassName -> typeName

      is ParameterizedTypeName -> {
            typeName.rawType.parameterizedBy(*(typeName.typeArguments.map { resolve(it) }.toTypedArray()))
            .asNullableIf(typeName.nullable)
      }

      is WildcardTypeName -> {
        when {
          typeName.lowerBounds.size == 1 -> {
            WildcardTypeName.supertypeOf(resolve(typeName.lowerBounds[0]))
                .asNullableIf(typeName.nullable)
          }
          typeName.upperBounds.size == 1 -> {
            WildcardTypeName.subtypeOf(resolve(typeName.upperBounds[0]))
                .asNullableIf(typeName.nullable)
          }
          else -> {
            throw IllegalArgumentException(
                "Unrepresentable wildcard type. Cannot have more than one bound: $typeName")
          }
        }
      }

      is TypeVariableName -> resolveTypeVariable(typeName)

      else -> throw IllegalArgumentException("Unrepresentable type: $typeName")
    }
  }
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.TypeName

internal fun TypeName.rawType(): ClassName {
  return when (this) {
    is ClassName -> this
    is ParameterizedTypeName -> rawType
    else -> throw IllegalArgumentException("Cannot get raw type from $this")
  }
}

internal fun TypeName.asNullableIf(condition: Boolean): TypeName {
  return if (condition) asNullable() else this
}

/*
 * Copyright (C) 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.moshi.kotlin.codegen

import com.squareup.kotlinpoet.ANY
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.WildcardTypeName
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Type
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter.Variance
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver

internal fun TypeParameter.asTypeName(
  nameResolver: NameResolver,
  getTypeParameter: (index: Int) -> TypeParameter,
  resolveAliases: Boolean = false
): TypeVariableName {
  val possibleBounds = upperBoundList.map {
    it.asTypeName(nameResolver, getTypeParameter, resolveAliases)
  }
  return if (possibleBounds.isEmpty()) {
    TypeVariableName(
        name = nameResolver.getString(name),
        variance = variance.asKModifier())
  } else {
    TypeVariableName(
        name = nameResolver.getString(name),
        bounds = *possibleBounds.toTypedArray(),
        variance = variance.asKModifier())
  }
}

internal fun TypeParameter.Variance.asKModifier(): KModifier? {
  return when (this) {
    Variance.IN -> KModifier.IN
    Variance.OUT -> KModifier.OUT
    Variance.INV -> null
  }
}

/**
 * Returns the TypeName of this type as it would be seen in the source code, including nullability
 * and generic type parameters.
 *
 * @param [nameResolver] a [NameResolver] instance from the source proto
 * @param [getTypeParameter] a function that returns the type parameter for the given index. **Only
 *     called if [ProtoBuf.Type.hasTypeParameter] is true!**
 */
internal fun Type.asTypeName(
  nameResolver: NameResolver,
  getTypeParameter: (index: Int) -> TypeParameter,
  useAbbreviatedType: Boolean = true
): TypeName {

  val argumentList = when {
    useAbbreviatedType && hasAbbreviatedType() -> abbreviatedType.argumentList
    else -> argumentList
  }

  if (hasFlexibleUpperBound()) {
    return WildcardTypeName.subtypeOf(
        flexibleUpperBound.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType))
        .asNullableIf(nullable)
  } else if (hasOuterType()) {
    return WildcardTypeName.supertypeOf(
        outerType.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType))
        .asNullableIf(nullable)
  }

  val realType = when {
    hasTypeParameter() -> return getTypeParameter(typeParameter)
        .asTypeName(nameResolver, getTypeParameter, useAbbreviatedType)
        .asNullableIf(nullable)
    hasTypeParameterName() -> typeParameterName
    useAbbreviatedType && hasAbbreviatedType() -> abbreviatedType.typeAliasName
    else -> className
  }

  var typeName: TypeName =
      ClassName.bestGuess(nameResolver.getString(realType)
          .replace("/", "."))

  if (argumentList.isNotEmpty()) {
    val remappedArgs: Array<TypeName> = argumentList.map { argumentType ->
      val nullableProjection = if (argumentType.hasProjection()) {
        argumentType.projection
      } else null
      if (argumentType.hasType()) {
        argumentType.type.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType)
            .let { argumentTypeName ->
              nullableProjection?.let { projection ->
                when (projection) {
                  Type.Argument.Projection.IN -> WildcardTypeName.supertypeOf(argumentTypeName)
                  Type.Argument.Projection.OUT -> {
                    if (argumentTypeName == ANY) {
                      // This becomes a *, which we actually don't want here.
                      // List<Any> works with List<*>, but List<*> doesn't work with List<Any>
                      argumentTypeName
                    } else {
                      WildcardTypeName.subtypeOf(argumentTypeName)
                    }
                  }
                  Type.Argument.Projection.STAR -> WildcardTypeName.STAR
                  Type.Argument.Projection.INV -> TODO("INV projection is unsupported")
                }
              } ?: argumentTypeName
            }
      } else {
        WildcardTypeName.STAR
      }
    }.toTypedArray()
    typeName = (typeName as ClassName).parameterizedBy(*remappedArgs)
  }

  return typeName.asNullableIf(nullable)
}

/*
 * 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.moshi.kotlin.reflect

import com.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.internal.Util
import com.squareup.moshi.internal.Util.resolve
import java.lang.reflect.Modifier
import java.lang.reflect.Type
import java.util.AbstractMap.SimpleEntry
import kotlin.collections.Map.Entry
import kotlin.reflect.KFunction
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaType

/** Classes annotated with this are eligible for this adapter. */
private val KOTLIN_METADATA = Class.forName("kotlin.Metadata") as Class<out Annotation>

/**
 * Placeholder value used when a field is absent from the JSON. Note that this code
 * distinguishes between absent values and present-but-null values.
 */
private val ABSENT_VALUE = Any()

/**
 * This class encodes Kotlin classes using their properties. It decodes them by first invoking the
 * constructor, and then by setting any additional properties that exist, if any.
 */
internal class KotlinJsonAdapter<T>(
    val constructor: KFunction<T>,
    val bindings: List<Binding<T, Any?>?>,
    val options: JsonReader.Options) : JsonAdapter<T>() {

  override fun fromJson(reader: JsonReader): T {
    val constructorSize = constructor.parameters.size

    // Read each value into its slot in the array.
    val values = Array<Any?>(bindings.size) { ABSENT_VALUE }
    reader.beginObject()
    while (reader.hasNext()) {
      val index = reader.selectName(options)
      val binding = if (index != -1) bindings[index] else null

      if (binding == null) {
        reader.skipName()
        reader.skipValue()
        continue
      }

      if (values[index] !== ABSENT_VALUE) {
        throw JsonDataException(
            "Multiple values for '${constructor.parameters[index].name}' at ${reader.path}")
      }

      values[index] = binding.adapter.fromJson(reader)

      if (values[index] == null && !binding.property.returnType.isMarkedNullable) {
        throw JsonDataException(
            "Non-null value '${binding.property.name}' was null at ${reader.path}")
      }
    }
    reader.endObject()

    // Confirm all parameters are present, optional, or nullable.
    for (i in 0 until constructorSize) {
      if (values[i] === ABSENT_VALUE && !constructor.parameters[i].isOptional) {
        if (!constructor.parameters[i].type.isMarkedNullable) {
          throw JsonDataException(
              "Required value '${constructor.parameters[i].name}' missing at ${reader.path}")
        }
        values[i] = null // Replace absent with null.
      }
    }

    // Call the constructor using a Map so that absent optionals get defaults.
    val result = constructor.callBy(IndexedParameterMap(constructor.parameters, values))

    // Set remaining properties.
    for (i in constructorSize until bindings.size) {
      val binding = bindings[i]!!
      val value = values[i]
      binding.set(result, value)
    }

    return result
  }

  override fun toJson(writer: JsonWriter, value: T?) {
    if (value == null) throw NullPointerException("value == null")

    writer.beginObject()
    for (binding in bindings) {
      if (binding == null) continue // Skip constructor parameters that aren't properties.

      writer.name(binding.name)
      binding.adapter.toJson(writer, binding.get(value))
    }
    writer.endObject()
  }

  override fun toString() = "KotlinJsonAdapter(${constructor.returnType})"

  data class Binding<K, P>(
      val name: String,
      val adapter: JsonAdapter<P>,
      val property: KProperty1<K, P>,
      val parameter: KParameter?) {
    fun get(value: K) = property.get(value)

    fun set(result: K, value: P) {
      if (value !== ABSENT_VALUE) {
        (property as KMutableProperty1<K, P>).set(result, value)
      }
    }
  }

  /** A simple [Map] that uses parameter indexes instead of sorting or hashing. */
  class IndexedParameterMap(val parameterKeys: List<KParameter>, val parameterValues: Array<Any?>)
    : AbstractMap<KParameter, Any?>() {

    override val entries: Set<Entry<KParameter, Any?>>
      get() {
        val allPossibleEntries = parameterKeys.mapIndexed { index, value ->
          SimpleEntry<KParameter, Any?>(value, parameterValues[index])
        }
        return allPossibleEntries.filterTo(LinkedHashSet<Entry<KParameter, Any?>>()) {
          it.value !== ABSENT_VALUE
        }
      }

    override fun containsKey(key: KParameter) = parameterValues[key.index] !== ABSENT_VALUE

    override fun get(key: KParameter): Any? {
      val value = parameterValues[key.index]
      return if (value !== ABSENT_VALUE) value else null
    }
  }
}

class KotlinJsonAdapterFactory : JsonAdapter.Factory {
  override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi)
      : JsonAdapter<*>? {
    if (!annotations.isEmpty()) return null

    val rawType = Types.getRawType(type)
    if (rawType.isInterface) return null
    if (rawType.isEnum) return null
    if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
    if (Util.isPlatformType(rawType)) return null
    val jsonClass = rawType.getAnnotation(JsonClass::class.java)
    if (jsonClass != null && jsonClass.generateAdapter) return null

    if (rawType.isLocalClass) {
      throw IllegalArgumentException("Cannot serialize local class or object expression ${rawType.name}")
    }
    val rawTypeKotlin = rawType.kotlin
    if (rawTypeKotlin.isAbstract) {
      throw IllegalArgumentException("Cannot serialize abstract class ${rawType.name}")
    }
    if (rawTypeKotlin.isInner) {
      throw IllegalArgumentException("Cannot serialize inner class ${rawType.name}")
    }
    if (rawTypeKotlin.objectInstance != null) {
      throw IllegalArgumentException("Cannot serialize object declaration ${rawType.name}")
    }

    val constructor = rawTypeKotlin.primaryConstructor ?: return null
    val parametersByName = constructor.parameters.associateBy { it.name }
    constructor.isAccessible = true

    val bindingsByName = LinkedHashMap<String, KotlinJsonAdapter.Binding<Any, Any?>>()

    for (property in rawTypeKotlin.memberProperties) {
      val parameter = parametersByName[property.name]

      if (Modifier.isTransient(property.javaField?.modifiers ?: 0)) {
        if (parameter != null && !parameter.isOptional) {
          throw IllegalArgumentException(
              "No default value for transient constructor $parameter")
        }
        continue
      }

      if (parameter != null && parameter.type != property.returnType) {
        throw IllegalArgumentException("'${property.name}' has a constructor parameter of type " +
            "${parameter.type} but a property of type ${property.returnType}.")
      }

      if (property !is KMutableProperty1 && parameter == null) continue

      property.isAccessible = true
      var allAnnotations = property.annotations
      var jsonAnnotation = property.findAnnotation<Json>()

      if (parameter != null) {
        allAnnotations += parameter.annotations
        if (jsonAnnotation == null) {
          jsonAnnotation = parameter.findAnnotation<Json>()
        }
      }

      val name = jsonAnnotation?.name ?: property.name
      val resolvedPropertyType = resolve(type, rawType, property.returnType.javaType)
      val adapter = moshi.adapter<Any>(
          resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()))

      bindingsByName[property.name] = KotlinJsonAdapter.Binding(name, adapter,
          property as KProperty1<Any, Any?>, parameter)
    }

    val bindings = ArrayList<KotlinJsonAdapter.Binding<Any, Any?>?>()

    for (parameter in constructor.parameters) {
      val binding = bindingsByName.remove(parameter.name)
      if (binding == null && !parameter.isOptional) {
        throw IllegalArgumentException("No property for required constructor ${parameter}")
      }
      bindings += binding
    }

    bindings += bindingsByName.values

    val options = JsonReader.Options.of(*bindings.map { it?.name ?: "\u0000" }.toTypedArray())
    return KotlinJsonAdapter(constructor, bindings, options).nullSafe()
  }
}