// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.

package org.jetbrains.kotlin.nj2k.externalCodeProcessing

import com.intellij.psi.*
import org.jetbrains.kotlin.j2k.AccessorKind
import org.jetbrains.kotlin.j2k.usageProcessing.AccessorToPropertyProcessing
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.utils.addToStdlib.cast

internal sealed class JKExternalConversion : Comparable<JKExternalConversion> {
    abstract val usage: PsiElement

    abstract fun apply()

    private val depth by lazy(LazyThreadSafetyMode.NONE) { usage.parentsWithSelf.takeWhile { it !is PsiFile }.count() }
    private val offset by lazy(LazyThreadSafetyMode.NONE) { usage.textRange.startOffset }

    override fun compareTo(other: JKExternalConversion): Int {
        val depth1 = depth
        val depth2 = other.depth
        if (depth1 != depth2) { // put deeper elements first to not invalidate them when processing ancestors
            return -depth1.compareTo(depth2)
        }

        // process elements with the same deepness from right to left
        // so that right-side of assignments is not invalidated by processing of the left one
        return -offset.compareTo(other.offset)
    }
}

internal class AccessorToPropertyKotlinExternalConversion(
    private val name: String,
    private val accessorKind: AccessorKind,
    override val usage: PsiElement
) : JKExternalConversion() {
    override fun apply() {
        AccessorToPropertyProcessing.processUsage(usage, name, accessorKind)
    }
}

internal class AccessorToPropertyJavaExternalConversion(
    private val name: String,
    private val accessorKind: AccessorKind,
    override val usage: PsiElement
) : JKExternalConversion() {
    override fun apply() {
        if (usage !is PsiReferenceExpression) return
        val methodCall = usage.parent as? PsiMethodCallExpression ?: return

        val factory = PsiElementFactory.getInstance(usage.project)
        val propertyAccess = factory.createReferenceExpression(usage.qualifierExpression)
        val newExpression = when (accessorKind) {
            AccessorKind.GETTER -> propertyAccess
            AccessorKind.SETTER -> {
                val value = methodCall.argumentList.expressions.singleOrNull() ?: return
                factory.createAssignment(propertyAccess, value)
            }
        }
        methodCall.replace(newExpression)
    }

    private fun PsiElementFactory.createReferenceExpression(qualifier: PsiExpression?): PsiReferenceExpression =
        createExpressionFromText(qualifier?.let { "qualifier." }.orEmpty() + name, usage).cast<PsiReferenceExpression>().apply {
            qualifierExpression?.replace(qualifier ?: return@apply)
        }

    private fun PsiElementFactory.createAssignment(target: PsiExpression, value: PsiExpression): PsiAssignmentExpression =
        createExpressionFromText("x = 1", usage).cast<PsiAssignmentExpression>().apply {
            lExpression.replace(target)
            rExpression!!.replace(value)
        }
}

internal class PropertyRenamedKotlinExternalUsageConversion(
    private val newName: String,
    override val usage: KtElement
) : JKExternalConversion() {
    override fun apply() {
        if (usage !is KtSimpleNameExpression) return
        val factory = KtPsiFactory(usage)
        usage.getReferencedNameElement().replace(factory.createExpression(newName))
    }
}

internal class PropertyRenamedJavaExternalUsageConversion(
    private val newName: String,
    override val usage: PsiElement
) : JKExternalConversion() {
    override fun apply() {
        if (usage !is PsiReferenceExpression) return
        val factory = PsiElementFactory.getInstance(usage.project)
        usage.referenceNameElement?.replace(factory.createExpressionFromText(newName, usage))
    }
}
