package prog8tests.compiler

import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldStartWith
import prog8.code.INTERNED_STRINGS_MODULENAME
import prog8.code.core.ZeropageType
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.compiler.determineCompilationOptions
import prog8.compiler.parseMainModule
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText


class TestImportedModulesOrderAndOptions: FunSpec({

    val outputDir = tempdir().toPath()

    test("testImportedModuleOrderAndMainModuleCorrect") {
        val result = compileText(
            C64Target(), false, """
%import textio
%import floats

main {
    sub start() {
        ; nothing
    }
}
""", outputDir)!!
        result.compilerAst.toplevelModule.name shouldStartWith "on_the_fly_test"

        val moduleNames = result.compilerAst.modules.map { it.name }
        withClue("main module must be first") {
            moduleNames[0] shouldStartWith "on_the_fly_test"
        }
        withClue("module order in parse tree") {
            moduleNames.drop(1) shouldBe listOf(
                INTERNED_STRINGS_MODULENAME,
                "textio",
                "syslib",
                "conv",
                "shared_cbm_textio_functions",
                "floats",
                "shared_floats_functions",
                "prog8_math",
                "prog8_lib"
            )
        }
        result.compilerAst.toplevelModule.name shouldStartWith "on_the_fly_test"
    }

    test("testCompilationOptionsCorrectFromMain") {
        val result = compileText(
            C64Target(), false, """
%import textio
%import floats
%zeropage dontuse
%option no_sysinit

main {
    sub start() {
        ; nothing
    }
}
""", outputDir)!!
        result.compilerAst.toplevelModule.name shouldStartWith "on_the_fly_test"
        val options = determineCompilationOptions(result.compilerAst, C64Target())
        options.floats shouldBe true
        options.zeropage shouldBe ZeropageType.DONTUSE
        options.noSysInit shouldBe true
    }

    test("testModuleOrderAndCompilationOptionsCorrectWithJustImports") {
        val errors = ErrorReporterForTests()
        val sourceText = """
%import textio
%import floats
%option no_sysinit
%zeropage dontuse            

main {
    sub start() {
        ; nothing
    }
}
"""
        val filenameBase = "on_the_fly_test_${sourceText.hashCode().toUInt().toString(16)}"

        val filepath = outputDir.resolve("$filenameBase.p8")
        filepath.toFile().writeText(sourceText)
        val (program, options, importedfiles) = parseMainModule(filepath, errors, C64Target(), emptyList(), emptyList(), false)

        program.toplevelModule.name shouldBe filenameBase
        withClue("all imports other than the test source must have been internal resources library files") {
            importedfiles.size shouldBe 1
        }
        withClue("module order in parse tree") {
            program.modules.map { it.name } shouldBe
                listOf(
                    INTERNED_STRINGS_MODULENAME,
                    filenameBase,
                    "textio", "syslib", "conv", "shared_cbm_textio_functions", "floats", "shared_floats_functions", "prog8_math", "prog8_lib"
                )
        }
        options.floats shouldBe true
        options.noSysInit shouldBe true
        withClue("zeropage option must be correctly taken from main module, not from float module import logic") {
            options.zeropage shouldBe ZeropageType.DONTUSE
        }
    }

    test("merge option works on library modules") {
        val src="""
%zeropage basicsafe
%import textio

txt {
    %option merge
    sub println(uword string) {
        txt.print(string)
        txt.nl()
    }
}

main {
    sub start() {
        txt.lowercase()
        txt.println("Hello, world1")
        txt.println("Hello, world2")
        txt.println("Hello, world3")
    }
}"""
        compileText(VMTarget(), optimize = false, src, outputDir) shouldNotBe null
    }

    test("double merge is invalid") {
        val src="""
main {

    sub start() {
        block1.sub1()
        block1.sub2()
    }
}

block1 {
    %option merge

    sub sub1() {
        cx16.r1++
    }
}


block1 {
    %option merge

    sub sub2() {
        cx16.r2++
    }
}"""
        val errors=ErrorReporterForTests()
        compileText(VMTarget(), optimize = false, src, outputDir, errors=errors) shouldBe null
        errors.errors.size shouldBe 1
        errors.errors[0] shouldContain "all declarations of block 'block1' have %option merge"
    }

    test("merge works") {
        val src = """
%import textio

main {

sub start() {
    blah.test()
}
}

txt {
; merges this block into the txt block coming from the textio library
%option merge

sub schrijf(str arg) {
    print(arg)
}
}

blah {
; merges this block into the other 'blah' one
%option merge

sub test() {
    printit("test merge")
}
}

blah {
sub printit(str arg) {
    txt.schrijf(arg)
}
}"""
        compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
    }

    test("merge override existing subroutine") {
        val src="""
%import textio

main {

sub start() {
    txt.print("sdfdsf")
}
}

txt {
%option merge

sub print(str text) {
    cx16.r0++
    ; just some dummy implementation to replace existing print
}
}"""

        val result = compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false)
        result shouldNotBe null
    }

    test("merge doesn't override existing subroutine if signature differs") {
        val src="""
%import textio

main {

sub start() {
    txt.print("sdfdsf")
}
}

txt {
%option merge

sub print(str anotherparamname) {
    cx16.r0++
    ; just some dummy implementation to replace existing print
}
}"""
        val errors = ErrorReporterForTests()
        compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null
        errors.errors.size shouldBe 1
        errors.errors[0] shouldContain "name conflict"
    }

    test("merge of float stuff into sys and txt - import order 1") {
        val src="""
%import textio
%import floats

main {
sub start() {
    txt.print_b(sys.MIN_BYTE)
    txt.print_b(sys.MAX_BYTE)
    txt.print_ub(sys.MIN_UBYTE)
    txt.print_ub(sys.MAX_UBYTE)
    txt.print_w(sys.MIN_WORD)
    txt.print_w(sys.MAX_WORD)
    txt.print_uw(sys.MIN_UWORD)
    txt.print_uw(sys.MAX_UWORD)

    txt.print_f(floats.EPSILON)
    txt.print_f(sys.MIN_FLOAT)
    txt.print_f(sys.MAX_FLOAT)
    txt.print_f(floats.E)
    txt.print_ub(sys.SIZEOF_FLOAT)
}
}"""

        compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
        compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
    }

    test("merge of float stuff into sys and txt - import order 2") {
        val src="""
%import floats
%import textio

main {
sub start() {
    txt.print_b(sys.MIN_BYTE)
    txt.print_b(sys.MAX_BYTE)
    txt.print_ub(sys.MIN_UBYTE)
    txt.print_ub(sys.MAX_UBYTE)
    txt.print_w(sys.MIN_WORD)
    txt.print_w(sys.MAX_WORD)
    txt.print_uw(sys.MIN_UWORD)
    txt.print_uw(sys.MAX_UWORD)

    txt.print_f(floats.EPSILON)
    txt.print_f(sys.MIN_FLOAT)
    txt.print_f(sys.MAX_FLOAT)
    txt.print_f(floats.E)
    txt.print_ub(sys.SIZEOF_FLOAT)
}
}"""

        compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
        compileText(Cx16Target(), optimize=false, src, outputDir, writeAssembly=false) shouldNotBe null
    }
})
