package prog8.codegen.cpu6502

import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.TargetStorageKind
import java.time.LocalDate
import java.time.LocalDateTime
import kotlin.math.absoluteValue

/**
 * Generates the main parts of the program:
 *  - entry/exit code
 *  - initialization routines
 *  - blocks
 *  - subroutines
 *  - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
 */
internal class ProgramAndVarsGen(
    val program: PtProgram,
    val options: CompilationOptions,
    val errors: IErrorReporter,
    private val symboltable: SymbolTable,
    private val functioncallAsmGen: FunctionCallAsmGen,
    private val asmgen: AsmGen6502Internal,
    private val allocator: VariableAllocator,
    private val zeropage: Zeropage
) {
    private val compTarget = options.compTarget
    private val blockVariableInitializers = program.allBlocks().associateWith { it.children.filterIsInstance<PtAssignment>() }

    internal fun generate() {
        header()
        val allBlocks = program.allBlocks()
        if(allBlocks.first().name != "main")
            throw AssemblyError("first block should be 'main'")

        if(errors.noErrors())  {
            program.allBlocks().forEach { block2asm(it) }

            // the global list of all floating point constants for the whole program
            asmgen.out("; global float constants")
            for (flt in allocator.globalFloatConsts) {
                val floatFill = compTarget.machine.getFloatAsmBytes(flt.key)
                val floatvalue = flt.key
                asmgen.out("${flt.value}\t.byte  $floatFill  ; float $floatvalue")
            }

            memorySlabs()
            footer()
        }
    }

    private fun header() {
        val ourName = this.javaClass.name
        val cpu = when(compTarget.machine.cpu) {
            CpuType.CPU6502 -> "6502"
            CpuType.CPU65c02 -> "w65c02"
            else -> "unsupported"
        }

        asmgen.out("; $cpu assembly code for '${program.name}'")
        asmgen.out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
        asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
        asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
        asmgen.out("")
        asmgen.out(".cpu  '$cpu'\n.enc  'none'")

        // the global prog8 variables needed
        val zp = zeropage
        asmgen.out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}")
        asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
        asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1}    ; word")
        asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2}    ; word")
        asmgen.out(".weak")   // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!)
        asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
        asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
        asmgen.out(".endweak")

        if(options.symbolDefs.isNotEmpty()) {
            asmgen.out("; -- user supplied symbols on the command line")
            for((name, value) in options.symbolDefs) {
                asmgen.out("$name = $value")
            }
        }

        when(options.output) {
            OutputType.RAW -> {
                asmgen.out("; ---- raw assembler program ----")
                asmgen.out("* = ${options.loadAddress.toHex()}")
            }
            OutputType.PRG -> {
                when(options.launcher) {
                    CbmPrgLauncherType.BASIC -> {
                        if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
                            errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.position)
                        }
                        asmgen.out("; ---- basic program with sys call ----")
                        asmgen.out("* = ${options.loadAddress.toHex()}")
                        val year = LocalDate.now().year
                        asmgen.out("  .word  (+), $year")
                        asmgen.out("  .null  $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
                        asmgen.out("+\t.word  0")
                        asmgen.out("prog8_entrypoint\t; assembly code starts here")
                        if(!options.noSysInit)
                            asmgen.out("  jsr  sys.init_system")
                        asmgen.out("  jsr  sys.init_system_phase2")
                    }
                    CbmPrgLauncherType.NONE -> {
                        asmgen.out("; ---- program without basic sys call ----")
                        asmgen.out("* = ${options.loadAddress.toHex()}")
                        if(!options.noSysInit)
                            asmgen.out("  jsr  sys.init_system")
                        asmgen.out("  jsr  sys.init_system_phase2")
                    }
                }
            }
            OutputType.XEX -> {
                asmgen.out("; ---- atari xex program ----")
                asmgen.out("* = ${options.loadAddress.toHex()}")
                if(!options.noSysInit)
                    asmgen.out("  jsr  sys.init_system")
                asmgen.out("  jsr  sys.init_system_phase2")
            }
        }

        if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
            asmgen.out("""
                ; zeropage is clobbered so we need to reset the machine at exit
                lda  #>sys.reset_system
                pha
                lda  #<sys.reset_system
                pha""")
        }

        // make sure that on the cx16 and c64, basic rom is banked in again when we exit the program
        when(compTarget.name) {
            "cx16" -> {
                if(options.floats)
                    asmgen.out("  lda  #4 |  sta  $01")    // to use floats, make sure Basic rom is banked in
                asmgen.out("  jsr  main.start")
                asmgen.out("  jmp  sys.cleanup_at_exit")
            }
            "c64" -> {
                asmgen.out("  jsr  main.start |  lda  #31 |  sta  $01")
                if(!options.noSysInit)
                    asmgen.out("  jmp  sys.cleanup_at_exit")
                else
                    asmgen.out("  rts")
            }
            "c128" -> {
                asmgen.out("  jsr  main.start |  lda  #0 |  sta ${"$"}ff00")
                if(!options.noSysInit)
                    asmgen.out("  jmp  sys.cleanup_at_exit")
                else
                    asmgen.out("  rts")
            }
            else -> asmgen.jmp("main.start")
        }
    }

    private fun memorySlabs() {
        if(symboltable.allMemorySlabs.isNotEmpty()) {
            asmgen.out("; memory slabs\n  .section slabs_BSS")
            asmgen.out("prog8_slabs\t.block")
            for (slab in symboltable.allMemorySlabs) {
                if (slab.align > 1u)
                    asmgen.out("\t.align  ${slab.align.toHex()}")
                asmgen.out("${slab.name}\t.fill  ${slab.size}")
            }
            asmgen.out("\t.bend\n  .send slabs_BSS")
        }
    }

    private fun footer() {
        asmgen.out("; bss sections")
        asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
        if(options.varsHighBank!=null) {
            if(options.compTarget.machine.BSSHIGHRAM_START == 0u || options.compTarget.machine.BSSHIGHRAM_END==0u) {
                throw AssemblyError("current compilation target hasn't got the high ram area properly defined")
            }
            // BSS vars in high ram area, memory() slabs just concatenated at the end of the program.
            if(symboltable.allMemorySlabs.isNotEmpty()) {
                asmgen.out("  .dsection slabs_BSS")
            }
            asmgen.out("prog8_program_end\t; end of program label for progend()")
            asmgen.out("  * = ${options.compTarget.machine.BSSHIGHRAM_START.toHex()}")
            asmgen.out("prog8_bss_section_start")
            asmgen.out("  .dsection BSS")
            asmgen.out("  .cerror * >= ${options.compTarget.machine.BSSHIGHRAM_END.toHex()}, \"too many variables for BSS section\"")
            asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
        } else {
            // BSS vars followed by memory() slabs, concatenated at the end of the program.
            asmgen.out("prog8_bss_section_start")
            asmgen.out("  .dsection BSS")
            asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
            if(symboltable.allMemorySlabs.isNotEmpty()) {
                asmgen.out("  .dsection slabs_BSS")
            }
            asmgen.out("prog8_program_end\t; end of program label for progend()")
        }
    }

    private fun block2asm(block: PtBlock) {
        asmgen.out("")
        asmgen.out("; ---- block: '${block.name}' ----")
        if(block.address!=null)
            asmgen.out("* = ${block.address!!.toHex()}")
        else {
            if(block.alignment==PtBlock.BlockAlignment.WORD)
                asmgen.out("\t.align 2")
            else if(block.alignment==PtBlock.BlockAlignment.PAGE)
                asmgen.out("\t.align $100")
        }

        asmgen.out("${block.name}\t" + (if(block.forceOutput) ".block" else ".proc"))
        asmgen.outputSourceLine(block)

        createBlockVariables(block)
        asmsubs2asm(block.children)

        asmgen.out("")

        val initializers = blockVariableInitializers.getValue(block)
        val notInitializers = block.children.filterNot { it in initializers }
        notInitializers.forEach { asmgen.translate(it) }

        // generate subroutine to initialize block-level (global) variables
        if (initializers.isNotEmpty()) {
            asmgen.out("prog8_init_vars\t.block")
            initializers.forEach { assign ->
                if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
                    asmgen.translate(assign)
                // the other variables that should be set to zero are done so as part of the BSS section.
            }
            asmgen.out("  rts\n  .bend")
        }

        asmgen.out(if(block.forceOutput) "\n\t.bend" else "\n\t.pend")
    }

    private fun getVars(scope: StNode): Map<String, StNode> =
        scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }

    private fun createBlockVariables(block: PtBlock) {
        val scope = symboltable.lookupUnscopedOrElse(block.name) { throw AssemblyError("lookup") }
        require(scope.type==StNodeType.BLOCK)
        val varsInBlock = getVars(scope)

        // Zeropage Variables
        val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
        zeropagevars2asm(varnames)

        // MemDefs and Consts
        val mvs = varsInBlock
            .filter { it.value.type==StNodeType.MEMVAR }
            .map { it.value as StMemVar }
        val consts = varsInBlock
            .filter { it.value.type==StNodeType.CONSTANT }
            .map { it.value as StConstant }
        memdefsAndConsts2asm(mvs, consts)

        // normal statically allocated variables
        val variables = varsInBlock
            .filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
            .map { it.value as StStaticVariable }
        nonZpVariables2asm(variables)
    }

    internal fun translateAsmSubroutine(sub: PtAsmSub) {
        if(sub.inline) {
            if(options.optimize) {
                return
            }
        }

        asmgen.out("")

        val asmStartScope: String
        val asmEndScope: String
        if(sub.definingBlock()!!.forceOutput) {
            asmStartScope = ".block"
            asmEndScope = ".bend"
        } else {
            asmStartScope = ".proc"
            asmEndScope = ".pend"
        }

        if(sub.address!=null)
            return  // already done at the memvars section

        // asmsub with most likely just an inline asm in it
        asmgen.out("${sub.name}\t$asmStartScope")
        sub.children.forEach { asmgen.translate(it) }
        asmgen.out("  $asmEndScope")
    }


    internal fun translateSubroutine(sub: PtSub) {
        asmgen.out("")

        val asmStartScope: String
        val asmEndScope: String
        if(sub.definingBlock()!!.forceOutput) {
            asmStartScope = ".block"
            asmEndScope = ".bend"
        } else {
            asmStartScope = ".proc"
            asmEndScope = ".pend"
        }

        asmgen.out("${sub.name}\t$asmStartScope")

        val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
        require(scope.type==StNodeType.SUBROUTINE)
        val varsInSubroutine = getVars(scope)

        // Zeropage Variables
        val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
        zeropagevars2asm(varnames)

        // MemDefs and Consts
        val mvs = varsInSubroutine
            .filter { it.value.type==StNodeType.MEMVAR }
            .map { it.value as StMemVar }
        val consts = varsInSubroutine
            .filter { it.value.type==StNodeType.CONSTANT }
            .map { it.value as StConstant }
        memdefsAndConsts2asm(mvs, consts)

        asmsubs2asm(sub.children)

        // the main.start subroutine is the program's entrypoint and should perform some initialization logic
        if(sub.name=="start" && sub.definingBlock()!!.name=="main")
            entrypointInitialization()

        if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
            asmgen.out("; simple int arg(s) passed via register(s)")
            if(sub.parameters.size==1) {
                val dt = sub.parameters[0].type
                val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
                if(dt in ByteDatatypes)
                    asmgen.assignRegister(RegisterOrPair.A, target)
                else
                    asmgen.assignRegister(RegisterOrPair.AY, target)
            } else {
                require(sub.parameters.size==2)
                // 2 simple byte args, first in A, second in Y
                val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name)
                val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, sub.parameters[1].position, variableAsmName = sub.parameters[1].name)
                asmgen.assignRegister(RegisterOrPair.A, target1)
                asmgen.assignRegister(RegisterOrPair.Y, target2)
            }
        }

        asmgen.out("; statements")
        sub.children.forEach { asmgen.translate(it) }

        asmgen.out("; variables")
        asmgen.out("    .section BSS")
        val asmGenInfo = asmgen.subroutineExtra(sub)
        for((dt, name, addr) in asmGenInfo.extraVars) {
            if(addr!=null)
                asmgen.out("$name = $addr")
            else when(dt) {
                DataType.UBYTE -> asmgen.out("$name    .byte  ?")
                DataType.UWORD -> asmgen.out("$name    .word  ?")
                DataType.FLOAT -> asmgen.out("$name    .fill  ${options.compTarget.machine.FLOAT_MEM_SIZE}")
                else -> throw AssemblyError("weird dt for extravar $dt")
            }
        }
        if(asmGenInfo.usedRegsaveA)      // will probably never occur
            asmgen.out("prog8_regsaveA     .byte  ?")
        if(asmGenInfo.usedRegsaveX)
            asmgen.out("prog8_regsaveX     .byte  ?")
        if(asmGenInfo.usedRegsaveY)
            asmgen.out("prog8_regsaveY     .byte  ?")
        if(asmGenInfo.usedFloatEvalResultVar1)
            asmgen.out("$subroutineFloatEvalResultVar1    .fill  ${options.compTarget.machine.FLOAT_MEM_SIZE}")
        if(asmGenInfo.usedFloatEvalResultVar2)
            asmgen.out("$subroutineFloatEvalResultVar2    .fill  ${options.compTarget.machine.FLOAT_MEM_SIZE}")
        asmgen.out("  .send BSS")

        // normal statically allocated variables
        val variables = varsInSubroutine
            .filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
            .map { it.value as StStaticVariable }
        nonZpVariables2asm(variables)

        asmgen.out("  $asmEndScope")
    }

    private fun entrypointInitialization() {
        asmgen.out("; program startup initialization")
        asmgen.out("  cld |  tsx |  stx  prog8_lib.orig_stackpointer    ; required for sys.exit()")
        // set full BSS area to zero
        asmgen.out("""
    .if  prog8_bss_section_size>0
    ; reset all variables in BSS section to zero
	lda  #<prog8_bss_section_start
	ldy  #>prog8_bss_section_start
	sta  P8ZP_SCRATCH_W1
	sty  P8ZP_SCRATCH_W1+1
	ldx  #<prog8_bss_section_size
	ldy  #>prog8_bss_section_size
	lda  #0
	jsr  prog8_lib.memset
    .endif""")

        blockVariableInitializers.forEach {
            if (it.value.isNotEmpty())
                asmgen.out("  jsr  ${it.key.name}.prog8_init_vars")
        }

        // string and array variables in zeropage that have initializer value, should be initialized
        val stringVarsWithInitInZp = getZpStringVarsWithInitvalue()
        val arrayVarsWithInitInZp = getZpArrayVarsWithInitvalue()
        if(stringVarsWithInitInZp.isNotEmpty() || arrayVarsWithInitInZp.isNotEmpty()) {
            asmgen.out("; zp str and array initializations")
            stringVarsWithInitInZp.forEach {
                val name = asmgen.asmVariableName(it.name)
                asmgen.out("""
                    lda  #<${name}
                    ldy  #>${name}
                    sta  P8ZP_SCRATCH_W1
                    sty  P8ZP_SCRATCH_W1+1
                    lda  #<${name}_init_value
                    ldy  #>${name}_init_value
                    jsr  prog8_lib.strcpy""")
            }
            arrayVarsWithInitInZp.forEach {
                val size = it.alloc.size
                val name = asmgen.asmVariableName(it.name)
                asmgen.out("""
                    lda  #<${name}_init_value
                    ldy  #>${name}_init_value
                    sta  cx16.r0L
                    sty  cx16.r0H
                    lda  #<${name}
                    ldy  #>${name}
                    sta  cx16.r1L
                    sty  cx16.r1H
                    lda  #<$size
                    ldy  #>$size
                    jsr  sys.memcopy""")
            }
            asmgen.out("  jmp  +")
        }

        stringVarsWithInitInZp.forEach {
            val varname = asmgen.asmVariableName(it.name)+"_init_value"
            outputStringvar(varname, it.value.second, it.value.first)
        }

        arrayVarsWithInitInZp.forEach {
            val varname = asmgen.asmVariableName(it.name)+"_init_value"
            arrayVariable2asm(varname, it.alloc.dt, it.value, null)
        }

        asmgen.out("""+        
                    ldx  #127       ; init estack ptr (half page)
                    clv
                    clc""")
    }

    private class ZpStringWithInitial(
        val name: String,
        val alloc: MemoryAllocator.VarAllocation,
        val value: Pair<String, Encoding>
    )

    private class ZpArrayWithInitial(
        val name: String,
        val alloc: MemoryAllocator.VarAllocation,
        val value: StArray
    )

    private fun getZpStringVarsWithInitvalue(): Collection<ZpStringWithInitial> {
        val result = mutableListOf<ZpStringWithInitial>()
        val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
        for (variable in vars) {
            val scopedName = variable.key
            val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
            if(svar.onetimeInitializationStringValue!=null)
                result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
        }
        return result
    }

    private fun getZpArrayVarsWithInitvalue(): Collection<ZpArrayWithInitial> {
        val result = mutableListOf<ZpArrayWithInitial>()
        val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
        for (variable in vars) {
            val scopedName = variable.key
            val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
            if(svar.onetimeInitializationArrayValue!=null)
                result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
        }
        return result
    }

    private fun zeropagevars2asm(varNames: Set<String>) {
        val zpVariables = allocator.zeropageVars.filter { it.key in varNames }.toList().sortedBy { it.second.address }
        for ((scopedName, zpvar) in zpVariables) {
            if (scopedName.startsWith("cx16.r"))
                continue        // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
            asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
        }
    }

    private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
        asmgen.out("")
        val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
        if(varsNoInit.isNotEmpty()) {
            asmgen.out("; non-zeropage variables without initialization value")
            asmgen.out("  .section BSS")
            varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
                uninitializedVariable2asm(it)
            }
            asmgen.out("  .send BSS")
        }

        if(varsWithInit.isNotEmpty()) {
            asmgen.out("; non-zeropage variables")
            val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
            stringvars.forEach {
                outputStringvar(
                    it.name,
                    it.onetimeInitializationStringValue!!.second,
                    it.onetimeInitializationStringValue!!.first
                )
            }
            othervars.sortedBy { it.type }.forEach {
                staticVariable2asm(it)
            }
        }
    }

    private fun uninitializedVariable2asm(variable: StStaticVariable) {
        when (variable.dt) {
            DataType.UBYTE -> asmgen.out("${variable.name}\t.byte  ?")
            DataType.BYTE -> asmgen.out("${variable.name}\t.char  ?")
            DataType.UWORD -> asmgen.out("${variable.name}\t.word  ?")
            DataType.WORD -> asmgen.out("${variable.name}\t.sint  ?")
            DataType.FLOAT -> asmgen.out("${variable.name}\t.fill  ${compTarget.machine.FLOAT_MEM_SIZE}")
            in SplitWordArrayTypes -> {
                val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!) / 2
                asmgen.out("${variable.name}_lsb\t.fill  $numbytesPerHalf")
                asmgen.out("${variable.name}_msb\t.fill  $numbytesPerHalf")
            }
            in ArrayDatatypes -> {
                val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
                asmgen.out("${variable.name}\t.fill  $numbytes")
            }
            else -> {
                throw AssemblyError("weird dt")
            }
        }
    }

    private fun staticVariable2asm(variable: StStaticVariable) {
        val initialValue: Number =
            if(variable.onetimeInitializationNumericValue!=null) {
                if(variable.dt== DataType.FLOAT)
                    variable.onetimeInitializationNumericValue!!
                else
                    variable.onetimeInitializationNumericValue!!.toInt()
            } else 0

        when (variable.dt) {
            DataType.UBYTE -> asmgen.out("${variable.name}\t.byte  ${initialValue.toHex()}")
            DataType.BYTE -> asmgen.out("${variable.name}\t.char  $initialValue")
            DataType.UWORD -> asmgen.out("${variable.name}\t.word  ${initialValue.toHex()}")
            DataType.WORD -> asmgen.out("${variable.name}\t.sint  $initialValue")
            DataType.FLOAT -> {
                if(initialValue==0) {
                    asmgen.out("${variable.name}\t.byte  0,0,0,0,0  ; float")
                } else {
                    val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
                    asmgen.out("${variable.name}\t.byte  $floatFill  ; float $initialValue")
                }
            }
            DataType.STR -> {
                throw AssemblyError("all string vars should have been interned into prog")
            }
            in ArrayDatatypes -> arrayVariable2asm(variable.name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
            else -> {
                throw AssemblyError("weird dt")
            }
        }
    }

    private fun arrayVariable2asm(varname: String, dt: DataType, value: StArray?, orNumberOfZeros: Int?) {
        when(dt) {
            DataType.ARRAY_UB -> {
                val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
                if (data.size <= 16)
                    asmgen.out("$varname\t.byte  ${data.joinToString()}")
                else {
                    asmgen.out(varname)
                    for (chunk in data.chunked(16))
                        asmgen.out("  .byte  " + chunk.joinToString())
                }
            }
            DataType.ARRAY_B -> {
                val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
                if (data.size <= 16)
                    asmgen.out("$varname\t.char  ${data.joinToString()}")
                else {
                    asmgen.out(varname)
                    for (chunk in data.chunked(16))
                        asmgen.out("  .char  " + chunk.joinToString())
                }
            }
            DataType.ARRAY_UW -> {
                val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
                if (data.size <= 16)
                    asmgen.out("$varname\t.word  ${data.joinToString()}")
                else {
                    asmgen.out(varname)
                    for (chunk in data.chunked(16))
                        asmgen.out("  .word  " + chunk.joinToString())
                }
            }
            DataType.ARRAY_W -> {
                val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
                if (data.size <= 16)
                    asmgen.out("$varname\t.sint  ${data.joinToString()}")
                else {
                    asmgen.out(varname)
                    for (chunk in data.chunked(16))
                        asmgen.out("  .sint  " + chunk.joinToString())
                }
            }
            DataType.ARRAY_UW_SPLIT -> {
                val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
                asmgen.out("_array_$varname := ${data.joinToString()}")
                asmgen.out("${varname}_lsb\t.byte <_array_$varname")
                asmgen.out("${varname}_msb\t.byte >_array_$varname")
            }
            DataType.ARRAY_W_SPLIT -> {
                val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
                asmgen.out("_array_$varname := ${data.joinToString()}")
                asmgen.out("${varname}_lsb\t.byte <_array_$varname")
                asmgen.out("${varname}_msb\t.byte >_array_$varname")
            }
            DataType.ARRAY_F -> {
                val array = value ?: zeroFilledArray(orNumberOfZeros!!)
                val floatFills = array.map {
                    compTarget.machine.getFloatAsmBytes(it.number!!)
                }
                asmgen.out(varname)
                for (f in array.zip(floatFills))
                    asmgen.out("  .byte  ${f.second}  ; float ${f.first}")
            }
            else -> throw AssemblyError("require array dt")
        }
    }

    private fun zeroFilledArray(numElts: Int): StArray {
        val values = mutableListOf<StArrayElement>()
        repeat(numElts) {
            values.add(StArrayElement(0.0, null))
        }
        return values
    }

    private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
        memvars.sortedBy { it.address }.forEach {
            asmgen.out("  ${it.name} = ${it.address.toHex()}")
        }
        consts.sortedBy { it.name }.forEach {
            if(it.dt==DataType.FLOAT)
                asmgen.out("  ${it.name} = ${it.value}")
            else
                asmgen.out("  ${it.name} = ${it.value.toHex()}")
        }
    }

    private fun asmsubs2asm(statements: List<PtNode>) {
        statements
            .filter { it is PtAsmSub && it.address!=null }
            .forEach { asmsub ->
                asmsub as PtAsmSub
                asmgen.out("  ${asmsub.name} = ${asmsub.address!!.toHex()}")
            }
    }

    private fun outputStringvar(varname: String, encoding: Encoding, value: String) {
        asmgen.out("$varname\t; $encoding:\"${value.escape().replace("\u0000", "<NULL>")}\"", false)
        val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte())
        val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
        for (chunk in outputBytes.chunked(16))
            asmgen.out("  .byte  " + chunk.joinToString())
    }

    private fun makeArrayFillDataUnsigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> {
        val array = value ?: zeroFilledArray(orNumberOfZeros!!)
        return when (dt) {
            DataType.ARRAY_UB ->
                // byte array can never contain pointer-to types, so treat values as all integers
                array.map {
                    val number = it.number!!.toInt()
                    "$"+number.toString(16).padStart(2, '0')
                }
            DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map {
                if(it.number!=null) {
                    "$" + it.number!!.toInt().toString(16).padStart(4, '0')
                }
                else if(it.addressOfSymbol!=null) {
                    asmgen.asmSymbolName(it.addressOfSymbol!!)
                }
                else
                    throw AssemblyError("weird array elt")
            }
            else -> throw AssemblyError("invalid dt")
        }
    }

    private fun makeArrayFillDataSigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> {
        val array = value ?: zeroFilledArray(orNumberOfZeros!!)
        return when (dt) {
            // byte array can never contain pointer-to types, so treat values as all integers
            DataType.ARRAY_UB ->
                array.map {
                    val number = it.number!!.toInt()
                    "$"+number.toString(16).padStart(2, '0')
                }
            DataType.ARRAY_B ->
                array.map {
                    val number = it.number!!.toInt()
                    val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
                    if(number>=0)
                        "$$hexnum"
                    else
                        "-$$hexnum"
                }
            DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map {
                val number = it.number!!.toInt()
                "$" + number.toString(16).padStart(4, '0')
            }
            DataType.ARRAY_W, DataType.ARRAY_W_SPLIT -> array.map {
                val number = it.number!!.toInt()
                val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
                if(number>=0)
                    "$$hexnum"
                else
                    "-$$hexnum"
            }
            else -> throw AssemblyError("invalid dt")
        }
    }

}
