/*
 * Copyright 2010-2021 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.jvm.ir

import org.jetbrains.kotlin.backend.common.ir.isReifiable
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.backend.common.originalBeforeInline
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.resolve.inline.INLINE_ONLY_ANNOTATION_FQ_NAME

fun IrValueParameter.isInlineParameter(): Boolean =
    kind == IrParameterKind.Regular && !isNoinline && (type.isFunction() || type.isSuspendFunction()) &&
            // Parameters with default values are always nullable, so check the expression too.
            // Note that the frontend has a diagnostic for nullable inline parameters, so actually
            // making this return `false` requires using `@Suppress`.
            (!type.isNullable() || defaultValue?.expression?.type?.isNullable() == false)

// Declarations in the scope of an externally visible inline function are implicitly part of the
// public ABI of a Kotlin module. This function returns the visibility of a containing inline function
// (determined *before* lowering), or null if the given declaration is not in the scope of an inline function.
//
// Currently, we mark all declarations in the scope of a public inline function as public, even if they are
// contained in a nested private inline function. This is an over approximation, since private declarations
// inside of a public inline function can still escape if they are used without being regenerated.
// See `plugins/jvm-abi-gen/testData/compile/inlineNoRegeneration` for an example.
val IrDeclaration.inlineScopeVisibility: DescriptorVisibility?
    get() {
        var owner: IrDeclaration? = original
        var result: DescriptorVisibility? = null
        while (owner != null) {
            if (owner is IrFunction && owner.isInline) {
                result = if (!DescriptorVisibilities.isPrivate(owner.visibility)) {
                    if (owner.parentClassOrNull?.visibility?.let(DescriptorVisibilities::isPrivate) == true)
                        DescriptorVisibilities.PRIVATE
                    else
                        return owner.visibility
                } else {
                    owner.visibility
                }
            }
            owner = (owner.parent as? IrDeclaration)?.original
        }
        return result
    }

// True for declarations which are in the scope of an externally visible inline function.
val IrDeclaration.isInPublicInlineScope: Boolean
    get() = inlineScopeVisibility?.let(DescriptorVisibilities::isPrivate) == false

// Map declarations to original declarations before lowering.
private val IrDeclaration.original: IrDeclaration
    get() = (this.attributeOwnerId as? IrDeclaration) ?: this

fun IrStatement.unwrapInlineLambda(): IrFunctionReference? = when (this) {
    is IrBlock -> statements.lastOrNull()?.unwrapInlineLambda()
    is IrFunctionReference -> takeIf { it.origin == IrStatementOrigin.INLINE_LAMBDA }
    else -> null
}

fun IrFunction.isInlineFunctionCall(context: JvmBackendContext): Boolean =
    (!context.config.isInlineDisabled || typeParameters.any { it.isReified }) && (isInline || isInlineArrayConstructor())

fun IrDeclaration.isInlineOnly(): Boolean =
    this is IrFunction && (
            (isInline && hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME)) ||
                    (this is IrSimpleFunction && correspondingPropertySymbol?.owner?.hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME) == true)
            )

fun IrDeclarationWithVisibility.isEffectivelyInlineOnly(): Boolean =
    this is IrFunction && (isReifiable() || isInlineOnly() || isPrivateInlineSuspend())

fun IrFunction.isPrivateInlineSuspend(): Boolean =
    isSuspend && isInline && visibility == DescriptorVisibilities.PRIVATE

private fun IrElement.getDeclarationBeforeInline(): IrDeclaration? {
    val original = this.originalBeforeInline ?: return null
    return original.extractRelatedDeclaration()
}

fun IrElement.getAttributeOwnerBeforeInline(): IrElement? {
    if (this.originalBeforeInline == null) return null
    return generateSequence(this) { it.originalBeforeInline }.last()
}

val IrDeclaration.fileParentBeforeInline: IrFile
    get() {
        val original = this.getDeclarationBeforeInline()
            ?: this.parentClassOrNull?.getDeclarationBeforeInline()
            ?: this
        return original.fileParent
    }

@OptIn(JvmIrInlineExperimental::class)
val IrInlinedFunctionBlock.inlineDeclaration: IrDeclaration
    get() = when (val element = inlinedElement) {
        is IrFunction -> element
        is IrFunctionExpression -> element.function
        is IrFunctionReference -> element.symbol.owner
        is IrPropertyReference -> element.symbol.owner
        else -> throw AssertionError("Not supported ir element for inlining ${element?.dump()}")
    }