/*
 * Use of this source code is governed by the MIT license that can be
 * found in the LICENSE file.
 */

package org.rust.lang.core.types.ty

import com.intellij.codeInsight.completion.CompletionUtil
import org.rust.lang.core.psi.RsConstParameter
import org.rust.lang.core.psi.RsLifetimeParameter
import org.rust.lang.core.psi.RsTypeAlias
import org.rust.lang.core.psi.RsTypeParameter
import org.rust.lang.core.psi.ext.*
import org.rust.lang.core.types.BoundElement
import org.rust.lang.core.types.Substitution
import org.rust.lang.core.types.consts.Const
import org.rust.lang.core.types.consts.CtConstParameter
import org.rust.lang.core.types.consts.CtUnknown
import org.rust.lang.core.types.infer.TypeFolder
import org.rust.lang.core.types.infer.TypeVisitor
import org.rust.lang.core.types.mergeFlags
import org.rust.lang.core.types.regions.ReEarlyBound
import org.rust.lang.core.types.regions.ReUnknown
import org.rust.lang.core.types.regions.Region

/**
 * Represents struct/enum/union.
 * "ADT" may be read as "Algebraic Data Type".
 * The name is inspired by rustc.
 */
@Suppress("DataClassPrivateConstructor")
data class TyAdt private constructor(
    val item: RsStructOrEnumItemElement,
    val typeArguments: List<Ty>,
    val regionArguments: List<Region>,
    val constArguments: List<Const>,
    override val aliasedBy: BoundElement<RsTypeAlias>?
) : Ty(mergeFlags(typeArguments) or mergeFlags(regionArguments) or mergeFlags(constArguments)) {

    // This method is rarely called (in comparison with folding), so we can implement it in a such inefficient way.
    override val typeParameterValues: Substitution
        get() {
            val typeSubst = item.typeParameters.withIndex().associate { (i, param) ->
                TyTypeParameter.named(param) to typeArguments.getOrElse(i) { TyUnknown }
            }
            val regionSubst = item.lifetimeParameters.withIndex().associate { (i, param) ->
                ReEarlyBound(param) to regionArguments.getOrElse(i) { ReUnknown }
            }
            val constSubst = item.constParameters.withIndex().associate { (i, param) ->
                CtConstParameter(param) to constArguments.getOrElse(i) { CtUnknown }
            }
            return Substitution(typeSubst, regionSubst, constSubst)
        }

    override fun superFoldWith(folder: TypeFolder): TyAdt =
        TyAdt(
            item,
            typeArguments.map { it.foldWith(folder) },
            regionArguments.map { it.foldWith(folder) },
            constArguments.map { it.foldWith(folder) },
            aliasedBy?.foldWith(folder)
        )

    override fun superVisitWith(visitor: TypeVisitor): Boolean =
        typeArguments.any { it.visitWith(visitor) } ||
            regionArguments.any { it.visitWith(visitor) } ||
            constArguments.any { it.visitWith(visitor) }

    override fun withAlias(aliasedBy: BoundElement<RsTypeAlias>): TyAdt =
        copy(aliasedBy = aliasedBy)

    override fun isEquivalentToInner(other: Ty): Boolean {
        if (this === other) return true
        if (other !is TyAdt) return false

        if (item != other.item) return false
        if (constArguments != other.constArguments) return false
        if (typeArguments.size != other.typeArguments.size) return false
        for (i in typeArguments.indices) {
            if (!typeArguments[i].isEquivalentTo(other.typeArguments[i])) return false
        }

        return true
    }

    companion object {
        fun valueOf(struct: RsStructOrEnumItemElement): TyAdt {
            val genericParameters = struct.getGenericParameters()

            val typeParameters = mutableListOf<TyTypeParameter>()
            val lifetimeParameters = mutableListOf<ReEarlyBound>()
            val constParameters = mutableListOf<CtConstParameter>()

            for (generic in genericParameters) {
                when (generic) {
                    is RsTypeParameter -> typeParameters += TyTypeParameter.named(generic)
                    is RsLifetimeParameter -> lifetimeParameters += ReEarlyBound(generic)
                    is RsConstParameter -> constParameters += CtConstParameter(generic)
                }
            }

            return TyAdt(
                CompletionUtil.getOriginalOrSelf(struct),
                typeParameters,
                lifetimeParameters,
                constParameters,
                null
            )
        }
    }
}
