package prog8tests

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.expressions.ArrayIndexedExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.PrefixExpression
import prog8.ast.statements.*
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SourceCode
import prog8.code.core.ZeropageWish
import prog8.code.target.C64Target
import prog8.compiler.printProgram
import prog8tests.helpers.DummyFunctions
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.compileText


class TestMemory: FunSpec({

    val c64target = C64Target()

    fun wrapWithProgram(statements: List<Statement>): Program {
        val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
        val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements.toMutableList(), Position.DUMMY)
        val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
        program.addModule(module)
        return program
    }

    test("assignment target not in mapped IO space C64") {

        var memexpr = NumericLiteral.optimalInteger(0x0002, Position.DUMMY)
        var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        var assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe false

        memexpr = NumericLiteral.optimalInteger(0x1000, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe false

        memexpr = NumericLiteral.optimalInteger(0x9fff, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe false

        memexpr = NumericLiteral.optimalInteger(0xa000, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe false

        memexpr = NumericLiteral.optimalInteger(0xc000, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe false

        memexpr = NumericLiteral.optimalInteger(0xcfff, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe false

        memexpr = NumericLiteral.optimalInteger(0xeeee, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe false

        memexpr = NumericLiteral.optimalInteger(0xffff, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe false
    }

    test("assign target in mapped IO space C64") {

        var memexpr = NumericLiteral.optimalInteger(0x0000, Position.DUMMY)
        var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        var assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe true

        memexpr = NumericLiteral.optimalInteger(0x0001, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe true

        memexpr = NumericLiteral.optimalInteger(0xd000, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe true

        memexpr = NumericLiteral.optimalInteger(0xdfff, Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe true
    }

    fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
        val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
        val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
        val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(decl, assignment))
        return target
    }

    test("identifier mapped to IO memory on C64") {
        var target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.VAR)
        target.isIOAddress(c64target.machine) shouldBe false
        target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.VAR)
        target.isIOAddress(c64target.machine) shouldBe false
        target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.CONST)
        target.isIOAddress(c64target.machine) shouldBe false
        target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.CONST)
        target.isIOAddress(c64target.machine) shouldBe true
        target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.MEMORY)
        target.isIOAddress(c64target.machine) shouldBe false
        target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.MEMORY)
        target.isIOAddress(c64target.machine) shouldBe true
    }

    test("memory expression mapped to IO memory on C64") {
        var memexpr = PrefixExpression("+", NumericLiteral.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
        var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        var assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        target.isIOAddress(c64target.machine) shouldBe false

        memexpr = PrefixExpression("+", NumericLiteral.optimalInteger(0xd020, Position.DUMMY), Position.DUMMY)
        target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
        assign = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        wrapWithProgram(listOf(assign))
        printProgram(target.definingModule.program)
        target.isIOAddress(c64target.machine) shouldBe true
    }

    test("regular variable not in mapped IO ram on C64") {
        val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, null, Position.DUMMY)
        val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
        val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
        val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
        Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
            .addModule(module)
        target.isIOAddress(c64target.machine) shouldBe false
    }

    test("memory mapped variable not in mapped IO ram on C64") {
        val address = 0x1000u
        val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
        val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
        val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
        val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
        Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
            .addModule(module)
        target.isIOAddress(c64target.machine) shouldBe false
    }

    test("memory mapped variable in mapped IO ram on C64") {
        val address = 0xd020u
        val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
        val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
        val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
        val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
        Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
            .addModule(module)
        target.isIOAddress(c64target.machine) shouldBe true
    }

    test("array not in mapped IO ram") {
        val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, null, Position.DUMMY)
        val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
        val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
        val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
        val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
        Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
            .addModule(module)
        target.isIOAddress(c64target.machine) shouldBe false
    }

    test("memory mapped array not in mapped IO ram") {
        val address = 0x1000u
        val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
        val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
        val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
        val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
        val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
        Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
            .addModule(module)
        target.isIOAddress(c64target.machine) shouldBe false
    }

    test("memory mapped array in mapped IO ram") {
        val address = 0xd800u
        val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, null, Position.DUMMY)
        val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
        val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
        val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
        val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
        val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
        Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
            .addModule(module)
        target.isIOAddress(c64target.machine) shouldBe true
    }


    test("memory() with spaces in name works") {
        compileText(
            C64Target(), false, """
            main {
                sub start() {
                    uword @shared mem = memory("a b c", 100, $100)
                }
            }
        """, writeAssembly = true) shouldNotBe null
    }

    test("memory() with invalid argument") {
        compileText(
            C64Target(), false, """
            main {
                sub start() {
                    uword @shared mem1 = memory("abc", 100, -2)
                }
            }
        """, writeAssembly = true) shouldBe null
    }
})
