/*
 * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Benchmarks
import net.sergeych.lyng.BytecodeBodyProvider
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.ForInStatement
import net.sergeych.lyng.Script
import net.sergeych.lyng.Statement
import net.sergeych.lyng.bytecode.CmdDisassembler
import net.sergeych.lyng.bytecode.CmdFunction
import net.sergeych.lyng.bytecode.BytecodeStatement
import net.sergeych.lyng.obj.ObjInt
import kotlin.time.TimeSource
import kotlin.test.Test
import kotlin.test.assertEquals

class NestedRangeBenchmarkTest {
    @Test
    fun benchmarkHappyNumbersNestedRanges() = runTest {
        if (!Benchmarks.enabled) return@runTest
        val bodyScript = """
            var count = 0
            for( n1 in 0..9 )
                for( n2 in 0..9 )
                    for( n3 in 0..9 )
                        for( n4 in 0..9 )
                            for( n5 in 0..9 )
                                for( n6 in 0..9 )
                                    if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
            count
        """.trimIndent()

        val compiled = Compiler.compile(bodyScript)
        dumpNestedLoopBytecode(compiled.debugStatements())

        val script = """
            fun naiveCountHappyNumbers() {
                $bodyScript
            }
        """.trimIndent()

        val scope = Script.newScope()
        scope.eval(script)
        val fnDisasm = scope.disassembleSymbol("naiveCountHappyNumbers")
        println("[DEBUG_LOG] [BENCH] nested-happy function naiveCountHappyNumbers cmd:\n$fnDisasm")
        dumpFunctionSlots(scope, "naiveCountHappyNumbers")
        runMode(scope)
    }

    private suspend fun runMode(scope: net.sergeych.lyng.Scope) {
        val start = TimeSource.Monotonic.markNow()
        val result = scope.eval("naiveCountHappyNumbers()") as ObjInt
        val elapsedMs = start.elapsedNow().inWholeMilliseconds
        println("[DEBUG_LOG] [BENCH] nested-happy elapsed=${elapsedMs} ms")
        assertEquals(55252L, result.value)
    }

    private fun dumpNestedLoopBytecode(statements: List<Statement>) {
        var current: Statement? = statements.firstOrNull { stmt ->
            stmt is BytecodeStatement && stmt.original is ForInStatement
        }
        var depth = 1
        while (current is BytecodeStatement && current.original is ForInStatement) {
            val original = current.original
            println(
                "[DEBUG_LOG] [BENCH] nested-happy loop depth=$depth " +
                    "constRange=${original.constRange} canBreak=${original.canBreak} " +
                    "loopSlotPlan=${original.loopSlotPlan}"
            )
            val fn = current.bytecodeFunction()
            val slots = fn.scopeSlotNames.mapIndexed { idx, name ->
                val slotName = name ?: "s$idx"
                "$slotName@${fn.scopeSlotIndices[idx]}"
            }
            println("[DEBUG_LOG] [BENCH] nested-happy slots depth=$depth: ${slots.joinToString(", ")}")
            val disasm = CmdDisassembler.disassemble(fn)
            println("[DEBUG_LOG] [BENCH] nested-happy cmd depth=$depth:\n$disasm")
            current = original.body
            depth += 1
        }
        if (depth == 1) {
            println("[DEBUG_LOG] [BENCH] nested-happy cmd: <not found>")
        }
    }

    private fun dumpFunctionSlots(scope: net.sergeych.lyng.Scope, name: String) {
        val record = scope[name]?.value as? Statement ?: return
        val fn = bytecodeFromStatement(record) ?: return
        val scopeNames = fn.scopeSlotNames.mapIndexedNotNull { idx, slotName ->
            slotName?.let { "$it@${fn.scopeSlotIndices[idx]}" }
        }
        val localNames = fn.localSlotNames.mapIndexedNotNull { idx, slotName ->
            slotName?.let { "$it@$idx" }
        }
        val captures = fn.localSlotNames.mapIndexedNotNull { idx, slotName ->
            if (slotName != null && fn.localSlotCaptures.getOrNull(idx) == true) "$slotName@$idx" else null
        }
        println(
            "[DEBUG_LOG] [BENCH] nested-happy function $name slots: " +
                "scopeCount=${fn.scopeSlotCount} " +
                "scope=[${scopeNames.joinToString(", ")}] " +
                "locals=[${localNames.joinToString(", ")}] " +
                "captures=[${captures.joinToString(", ")}]"
        )
    }

    private fun bytecodeFromStatement(stmt: Statement): CmdFunction? {
        return (stmt as? BytecodeStatement)?.bytecodeFunction()
            ?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
    }

}
