/*
 * Copyright 2010-2019 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.scripting.compiler.plugin.repl

import org.jetbrains.kotlin.backend.jvm.serialization.JvmIdSignatureDescriptor
import org.jetbrains.kotlin.cli.common.repl.BasicReplStageHistory
import org.jetbrains.kotlin.cli.common.repl.ILineId
import org.jetbrains.kotlin.cli.common.repl.IReplStageState
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.idea.MainFunctionDetector
import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmDescriptorMangler
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.messages.DiagnosticMessageHolder
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.dependencies.ScriptDependencies

class ReplCompilerStageHistory(private val state: GenericReplCompilerState) : BasicReplStageHistory<ScriptDescriptor>(state.lock) {

    override fun reset(): Iterable<ILineId> {
        lock.write {
            val removedCompiledLines = super.reset()
            val removedAnalyzedLines = state.analyzerEngine.reset()

            checkConsistent(removedCompiledLines, removedAnalyzedLines)
            return removedCompiledLines
        }
    }

    override fun resetTo(id: ILineId): Iterable<ILineId> {
        lock.write {
            val removedCompiledLines = super.resetTo(id)
            val removedAnalyzedLines = state.analyzerEngine.resetToLine(id)

            checkConsistent(removedCompiledLines, removedAnalyzedLines)
            return removedCompiledLines
        }
    }


    private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<SourceCodeByReplLine>) {
        removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
            if (removedCompiledLine.no != removedAnalyzedLine.no) {
                throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
            }
        }
    }
}

abstract class GenericReplCheckerState : IReplStageState<ScriptDescriptor> {

    // "line" - is the unit of evaluation here, could in fact consists of several character lines
    class LineState(
        val codeLine: ReplCodeLine,
        val psiFile: KtFile,
        val errorHolder: DiagnosticMessageHolder
    )

    var lastLineState: LineState? = null // for transferring state to the compiler in most typical case
}

class GenericReplCompilerState(environment: KotlinCoreEnvironment, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) :
    IReplStageState<ScriptDescriptor>, GenericReplCheckerState() {

    override val history = ReplCompilerStageHistory(this)

    override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()

    val analyzerEngine = ReplCodeAnalyzerBase(environment)

    var lastDependencies: ScriptDependencies? = null

    private val manglerAndSymbolTable by lazy {
        val mangler = JvmDescriptorMangler(
            MainFunctionDetector(analyzerEngine.trace.bindingContext, environment.configuration.languageVersionSettings)
        )
        val symbolTable = SymbolTable(JvmIdSignatureDescriptor(mangler), IrFactoryImpl)
        mangler to symbolTable
    }

    val mangler: JvmDescriptorMangler get() = manglerAndSymbolTable.first
    val symbolTable: SymbolTable get() = manglerAndSymbolTable.second
}
