
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import io.emeraldpay.dshackle.BlockchainType
import io.emeraldpay.dshackle.config.ChainsConfig
import io.emeraldpay.dshackle.config.ChainsConfigReader
import io.emeraldpay.dshackle.foundation.ChainOptionsReader
import java.math.BigInteger

open class CodeGen(private val config: ChainsConfig) {
    companion object {
        fun generateFromChains(path: File) {
            val chainConfigReader = ChainsConfigReader(ChainOptionsReader())
            val config = chainConfigReader.read(null)
            CodeGen(config).generateChainsFile().writeTo(path)
        }
    }

    private fun addEnumProperties(builder: TypeSpec.Builder): TypeSpec.Builder {
        builder.addEnumConstant(
            "UNSPECIFIED",
            TypeSpec.anonymousClassBuilder()
                .addSuperclassConstructorParameter("%L, %S, %S, %S, %L, %L, %L", 0, "UNSPECIFIED", "Unknown", "0x0", "BigInteger.ZERO", "emptyList()", "BlockchainType.UNKNOWN")
                .build(),
        )
        for (chain in config) {
            builder.addEnumConstant(
                chain.blockchain.uppercase().replace('-', '_') + "__" + chain.id.uppercase().replace('-', '_')
                    .replace(' ', '_'),
                TypeSpec.anonymousClassBuilder()
                    .addSuperclassConstructorParameter(
                        "%L, %S, %S, %S, %L, %L, %L",
                        chain.grpcId,
                        chain.code,
                        chain.blockchain.replaceFirstChar { it.uppercase() } + " " + chain.id.replaceFirstChar { it.uppercase() },
                        chain.chainId,
                        "BigInteger(\"" + chain.netVersion + "\")",
                        "listOf(" + chain.shortNames.map { "\"${it}\"" }.joinToString() + ")",
                        type(chain.type)
                    )
                    .build(),
            )
        }

        return builder
    }

    fun generateChainsFile(): FileSpec {
        val byIdFun = FunSpec.builder("byId")
            .addParameter("id", Int::class)
            .returns(ClassName("", "Chain"))
            .beginControlFlow("for (chain in values())")
            .beginControlFlow("if (chain.id == id)")
            .addStatement("return chain")
            .endControlFlow()
            .endControlFlow()
            .addStatement("return UNSPECIFIED")
            .build()

        val chainType = addEnumProperties(
            TypeSpec.enumBuilder("Chain")
                .addType(TypeSpec.companionObjectBuilder().addFunction(byIdFun).build())
                .primaryConstructor(
                    FunSpec.constructorBuilder()
                        .addParameter("id", Int::class)
                        .addParameter("chainCode", String::class)
                        .addParameter("chainName", String::class)
                        .addParameter("chainId", String::class)
                        .addParameter("netVersion", BigInteger::class)
                        .addParameter("shortNames", List::class.asClassName().parameterizedBy(String::class.asClassName()))
                        .addParameter("type", BlockchainType::class)
                        .build(),
                )
                .addProperty(
                    PropertySpec.builder("id", Int::class)
                        .initializer("id")
                        .build(),
                )
                .addProperty(
                    PropertySpec.builder("chainCode", String::class)
                        .initializer("chainCode")
                        .build(),
                )
                .addProperty(
                    PropertySpec.builder("netVersion", BigInteger::class)
                        .initializer("netVersion")
                        .build(),
                )
                .addProperty(
                    PropertySpec.builder("chainId", String::class)
                        .initializer("chainId")
                        .build(),
                )
                .addProperty(
                    PropertySpec.builder("chainName", String::class)
                        .initializer("chainName")
                        .build(),
                )
                .addProperty(
                    PropertySpec.builder("shortNames", List::class.asClassName().parameterizedBy(String::class.asClassName()))
                        .initializer("shortNames")
                        .build(),
                )
                .addProperty(
                    PropertySpec.builder("type", BlockchainType::class)
                        .initializer("type")
                        .build(),
                )
        ).build()
        return FileSpec.builder("io.emeraldpay.dshackle", "Chain")
            .addType(chainType)
            .build()
    }

    private fun type(type: String): String {
        return when(type) {
            "eth" -> "BlockchainType.ETHEREUM"
            "bitcoin" -> "BlockchainType.BITCOIN"
            "starknet" -> "BlockchainType.STARKNET"
            "polkadot" -> "BlockchainType.POLKADOT"
            "solana" -> "BlockchainType.SOLANA"
            "near" -> "BlockchainType.NEAR"
            "eth-beacon-chain" -> "BlockchainType.ETHEREUM_BEACON_CHAIN"
            else -> throw IllegalArgumentException("unknown blockchain type $type")
        }
    }
}

open class ChainsCodeGenTask : DefaultTask() {
    init {
        group = "custom"
        description = "Generate chains config"
    }

    @TaskAction
    fun chainscodegenClass() {
        val output = project.layout.buildDirectory.dir("generated/kotlin").get().asFile
        output.mkdirs()
        CodeGen.generateFromChains(output)
    }
}

tasks.register<ChainsCodeGenTask>("chainscodegen")
