/*
 * 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.ir.interpreter

import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl
import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy
import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.properties
import org.jetbrains.kotlin.ir.util.toIrConst
import org.jetbrains.kotlin.platform.isJs

class IrInterpreterEnvironment(
    val irBuiltIns: IrBuiltIns,
    val configuration: IrInterpreterConfiguration = IrInterpreterConfiguration(),
) {
    internal val callStack: CallStack = CallStack()
    internal val irExceptions = mutableListOf<IrClass>()
    internal var mapOfEnums = mutableMapOf<IrSymbol, Complex>()
    internal var mapOfObjects = mutableMapOf<IrSymbol, Complex>()
    internal var javaClassToIrClass = mutableMapOf<Class<*>, IrClass>()
    private var functionCache = mutableMapOf<CacheFunctionSignature, IrFunctionSymbol>()

    internal val kTypeParameterClass by lazy { irBuiltIns.kClassClass.getIrClassOfReflectionFromList("typeParameters")!! }
    internal val kParameterClass by lazy { irBuiltIns.kFunctionClass.getIrClassOfReflectionFromList("parameters")!! }
    internal val kTypeProjectionClass by lazy { kTypeClass.getIrClassOfReflectionFromList("arguments")!! }
    internal val kTypeClass: IrClassSymbol by lazy { irBuiltIns.kTypeClass }

    init {
        mapOfObjects[irBuiltIns.unitClass] = Common(irBuiltIns.unitClass.owner)
    }

    private data class CacheFunctionSignature(
        val symbol: IrFunctionSymbol,

        // must create different invoke function for function expression for which different parameters are bound
        val boundParameters: Set<IrValueParameter>,

        // must create different default functions for constructor call and delegating call;
        // their symbols are the same but calls are different, so default function must return different calls
        val fromDelegatingCall: Boolean
    )

    private constructor(environment: IrInterpreterEnvironment) : this(environment.irBuiltIns, configuration = environment.configuration) {
        irExceptions.addAll(environment.irExceptions)
        mapOfEnums = environment.mapOfEnums
        mapOfObjects = environment.mapOfObjects
    }

    fun copyWithNewCallStack(): IrInterpreterEnvironment {
        return IrInterpreterEnvironment(this)
    }

    internal fun getCachedFunction(
        symbol: IrFunctionSymbol,
        boundParameters: Set<IrValueParameter> = emptySet(),
        fromDelegatingCall: Boolean = false
    ): IrFunctionSymbol? {
        return functionCache[CacheFunctionSignature(symbol, boundParameters, fromDelegatingCall)]
    }

    internal fun setCachedFunction(
        symbol: IrFunctionSymbol,
        boundParameters: Set<IrValueParameter> = emptySet(),
        fromDelegatingCall: Boolean = false,
        newFunction: IrFunctionSymbol
    ): IrFunctionSymbol {
        functionCache[CacheFunctionSignature(symbol, boundParameters, fromDelegatingCall)] = newFunction
        return newFunction
    }

    /**
     * Convert object from outer world to state
     */
    internal fun convertToState(value: Any?, irType: IrType): State {
        return when (value) {
            is Proxy -> value.state
            is State -> value
            is Boolean, is Char, is Byte, is Short, is Int, is Long, is String, is Float, is Double, is Array<*>, is ByteArray,
            is CharArray, is ShortArray, is IntArray, is LongArray, is FloatArray, is DoubleArray, is BooleanArray -> Primitive(value, irType)
            null -> Primitive.nullStateOfType(irType)
            else -> irType.classOrNull?.owner?.let { Wrapper(value, it, this) }
                ?: Wrapper(value, this.javaClassToIrClass[value::class.java]!!, this)
        }
    }

    internal fun stateToIrExpression(state: State, original: IrExpression): IrExpression {
        val start = original.startOffset
        val end = original.endOffset
        val type = original.type.makeNotNull()
        return when (state) {
            is Primitive -> when {
                configuration.platform.isJs() && state.value is Float -> IrConstImpl.float(start, end, type, state.value)
                configuration.platform.isJs() && state.value is Double -> IrConstImpl.double(start, end, type, state.value)
                state.value == null || type.isPrimitiveType() || type.isString() -> state.value.toIrConst(type, start, end)
                else -> original // TODO support for arrays
            }
            is ExceptionState -> {
                val message = if (configuration.printOnlyExceptionMessage) state.getShortDescription() else "\n" + state.getFullDescription()
                IrErrorExpressionImpl(original.startOffset, original.endOffset, original.type, message)
            }
            is Complex -> {
                val stateType = state.irClass.defaultType
                when {
                    stateType.isUnsignedType() -> (state.fields.values.single() as Primitive).value.toIrConst(type, start, end)
                    else -> original
                }
            }
            else -> original // TODO support
        }
    }

    private fun IrClassSymbol.getIrClassOfReflectionFromList(name: String): IrClassSymbol? {
        val property = this.owner.properties.singleOrNull { it.name.asString() == name }
        val list = property?.getter?.returnType as? IrSimpleType
        return list?.arguments?.single()?.typeOrNull?.classOrNull
    }
}
