/*
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.backend.common.serialization

import org.jetbrains.kotlin.backend.common.serialization.encodings.BinarySymbolData
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.util.IdSignature
import org.jetbrains.kotlin.library.IrLibrary
import org.jetbrains.kotlin.library.KotlinAbiVersion
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.KotlinLibraryProperResolverWithAttributes
import org.jetbrains.kotlin.utils.DFS

fun IrSymbol.kind(): BinarySymbolData.SymbolKind {
    return when (this) {
        is IrClassSymbol -> BinarySymbolData.SymbolKind.CLASS_SYMBOL
        is IrConstructorSymbol -> BinarySymbolData.SymbolKind.CONSTRUCTOR_SYMBOL
        is IrSimpleFunctionSymbol -> BinarySymbolData.SymbolKind.FUNCTION_SYMBOL
        is IrPropertySymbol -> BinarySymbolData.SymbolKind.PROPERTY_SYMBOL
        is IrFieldSymbol -> BinarySymbolData.SymbolKind.FIELD_SYMBOL
        is IrEnumEntrySymbol -> BinarySymbolData.SymbolKind.ENUM_ENTRY_SYMBOL
        is IrTypeAliasSymbol -> BinarySymbolData.SymbolKind.TYPEALIAS_SYMBOL
        is IrTypeParameterSymbol -> BinarySymbolData.SymbolKind.TYPE_PARAMETER_SYMBOL
        else -> error("Unexpected symbol kind $this")
    }
}

// TODO: drop CompatibilityMode, KT-72100
class CompatibilityMode(val abiVersion: KotlinAbiVersion) {

    init {
        require(abiVersion.isCompatible()) {
            "Incompatible KLIB should have been discarded in ${KotlinLibraryProperResolverWithAttributes<Nothing>::libraryMatch.name}"
        }
    }

    /** See comments for [LAST_WITH_LEGACY_SIGNATURES_FOR_PRIVATE_AND_LOCAL_DECLARATIONS]. */
    val legacySignaturesForPrivateAndLocalDeclarations: Boolean
        get() = abiVersion.isAtMost(LAST_WITH_LEGACY_SIGNATURES_FOR_PRIVATE_AND_LOCAL_DECLARATIONS)

    companion object {
        val CURRENT = CompatibilityMode(KotlinAbiVersion.CURRENT)

        /**
         * KLIBs with ABI version <= 1.5.0 had different rules for computing signatures for private and local declarations.
         * See also [org.jetbrains.kotlin.backend.common.serialization.mangle.ir.IrExportCheckerVisitor.CompatibleChecker].
         */
        val LAST_WITH_LEGACY_SIGNATURES_FOR_PRIVATE_AND_LOCAL_DECLARATIONS = KotlinAbiVersion(1, 5, 0)
    }
}

enum class IrModuleDeserializerKind {
    CURRENT, DESERIALIZED, SYNTHETIC
}

abstract class IrModuleDeserializer(private val _moduleDescriptor: ModuleDescriptor?, val libraryAbiVersion: KotlinAbiVersion) {
    abstract operator fun contains(idSig: IdSignature): Boolean
    abstract fun tryDeserializeIrSymbol(idSig: IdSignature, symbolKind: BinarySymbolData.SymbolKind): IrSymbol?
    abstract fun deserializedSymbolNotFound(idSig: IdSignature): Nothing

    val moduleDescriptor: ModuleDescriptor get() = _moduleDescriptor ?: error("No ModuleDescriptor provided")

    open fun referenceSimpleFunctionByLocalSignature(file: IrFile, idSignature: IdSignature): IrSimpleFunctionSymbol =
        error("Unsupported operation")

    open fun referencePropertyByLocalSignature(file: IrFile, idSignature: IdSignature): IrPropertySymbol =
        error("Unsupported operation")

    open fun declareIrSymbol(symbol: IrSymbol) {
        val signature = symbol.signature
        require(signature != null) { "Symbol is not public API: ${symbol.descriptor}" }
        assert(symbol.hasDescriptor)
        deserializeIrSymbolOrFail(signature, symbol.kind())
    }

    open val klib: IrLibrary get() = error("Unsupported operation")

    open fun init() = init(this)

    open fun postProcess() {}

    open fun init(delegate: IrModuleDeserializer) {}

    /**
     * Schedule deserialization of the top-level declaration with the given signature in the given file.
     */
    open fun addModuleReachableTopLevel(topLevelDeclarationSignature: IdSignature) {
        error("Unsupported Operation (sig: $topLevelDeclarationSignature")
    }

    /**
     * Run deserialization of top-level declarations previously scheduled for deserialization in the current module.
     */
    open fun deserializeReachableDeclarations() { error("Unsupported Operation") }

    abstract val moduleFragment: IrModuleFragment

    abstract val moduleDependencies: Collection<IrModuleDeserializer>

    open val strategyResolver: (String) -> DeserializationStrategy = { DeserializationStrategy.ONLY_DECLARATION_HEADERS }

    abstract val kind: IrModuleDeserializerKind

    open fun fileDeserializers(): Collection<IrFileDeserializer> = error("Unsupported")

    val compatibilityMode: CompatibilityMode get() = CompatibilityMode(libraryAbiVersion)

    open fun signatureDeserializerForFile(fileName: String): IdSignatureDeserializer = error("Unsupported")
}

fun IrModuleDeserializer.deserializeIrSymbolOrFail(idSig: IdSignature, symbolKind: BinarySymbolData.SymbolKind): IrSymbol =
    tryDeserializeIrSymbol(idSig, symbolKind) ?: deserializedSymbolNotFound(idSig)

// Used to resolve built in symbols like `kotlin.ir.internal.*` or `kotlin.FunctionN`
class IrModuleDeserializerWithBuiltIns(
    private val builtIns: IrBuiltIns,
    private val delegate: IrModuleDeserializer
) : IrModuleDeserializer(delegate.moduleDescriptor, delegate.libraryAbiVersion) {

    init {
        // TODO: figure out how it should work for K/N
//        assert(builtIns.builtIns.builtInsModule === delegate.moduleDescriptor)
    }

    private val irBuiltInsMap = builtIns.knownBuiltins.associate {
        val symbol = (it as IrSymbolOwner).symbol
        symbol.signature to symbol
    }

    override operator fun contains(idSig: IdSignature): Boolean {
        val topLevel = idSig.topLevelSignature()
        if (topLevel in irBuiltInsMap) return true

        return checkIsFunctionInterface(topLevel) || idSig in delegate
    }

    override fun referenceSimpleFunctionByLocalSignature(file: IrFile, idSignature: IdSignature) : IrSimpleFunctionSymbol =
        delegate.referenceSimpleFunctionByLocalSignature(file, idSignature)

    override fun referencePropertyByLocalSignature(file: IrFile, idSignature: IdSignature): IrPropertySymbol =
        delegate.referencePropertyByLocalSignature(file, idSignature)

    override fun deserializeReachableDeclarations() {
        delegate.deserializeReachableDeclarations()
    }

    private fun computeFunctionClass(className: String): IrClass {
        val isK = className[0] == 'K'
        val isSuspend = (if (isK) className[1] else className[0]) == 'S'
        val arity = className.run { substring(indexOfFirst { it.isDigit() }).toInt(10) }
        return builtIns.run {
            when {
                isK && isSuspend -> kSuspendFunctionN(arity)
                isK -> kFunctionN(arity)
                isSuspend -> suspendFunctionN(arity)
                else -> functionN(arity)
            }
        }
    }

    private fun resolveFunctionalInterface(idSig: IdSignature, symbolKind: BinarySymbolData.SymbolKind): IrSymbol {
        if (symbolKind == BinarySymbolData.SymbolKind.TYPE_PARAMETER_SYMBOL) {
            val composite = idSig as IdSignature.CompositeSignature
            val classSignature = idSig.container
            val classSymbol = resolveFunctionalInterface(classSignature, BinarySymbolData.SymbolKind.CLASS_SYMBOL) as IrClassSymbol
            val typeParameterSig = composite.inner as IdSignature.LocalSignature
            val typeParameterIndex = typeParameterSig.index()
            val typeParameter = classSymbol.owner.typeParameters[typeParameterIndex]
            return typeParameter.symbol
        }
        val publicSig = idSig.asPublic() ?: error("$idSig has to be public")

        val fqnParts = publicSig.nameSegments
        val className = fqnParts.firstOrNull() ?: error("Expected class name for $idSig")

        val functionClass = computeFunctionClass(className)

        return when (fqnParts.size) {
            1 -> functionClass.symbol.also { assert(symbolKind == BinarySymbolData.SymbolKind.CLASS_SYMBOL) }
            2 -> {
                val memberName = fqnParts[1]
                functionClass.declarations.single { it is IrDeclarationWithName && it.name.asString() == memberName }.let {
                    (it as IrSymbolOwner).symbol
                }
            }
            3 -> {
                assert(idSig is IdSignature.AccessorSignature)
                assert(symbolKind == BinarySymbolData.SymbolKind.FUNCTION_SYMBOL)
                val propertyName = fqnParts[1]
                val accessorName = fqnParts[2]
                functionClass.declarations.filterIsInstance<IrProperty>().single { it.name.asString() == propertyName }.let { p ->
                    p.getter?.let { g -> if (g.name.asString() == accessorName) return g.symbol }
                    p.setter?.let { s -> if (s.name.asString() == accessorName) return s.symbol }
                    error("No accessor found for signature $idSig")
                }
            }
            else -> error("No member found for signature $idSig")
        }
    }

    override fun tryDeserializeIrSymbol(idSig: IdSignature, symbolKind: BinarySymbolData.SymbolKind): IrSymbol? {
        irBuiltInsMap[idSig]?.let { return it }

        val topLevel = idSig.topLevelSignature()
        if (checkIsFunctionInterface(topLevel)) return resolveFunctionalInterface(idSig, symbolKind)

        return delegate.tryDeserializeIrSymbol(idSig, symbolKind)
    }

    override fun deserializedSymbolNotFound(idSig: IdSignature): Nothing = delegate.deserializedSymbolNotFound(idSig)

    override fun declareIrSymbol(symbol: IrSymbol) {
        val signature = symbol.signature
        if (signature != null && checkIsFunctionInterface(signature))
            resolveFunctionalInterface(signature, symbol.kind())
        else delegate.declareIrSymbol(symbol)
    }

    override fun init() {
        delegate.init(this)
    }

    override val klib: IrLibrary
        get() = delegate.klib

    override val strategyResolver: (String) -> DeserializationStrategy
        get() = delegate.strategyResolver

    override fun addModuleReachableTopLevel(topLevelDeclarationSignature: IdSignature) {
        delegate.addModuleReachableTopLevel(topLevelDeclarationSignature)
    }

    override val moduleFragment: IrModuleFragment get() = delegate.moduleFragment
    override val moduleDependencies: Collection<IrModuleDeserializer> get() = delegate.moduleDependencies
    override val kind get() = delegate.kind

    override fun fileDeserializers(): Collection<IrFileDeserializer> {
        return delegate.fileDeserializers()
    }

    override fun postProcess() {
        delegate.postProcess()
    }

    override fun signatureDeserializerForFile(fileName: String): IdSignatureDeserializer {
        return delegate.signatureDeserializerForFile(fileName)
    }
}

open class CurrentModuleDeserializer(
    override val moduleFragment: IrModuleFragment,
    override val moduleDependencies: Collection<IrModuleDeserializer>
) : IrModuleDeserializer(moduleFragment.descriptor, KotlinAbiVersion.CURRENT) {
    override fun contains(idSig: IdSignature): Boolean = false // TODO:

    override fun tryDeserializeIrSymbol(idSig: IdSignature, symbolKind: BinarySymbolData.SymbolKind): Nothing =
        error("Unreachable execution: there could not be back-links (sig: $idSig)")

    override fun deserializedSymbolNotFound(idSig: IdSignature): Nothing =
        error("Unreachable execution: there could not be back-links (sig: $idSig)")

    override fun declareIrSymbol(symbol: IrSymbol) = Unit

    override val kind get() = IrModuleDeserializerKind.CURRENT
}

fun sortDependencies(moduleDependencies: Map<KotlinLibrary, List<KotlinLibrary>>): Collection<KotlinLibrary> {
    return DFS.topologicalOrder(moduleDependencies.keys) { m ->
        moduleDependencies.getValue(m)
    }.reversed()
}
