package millfork.output

import millfork.assembly.OptimizationContext
import millfork.assembly.opt.{SingleStatus, Status}
import millfork.{CompilationFlag, CompilationOptions, Cpu, Platform}
import millfork.assembly.z80.{ZOpcode, _}
import millfork.assembly.z80.opt.{CoarseFlowAnalyzer, ConditionalInstructions, CpuStatus, JumpFollowing, JumpShortening}
import millfork.compiler.z80.Z80Compiler
import millfork.env._
import millfork.node.Z80NiceFunctionProperty.{DoesntChangeBC, DoesntChangeDE, DoesntChangeHL, DoesntChangeIY, SetsATo}
import millfork.node.{NiceFunctionProperty, Position, Program, ZRegister}

import scala.annotation.tailrec
import scala.collection.mutable

/**
  * @author Karol Stasiak
  */
class Z80Assembler(program: Program,
                   rootEnv: Environment,
                   platform: Platform) extends AbstractAssembler[ZLine](program, rootEnv, platform, Z80InliningCalculator, Z80Compiler) {
  override def performFinalOptimizationPass(f: NormalFunction, actuallyOptimize: Boolean, options: CompilationOptions, code: List[ZLine]): List[ZLine] = {
    if (actuallyOptimize) {
      JumpShortening(f, ConditionalInstructions(options, JumpFollowing(options, code)), options)
    } else code
  }

  private def internalRegisterIndex(reg: ZRegister.Value): Int = reg match {
    case ZRegister.B => 0
    case ZRegister.C => 1
    case ZRegister.D => 2
    case ZRegister.E => 3
    case ZRegister.H => 4
    case ZRegister.L => 5
    case ZRegister.MEM_HL => 6
    case ZRegister.MEM_IX_D => 6
    case ZRegister.MEM_IY_D => 6
    case ZRegister.A => 7
    case ZRegister.BC => 0
    case ZRegister.DE => 1
    case ZRegister.HL => 2
    case ZRegister.AF => 3
    case ZRegister.SP => 3
  }

  private def prefixByte(reg: ZRegister.Value): Int = reg match {
    case ZRegister.IX | ZRegister.MEM_IX_D => 0xdd
    case ZRegister.IY | ZRegister.MEM_IY_D => 0xfd
  }

  override def emitInstruction(bank: String, options: CompilationOptions, index: Int, instr: ZLine): Int = {
    import millfork.assembly.z80.ZOpcode._
    import ZRegister._
    import Z80Assembler._
    import CompilationFlag._
    def requireIntel8080(): Unit = if (!options.flag(EmitIntel8080Opcodes)) log.error("Unsupported instruction: " + instr)

    def requireZ80(): Unit = if (!options.flag(EmitZ80Opcodes)) log.error("Unsupported instruction: " + instr)

    def requireZ80Illegals(): Unit = if (!options.flag(EmitZ80Opcodes) || !options.flag(EmitIllegals)) log.error("Unsupported instruction: " + instr)

    def requireExtended80(): Unit = if (!options.flag(EmitExtended80Opcodes)) log.error("Unsupported instruction: " + instr)

    def requireSharp(): Unit = if (!options.flag(EmitSharpOpcodes)) log.error("Unsupported instruction: " + instr)

    def requireEZ80(): Unit = if (!options.flag(EmitEZ80Opcodes)) log.error("Unsupported instruction: " + instr)

    def requireIntel8085(): Unit = if (!options.flag(EmitIntel8085Opcodes)) log.error("Unsupported instruction: " + instr)

    def requireIntel8085Illegals(): Unit = if (!options.flag(EmitIntel8085Opcodes) || !options.flag(EmitIllegals)) log.error("Unsupported instruction: " + instr)

    def requireNext(): Unit = if (!options.flag(EmitZ80NextOpcodes)) log.error("Unsupported instruction: " + instr)

    def useSharpOpcodes():Boolean = {
      if (!options.flag(EmitSharpOpcodes) && !options.flag(EmitIntel8080Opcodes))
        log.error("Cannot determine which variant to emit : " + instr)
      options.flag(EmitSharpOpcodes)
    }

    implicit val position = instr.source.map(sl => Position(sl.moduleName, sl.line, 0, 0))
    try { instr match {
      case ZLine0(LABEL, NoRegisters, MemoryAddressConstant(Label(labelName))) =>
        val bank0 = mem.banks(bank)
        labelMap(labelName) = bank0.index -> index
        index
      case ZLine0(BYTE, NoRegisters, param) =>
        writeByte(bank, index, param)
        index + 1
      case ZLine0(CHANGED_MEM, NoRegisters, MemoryAddressConstant(Label(l))) if l.contains("..brk") =>
        breakpointSet += mem.banks(bank).index -> index
        index
      case ZLine0(DISCARD_F | DISCARD_HL | DISCARD_BC | DISCARD_DE | DISCARD_IX | DISCARD_IY | DISCARD_A | CHANGED_MEM, NoRegisters, _) =>
        index
      case ZLine0(LABEL | BYTE | DISCARD_F | DISCARD_HL | DISCARD_BC | DISCARD_DE | DISCARD_IX | DISCARD_IY | DISCARD_A | CHANGED_MEM, _, _) =>
        ???
      case ZLine0(RIM, NoRegisters, _) =>
        requireIntel8085()
        writeByte(bank, index, 0x20)
        index + 1
      case ZLine0(SIM, NoRegisters, _) =>
        requireIntel8085()
        writeByte(bank, index, 0x30)
        index + 1
      case ZLine0(RST, NoRegisters, param) =>
        val opcode = param.quickSimplify match {
          case NumericConstant(n, _) if n >=0 && n <= 0x38 && n % 8 == 0 => 0xc7 + n.toInt
          case _ => log.error("Invalid param for RST"); 0xc7
        }
        writeByte(bank, index, opcode)
        index + 1
      case ZLine0(IM, NoRegisters, param) =>
        requireZ80()
        val opcode = param.quickSimplify match {
          case NumericConstant(0, _) => 0x46
          case NumericConstant(1, _) => 0x56
          case NumericConstant(2, _) => 0x5e
          case _ => log.error("Invalid param for IM"); 0xc7
        }
        writeByte(bank, index, 0xED)
        writeByte(bank, index + 1, opcode)
        index + 2
      case ZLine0(op, OneRegister(reg), _) if ZOpcodeClasses.AllSingleBit(op) =>
        requireExtended80()
        writeByte(bank, index, 0xcb)
        writeByte(bank, index + 1, ZOpcodeClasses.singleBitOpcode(op) + internalRegisterIndex(reg))
        index + 2
      case ZLine0(op, NoRegisters, _) if implieds.contains(op) =>
        writeByte(bank, index, implieds(op))
        index + 1
      case ZLine0(EXX, NoRegisters, _)=>
        requireZ80()
        writeByte(bank, index, 0xd9)
        index + 1
      case ZLine0(EX_AF_AF, NoRegisters, _)=>
        requireZ80()
        writeByte(bank, index, 8)
        index + 1
      case ZLine0(RETI, NoRegisters, _) =>
        requireExtended80()
        if (useSharpOpcodes()) {
          writeByte(bank, index, 0xd9)
          index + 1
        } else {
          writeByte(bank, index, 0xed)
          writeByte(bank, index + 1, 0x4d)
          index + 2
        }
      case ZLine0(op, NoRegisters, _) if edImplieds.contains(op) =>
        requireZ80()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, edImplieds(op))
        index + 2
      case ZLine0(op, NoRegisters, _) if nextEdImplieds.contains(op) =>
        requireNext()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, nextEdImplieds(op))
        index + 2
      case ZLine0(ADD_16, TwoRegisters(r@(ZRegister.HL | ZRegister.BC | ZRegister.DE), ZRegister.A), _) =>
        requireNext()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x33 - internalRegisterIndex(r))
        index + 2
      case ZLine0(ADD_16, TwoRegisters(r@(ZRegister.HL | ZRegister.BC | ZRegister.DE), ZRegister.IMM_16), nn) =>
        requireNext()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x36 - internalRegisterIndex(r))
        writeWord(bank, index + 2, nn)
        index + 4
      case ZLine0(PUSH, OneRegister(ZRegister.IMM_16), nn) =>
        requireNext()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x8A)
        writeByte(bank, index + 2, nn.hiByte)
        writeByte(bank, index + 3, nn.loByte)
        index + 4
      case ZLine0(TEST, OneRegister(ZRegister.IMM_8), nn) =>
        requireNext()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x27)
        writeByte(bank, index + 2, nn)
        index + 3
      case ZLine0(NEXTREG, TwoRegisters(ZRegister.IMM_8, ZRegister.IMM_8), nn) =>
        requireNext()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x91)
        writeWord(bank, index + 2, nn)
        index + 4
      case ZLine0(NEXTREG, TwoRegisters(ZRegister.IMM_8, ZRegister.A), nn) =>
        requireNext()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x92)
        writeByte(bank, index + 2, nn)
        index + 3
      case ZLine0(ADD_16, TwoRegisters(ZRegister.HL, source), _) =>
        writeByte(bank, index, 9 + 16 * internalRegisterIndex(source))
        index + 1
      case ZLine0(ADD_16, TwoRegisters(ix@(ZRegister.IX | ZRegister.IY), source@(ZRegister.IX | ZRegister.IY)), _)=>
        requireZ80()
        if (ix == source) {
          writeByte(bank, index, prefixByte(ix))
          writeByte(bank, index + 1, 9 + 16 * internalRegisterIndex(HL))
          index + 2
        } else {
          log.fatal("Cannot assemble " + instr)
          index
        }
      case ZLine0(ADD_16, TwoRegisters(ix@(ZRegister.IX | ZRegister.IY), source), _) =>
        requireZ80()
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, 9 + 16 * internalRegisterIndex(source))
        index + 2
      case ZLine0(ADC_16, TwoRegisters(ZRegister.HL, reg), _) =>
        requireZ80()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x4a + 0x10 * internalRegisterIndex(reg))
        index + 2
      case ZLine0(SBC_16, TwoRegisters(ZRegister.HL, reg), _) =>
        requireZ80()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x42 + 0x10 * internalRegisterIndex(reg))
        index + 2
      case ZLine0(LD_16, TwoRegisters(ix@(ZRegister.IX | ZRegister.IY), ZRegister.IMM_16), param) =>
        requireZ80()
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, 0x21)
        writeWord(bank, index + 2, param)
        index + 4
      case ZLine0(LD_16, TwoRegisters(target, ZRegister.IMM_16), param) =>
        writeByte(bank, index, 1 + 16 * internalRegisterIndex(target))
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(LD_16, TwoRegisters(ix@(ZRegister.IX | ZRegister.IY), ZRegister.MEM_ABS_16), param) =>
        requireZ80()
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, 0x2a)
        writeWord(bank, index + 2, param)
        index + 4
      case ZLine0(LD_16, TwoRegisters(ZRegister.MEM_ABS_16, ix@(ZRegister.IX | ZRegister.IY)), param) =>
        requireZ80()
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, 0x22)
        writeWord(bank, index + 2, param)
        index + 4
      case ZLine0(LD_16, TwoRegisters(ZRegister.HL, ZRegister.MEM_ABS_16), param) =>
        requireIntel8080()
        writeByte(bank, index, 0x2a)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(LD_16, TwoRegisters(ZRegister.MEM_ABS_16, ZRegister.HL), param) =>
        requireIntel8080()
        writeByte(bank, index, 0x22)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(LD_16, TwoRegisters(reg@(ZRegister.BC | ZRegister.DE | ZRegister.SP), ZRegister.MEM_ABS_16), param) =>
        requireZ80()
        writeByte(bank, index, 0xed)
        writeByte(bank, index+1, 0x4b + 0x10 * internalRegisterIndex(reg))
        writeWord(bank, index + 2, param)
        index + 4
      case ZLine0(LD_16, TwoRegisters(ZRegister.MEM_ABS_16, ZRegister.SP), param) =>
        requireExtended80()
        if (useSharpOpcodes()) {
          writeByte(bank, index, 8)
          writeWord(bank, index + 1, param)
          index + 3
        } else {
          writeByte(bank, index, 0xed)
          writeByte(bank, index + 1, 0x43 + 0x10 * internalRegisterIndex(SP))
          writeWord(bank, index + 2, param)
          index + 4
        }
      case ZLine0(LD_16, TwoRegisters(ZRegister.MEM_ABS_16, reg@(ZRegister.BC | ZRegister.DE)), param) =>
        requireZ80()
        writeByte(bank, index, 0xed)
        writeByte(bank, index+1, 0x43 + 0x10 * internalRegisterIndex(reg))
        writeWord(bank, index + 2, param)
        index + 4
      case ZLine0(LD_16, TwoRegisters(ZRegister.SP, ZRegister.HL), _) =>
        writeByte(bank, index, 0xF9)
        index + 1
      case ZLine0(LD_16, TwoRegisters(ZRegister.SP, ix@(ZRegister.IX | ZRegister.IY)), _) =>
        requireZ80()
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, 0xF9)
        index + 2
      case ZLine0(SRA, OneRegister(HL), _) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0x10)
        index + 1
      case ZLine0(RL, OneRegister(DE), _) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0x18)
        index + 1
      case ZLine0(op, OneRegister(ZRegister.IMM_8), param) if immediates.contains(op) =>
        val o = immediates(op)
        writeByte(bank, index, o)
        writeByte(bank, index + 1, param)
        index + 2
      case ZLine0(op, OneRegisterOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), offset), _) if ZOpcodeClasses.AllSingleBit(op) =>
        requireZ80()
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, 0xcb)
        writeByte(bank, index + 2, offset)
        writeByte(bank, index + 3, ZOpcodeClasses.singleBitOpcode(op) + internalRegisterIndex(ZRegister.MEM_HL))
        index + 4
      case ZLine0(op, OneRegisterOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), offset), _) if oneRegister.contains(op) =>
        requireZ80()
        val o = oneRegister(op)
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, o.opcode + internalRegisterIndex(ZRegister.MEM_HL) * o.multiplier)
        writeByte(bank, index + 2, offset)
        index + 3
      case ZLine0(op, OneRegister(ix@(ZRegister.IX | ZRegister.IY)), _) if oneRegister.contains(op) =>
        requireZ80()
        val o = oneRegister(op)
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, o.opcode + internalRegisterIndex(ZRegister.HL) * o.multiplier)
        writeByte(bank, index + 2, instr.parameter)
        index + 3
      case ZLine0(op, OneRegister(reg), _) if reg != ZRegister.IMM_8 && oneRegister.contains(op) =>
        val o = oneRegister(op)
        writeByte(bank, index, o.opcode + internalRegisterIndex(reg) * o.multiplier)
        index + 1
      case ZLine0(SLL, OneRegister(reg), _) =>
        requireZ80Illegals()
        writeByte(bank, index, 0xcb)
        writeByte(bank, index + 1, 0x30 + internalRegisterIndex(reg))
        index + 2
      case ZLine0(SWAP, OneRegister(reg), _) =>
        requireSharp()
        writeByte(bank, index, 0xcb)
        writeByte(bank, index + 1, 0x30 + internalRegisterIndex(reg))
        index + 2
      case ZLine0(SLL, OneRegisterOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), offset), _) =>
        requireZ80Illegals()
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, 0xcb)
        writeByte(bank, index + 2, offset)
        writeByte(bank, index + 3, 0x30 + internalRegisterIndex(MEM_HL))
        index + 4
      case ZLine0(op, OneRegister(reg), _) if cbOneRegister.contains(op) =>
        requireExtended80()
        val o = cbOneRegister(op)
        writeByte(bank, index, 0xcb)
        writeByte(bank, index + 1, o.opcode + internalRegisterIndex(reg) * o.multiplier)
        index + 2
      case ZLine0(op, OneRegisterOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), offset), _) if cbOneRegister.contains(op) =>
        requireZ80()
        val o = cbOneRegister(op)
        writeByte(bank, index, prefixByte(ix))
        writeByte(bank, index + 1, 0xcb)
        writeByte(bank, index + 2, offset)
        writeByte(bank, index + 3, o.opcode + internalRegisterIndex(MEM_HL) * o.multiplier)
        index + 4
      case ZLine0(LD, registers, _) =>
        registers match {
          case TwoRegisters(I, A) =>
            requireZ80()
            writeByte(bank, index, 0xed)
            writeByte(bank, index + 1, 0x47)
            index + 2
          case TwoRegisters(A, I) =>
            requireZ80()
            writeByte(bank, index, 0xed)
            writeByte(bank, index + 1, 0x57)
            index + 2
          case TwoRegisters(R, A) =>
            requireZ80()
            writeByte(bank, index, 0xed)
            writeByte(bank, index + 1, 0x4f)
            index + 2
          case TwoRegisters(A, R) =>
            requireZ80()
            writeByte(bank, index, 0xed)
            writeByte(bank, index + 1, 0x5f)
            index + 2
          case TwoRegisters(I | R, _) | TwoRegisters(_, I | R) =>
            log.fatal("Cannot assemble " + instr)
            index
          case TwoRegisters(reg, ZRegister.IMM_8) =>
            writeByte(bank, index, 6 + 8 * internalRegisterIndex(reg))
            writeByte(bank, index + 1, instr.parameter)
            index + 2
          case TwoRegistersOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), ZRegister.IMM_8, offset) =>
            requireZ80()
            writeByte(bank, index, prefixByte(ix))
            writeByte(bank, index + 1, 0x36)
            writeByte(bank, index + 2, offset)
            writeByte(bank, index + 3, instr.parameter)
            index + 4
          case TwoRegisters(ZRegister.A, ZRegister.MEM_ABS_8) =>
            if (useSharpOpcodes()) {
              writeByte(bank, index, 0xfa)
            } else {
              writeByte(bank, index, 0x3a)
            }
            writeWord(bank, index + 1, instr.parameter)
            index + 3
          case TwoRegisters(ZRegister.MEM_ABS_8, ZRegister.A) =>
            if (useSharpOpcodes()) {
              writeByte(bank, index, 0xea)
            } else {
              writeByte(bank, index, 0x32)
            }
            writeWord(bank, index + 1, instr.parameter)
            index + 3
          case TwoRegisters(ZRegister.MEM_BC, ZRegister.A) =>
            writeByte(bank, index, 2)
            index + 1
          case TwoRegisters(ZRegister.MEM_DE, ZRegister.A) =>
            writeByte(bank, index, 0x12)
            index + 1
          case TwoRegisters(ZRegister.A, ZRegister.MEM_BC) =>
            writeByte(bank, index, 0x0a)
            index + 1
          case TwoRegisters(ZRegister.A, ZRegister.MEM_DE) =>
            writeByte(bank, index, 0x1a)
            index + 1
          case TwoRegistersOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), source, offset) =>
            requireZ80()
            writeByte(bank, index, prefixByte(ix))
            writeByte(bank, index + 1, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(ZRegister.MEM_HL) * 8)
            writeByte(bank, index + 2, offset)
            index + 3
          case TwoRegistersOffset(target, ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), offset) =>
            requireZ80()
            writeByte(bank, index, prefixByte(ix))
            writeByte(bank, index + 1, 0x40 + internalRegisterIndex(ZRegister.MEM_HL) + internalRegisterIndex(target) * 8)
            writeByte(bank, index + 2, offset)
            index + 3
          case TwoRegisters(target, source) =>
            writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8)
            index + 1
          case TwoRegisters(target, source) =>
            writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8)
            index + 1
        }
      case ZLine0(IN_IMM, OneRegister(ZRegister.A), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xdb)
        writeByte(bank, index + 1, param)
        index + 2
      case ZLine0(IN_C, OneRegister(reg), param) =>
        requireZ80()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x40 + 8 * internalRegisterIndex(reg))
        index + 2
      case ZLine0(OUT_IMM, OneRegister(ZRegister.A), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xd3)
        writeByte(bank, index + 1, param)
        index + 2
      case ZLine0(OUT_C, OneRegister(reg), param) =>
        requireZ80()
        writeByte(bank, index, 0xed)
        writeByte(bank, index + 1, 0x41 + 8 * internalRegisterIndex(reg))
        index + 2

      case ZLine0(CALL, NoRegisters, param) =>
        writeByte(bank, index, 0xcd)
        writeWord(bank, index + 1, param)
        index + 3



      case ZLine0(CALL, IfFlagClear(ZFlag.Z), param) =>
        writeByte(bank, index, 0xc4)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(CALL, IfFlagClear(ZFlag.C), param) =>
        writeByte(bank, index, 0xd4)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(CALL, IfFlagClear(ZFlag.P), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xe4)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(CALL, IfFlagClear(ZFlag.S), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xf4)
        writeWord(bank, index + 1, param)
        index + 3

      case ZLine0(CALL, IfFlagSet(ZFlag.Z), param) =>
        writeByte(bank, index, 0xcc)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(CALL, IfFlagSet(ZFlag.C), param) =>
        writeByte(bank, index, 0xdc)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(CALL, IfFlagSet(ZFlag.P), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xec)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(CALL, IfFlagSet(ZFlag.S), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xfc)
        writeWord(bank, index + 1, param)
        index + 3

      case ZLine0(JP, NoRegisters, param) =>
        writeByte(bank, index, 0xc3)
        writeWord(bank, index + 1, param)
        index + 3

      case ZLine0(JP, IfFlagClear(ZFlag.Z), param) =>
        writeByte(bank, index, 0xc2)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(JP, IfFlagClear(ZFlag.C), param) =>
        writeByte(bank, index, 0xd2)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(JP, IfFlagClear(ZFlag.P), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xe2)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(JP, IfFlagClear(ZFlag.S), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xf2)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(JP, IfFlagClear(ZFlag.K), param) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0xdd)
        writeWord(bank, index + 1, param)
        index + 3

      case ZLine0(JP, IfFlagSet(ZFlag.Z), param) =>
        writeByte(bank, index, 0xca)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(JP, IfFlagSet(ZFlag.C), param) =>
        writeByte(bank, index, 0xda)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(JP, IfFlagSet(ZFlag.P), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xea)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(JP, IfFlagSet(ZFlag.S), param) =>
        requireIntel8080()
        writeByte(bank, index, 0xfa)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(JP, IfFlagSet(ZFlag.K), param) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0xfd)
        writeWord(bank, index + 1, param)
        index + 3
      case ZLine0(JP, OneRegister(HL), _) =>
        writeByte(bank, index, 0xe9)
        index + 1
      case ZLine0(JP, OneRegister(IX), _) =>
        requireZ80()
        writeByte(bank, index, 0xdd)
        writeByte(bank, index, 0xe9)
        index + 2
      case ZLine0(JP, OneRegister(IY), _) =>
        requireZ80()
        writeByte(bank, index, 0xfd)
        writeByte(bank, index, 0xe9)
        index + 2

      case ZLine0(RET, IfFlagClear(ZFlag.Z), _) =>
        writeByte(bank, index, 0xc0)
        index + 1
      case ZLine0(RET, IfFlagClear(ZFlag.C), _) =>
        writeByte(bank, index, 0xd0)
        index + 1
      case ZLine0(RET, IfFlagClear(ZFlag.P), _) =>
        requireIntel8080()
        writeByte(bank, index, 0xe0)
        index + 1
      case ZLine0(RET, IfFlagClear(ZFlag.S), _) =>
        requireIntel8080()
        writeByte(bank, index, 0xf0)
        index + 1

      case ZLine0(RET, IfFlagSet(ZFlag.Z), _) =>
        writeByte(bank, index, 0xc8)
        index + 1
      case ZLine0(RET, IfFlagSet(ZFlag.C), _) =>
        writeByte(bank, index, 0xd8)
        index + 1
      case ZLine0(RET, IfFlagSet(ZFlag.P), _) =>
        requireIntel8080()
        writeByte(bank, index, 0xe8)
        index + 1
      case ZLine0(RET, IfFlagSet(ZFlag.S), _) =>
        requireIntel8080()
        writeByte(bank, index, 0xf8)
        index + 1

      case ZLine0(JR, IfFlagClear(ZFlag.Z), param) =>
        requireExtended80()
        writeByte(bank, index, 0x20)
        writeByte(bank, index + 1, AssertByte(param - index - 2))
        index + 2
      case ZLine0(JR, IfFlagClear(ZFlag.C), param) =>
        requireExtended80()
        writeByte(bank, index, 0x30)
        writeByte(bank, index + 1, AssertByte(param - index - 2))
        index + 2

      case ZLine0(JR, IfFlagSet(ZFlag.Z), param) =>
        requireExtended80()
        writeByte(bank, index, 0x28)
        writeByte(bank, index + 1, AssertByte(param - index - 2))
        index + 2
      case ZLine0(JR, IfFlagSet(ZFlag.C), param) =>
        requireExtended80()
        writeByte(bank, index, 0x38)
        writeByte(bank, index + 1, AssertByte(param - index - 2))
        index + 2

      case ZLine0(JR, NoRegisters, param) =>
        requireExtended80()
        writeByte(bank, index, 0x18)
        writeByte(bank, index + 1, AssertByte(param - index - 2))
        index + 2
      case ZLine0(DJNZ, NoRegisters, param) =>
        requireZ80()
        writeByte(bank, index, 0x10)
        writeByte(bank, index + 1, AssertByte(param - index - 2))
        index + 2
      case ZLine0(EX_SP, OneRegister(HL), _) =>
        requireIntel8080()
        writeByte(bank, index, 0xe3)
        index + 1
      case ZLine0(EX_SP, OneRegister(IX), _) =>
        requireZ80()
        writeByte(bank, index, 0xdd)
        writeByte(bank, index + 1, 0xe3)
        index + 2
      case ZLine0(EX_SP, OneRegister(IY), _) =>
        requireZ80()
        writeByte(bank, index, 0xfd)
        writeByte(bank, index + 1, 0xe3)
        index + 2
      case ZLine0(EX_DE_HL, _, _) =>
        requireIntel8080()
        writeByte(bank, index, 0xeb)
        index + 1

      case ZLine0(ADD_SP, _, param) =>
        requireSharp()
        writeByte(bank, index, 0xe8)
        writeByte(bank, index + 1, param)
        index + 2
      case ZLine0(LD_HLSP, _, param) =>
        requireSharp()
        writeByte(bank, index, 0xf8)
        writeByte(bank, index + 1, param)
        index + 2
      case ZLine0(LD_AHLI, _, _) =>
        requireSharp()
        writeByte(bank, index, 0x2a)
        index + 1
      case ZLine0(LD_AHLD, _, _) =>
        requireSharp()
        writeByte(bank, index, 0x3a)
        index + 1
      case ZLine0(LD_HLIA, _, _) =>
        requireSharp()
        writeByte(bank, index, 0x22)
        index + 1
      case ZLine0(LD_HLDA, _, _) =>
        requireSharp()
        writeByte(bank, index, 0x32)
        index + 1
      case ZLine0(LDH_AD, _, param) =>
        requireSharp()
        writeByte(bank, index, 0xf0)
        writeByte(bank, index + 1, param.loByte)
        index + 2
      case ZLine0(LDH_DA, _, param) =>
        requireSharp()
        writeByte(bank, index, 0xe0)
        writeByte(bank, index + 1, param.loByte)
        index + 2
      case ZLine0(LDH_AC, _, _) =>
        requireSharp()
        writeByte(bank, index, 0xf2)
        index + 1
      case ZLine0(LDH_CA, _, _) =>
        requireSharp()
        writeByte(bank, index, 0xe2)
        index + 1
      case ZLine0(STOP, _, _) =>
        requireSharp()
        writeByte(bank, index, 0x10)
        index + 1
      case ZLine0(DSUB, _, _) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0x08)
        index + 1
      case ZLine0(LD_DEHL, _, param) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0x28)
        writeByte(bank, index + 1, param)
        index + 2
      case ZLine0(LD_DESP, _, param) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0x38)
        writeByte(bank, index + 1, param)
        index + 2
      case ZLine0(RSTV, _, _) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0xcb)
        index + 1
      case ZLine0(SHLX, _, _) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0xd9)
        index + 1
      case ZLine0(LHLX, _, _) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0xed)
        index + 1
      case ZLine0(RLDE, _, _) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0x18)
        index + 1
      case ZLine0(RRHL, _, _) =>
        requireIntel8085Illegals()
        writeByte(bank, index, 0x10)
        index + 1
      case _ =>
        log.fatal("Cannot assemble " + instr)
        index
    } } catch {
      case _ : MatchError =>
        log.fatal("Cannot assemble " + instr)
        index
    }
  }

  override def injectLabels(labelMap: Map[String, (Int, Int)], code: List[ZLine]): List[ZLine] = code // TODO

  override def quickSimplify(code: List[ZLine]): List[ZLine] = code.map(a => a.copy(parameter = a.parameter.quickSimplify))

  override def gatherNiceFunctionProperties(options: CompilationOptions, niceFunctionProperties: mutable.Set[(NiceFunctionProperty, String)], function: NormalFunction, code: List[ZLine]): Unit = {
    import ZOpcode._
    val functionName = function.name
    if (isNaughty(code)) return
    val localLabels = code.flatMap {
      case ZLine0(LABEL, _, MemoryAddressConstant(Label(l))) => Some(l)
      case _ => None
    }.toSet
    if (code.exists {
      case ZLine0(JP | JR, _, MemoryAddressConstant(Label(l))) => !localLabels(l)
      case ZLine0(JP | JR, _, _) => true
      case ZLine0(RST, _, _) => true
      case _ => false
    }) return
    val optimizationContext = OptimizationContext(options, Map(), None, Set())
    val flow = CoarseFlowAnalyzer.analyze(function, code, optimizationContext)
    def retPropertyScan[T](extractor: CpuStatus => Status[T])(niceFunctionProperty: Status[T] => Option[NiceFunctionProperty]): Unit = {
      val statuses = code.zipWithIndex.flatMap{
        case (ZLine0(RET | RETI | RETN, _, _), ix) => Some(extractor(flow(ix)))
        case _ => None
      }.toSet
      statuses.toSeq match {
        case Seq(only) =>
          niceFunctionProperty(only).foreach { np =>
            niceFunctionProperties += (np -> functionName)
          }
        case _ =>
      }
    }
    def simpleRetPropertyScan[T](extractor: CpuStatus => Status[T])(niceFunctionProperty: T => NiceFunctionProperty): Unit = {
      retPropertyScan(extractor) {
        case SingleStatus(x) => Some(niceFunctionProperty(x))
        case _ => None
      }
    }
    def genericPropertyScan(niceFunctionProperty: NiceFunctionProperty)(predicate: ZLine => Boolean): Unit = {
      val preserved = code.forall {
        case ZLine0(JP | JR, _, MemoryAddressConstant(Label(label))) => localLabels(label)
        case ZLine0(CALL | JP | JR, _, MemoryAddressConstant(th)) => niceFunctionProperties(niceFunctionProperty -> th.name)
        case ZLine0(CALL | JP | JR, _, _) => false
        case l => predicate(l)
      }
      if (preserved) {
        niceFunctionProperties += (niceFunctionProperty -> functionName)
      }
    }
    genericPropertyScan(DoesntChangeHL)(l => !l.changesRegister(ZRegister.HL))
    genericPropertyScan(DoesntChangeDE)(l => !l.changesRegister(ZRegister.DE))
    genericPropertyScan(DoesntChangeBC)(l => !l.changesRegister(ZRegister.BC))
    genericPropertyScan(DoesntChangeIY)(l => !l.changesRegister(ZRegister.IY))
    simpleRetPropertyScan(_.a)(SetsATo)
  }

  @tailrec
  private def isNaughty(code: List[ZLine]): Boolean = {
    import ZOpcode._
    code match {
      case ZLine0(JP, OneRegister(_), _) :: _ => true
      case ZLine0(PUSH, _, _) :: ZLine0(RET | RETI | RETN, _, _) :: _ => true
      case _ :: xs => isNaughty(xs)
      case Nil => false
    }
  }

  override def bytePseudoopcode: String = "DB"

  override def deduplicate(options: CompilationOptions, compiledFunctions: mutable.Map[String, CompiledFunction[ZLine]]): Unit =
    new Z80Deduplicate(rootEnv, options).apply(compiledFunctions)
}

object Z80Assembler {

  case class One(opcode: Int, multiplier: Int)

  val implieds: mutable.Map[ZOpcode.Value, Int] = mutable.Map[ZOpcode.Value, Int]()
  val immediates: mutable.Map[ZOpcode.Value, Int] = mutable.Map[ZOpcode.Value, Int]()
  val edImplieds: mutable.Map[ZOpcode.Value, Int] = mutable.Map[ZOpcode.Value, Int]()
  val oneRegister: mutable.Map[ZOpcode.Value, One] = mutable.Map[ZOpcode.Value, One]()
  val cbOneRegister: mutable.Map[ZOpcode.Value, One] = mutable.Map[ZOpcode.Value, One]()
  val nextEdImplieds: mutable.Map[ZOpcode.Value, Int] = mutable.Map[ZOpcode.Value, Int]()

  do {
    import ZOpcode._
    implieds(NOP) = 0
    implieds(DAA) = 0x27
    implieds(SCF) = 0x37
    implieds(CPL) = 0x2f
    implieds(CCF) = 0x3f
    implieds(RET) = 0xc9
    implieds(EI) = 0xfb
    implieds(DI) = 0xf3
    implieds(HALT) = 0x76
    implieds(RLCA) = 7
    implieds(RRCA) = 0xf
    implieds(RLA) = 0x17
    implieds(RRA) = 0x1f

    immediates(ADD) = 0xc6
    immediates(ADC) = 0xce
    immediates(SUB) = 0xd6
    immediates(SBC) = 0xde
    immediates(AND) = 0xe6
    immediates(XOR) = 0xee
    immediates(OR) = 0xf6
    immediates(CP) = 0xfe

    edImplieds(NEG) = 0x44
    edImplieds(RETN) = 0x45
    edImplieds(RRD) = 0x67
    edImplieds(RLD) = 0x6f
    edImplieds(LDI) = 0xa0
    edImplieds(LDIR) = 0xb0
    edImplieds(CPI) = 0xa1
    edImplieds(CPIR) = 0xb1
    edImplieds(INI) = 0xa2
    edImplieds(INIR) = 0xb2
    edImplieds(OUTI) = 0xa3
    edImplieds(OUTIR) = 0xb3
    edImplieds(LDD) = 0xa8
    edImplieds(LDDR) = 0xb8
    edImplieds(CPD) = 0xa9
    edImplieds(CPDR) = 0xb9
    edImplieds(IND) = 0xaa
    edImplieds(INDR) = 0xba
    edImplieds(OUTD) = 0xab
    edImplieds(OUTDR) = 0xbb

    oneRegister(ADD) = One(0x80, 1)
    oneRegister(ADC) = One(0x88, 1)
    oneRegister(SUB) = One(0x90, 1)
    oneRegister(SBC) = One(0x98, 1)
    oneRegister(AND) = One(0xa0, 1)
    oneRegister(XOR) = One(0xa8, 1)
    oneRegister(OR) = One(0xb0, 1)
    oneRegister(CP) = One(0xb8, 1)
    oneRegister(INC) = One(4, 8)
    oneRegister(DEC) = One(5, 8)
    oneRegister(INC_16) = One(3, 0x10)
    oneRegister(DEC_16) = One(0xb, 0x10)

    oneRegister(POP) = One(0xc1, 0x10)
    oneRegister(PUSH) = One(0xc5, 0x10)

    cbOneRegister(RLC) = One(0, 1)
    cbOneRegister(RRC) = One(8, 1)
    cbOneRegister(RL) = One(0x10, 1)
    cbOneRegister(RR) = One(0x18, 1)
    cbOneRegister(SLA) = One(0x20, 1)
    cbOneRegister(SRA) = One(0x28, 1)
    cbOneRegister(SRL) = One(0x38, 1)

    nextEdImplieds(LDIX) = 0xa4
    nextEdImplieds(LDWS) = 0xa5
    nextEdImplieds(LDIRX) = 0xb4
    nextEdImplieds(LDDX) = 0xb5
    nextEdImplieds(LDDRX) = 0xac
    nextEdImplieds(LDPIRX) = 0xbc
    nextEdImplieds(OUTINB) = 0x90
    nextEdImplieds(MUL) = 0xa4
    nextEdImplieds(SWAPNIB) = 0x23
    nextEdImplieds(MIRROR) = 0x24
    nextEdImplieds(PIXELDN) = 0x93
    nextEdImplieds(PIXELAD) = 0x94
    nextEdImplieds(SETAE) = 0x95
  } while (false)

}

