///|
// DWARF4 compilation-unit writer for debug info declared through the
// programmatic builder (and the C ABI / Rust crate).
//
// The line table itself comes from the assembler (.file/.loc); this module
// adds the .debug_info tree (compile unit, subprograms, variables, base types)
// and a .debug_loc list with the location of each variable, so `frame
// variable` works. Locations are resolved after register allocation: a value
// in a register uses DW_OP_regx, one in a stack slot uses DW_OP_fbreg.

///|
// A resolved variable location.
pub(all) struct DbgLoc {
  name : String
  ty : @types.DbgType
  kind : Int // 0 = register, 1 = frame-base relative (fbreg)
  value : Int // DWARF register number, or frame-base offset
} derive(Debug)

///|
// Entry
struct DbgFn {
  name : String
  start_label : String
  end_label : String
  locs : Array[DbgLoc]
}

///|
let dbg_fns : Array[Array[DbgFn]] = [[]]

///|
pub fn reset() -> Unit {
  dbg_fns[0] = []
}

///|
pub fn has_info() -> Bool {
  dbg_fns[0].length() > 0
}

///|
pub fn record_fn(
  name : String,
  start_label : String,
  end_label : String,
  locs : Array[DbgLoc],
) -> Unit {
  dbg_fns[0].push(DbgFn::{ name, start_label, end_label, locs, })
}

///|
fn uleb(v : Int) -> Array[Int] {
  let out : Array[Int] = []
  let mut x = v
  while true {
    let b = x & 0x7F
    x = x >> 7
    if x == 0 {
      out.push(b)
      break
    } else {
      out.push(b | 0x80)
    }
  }
  out
}

///|
fn sleb(v : Int) -> Array[Int] {
  let out : Array[Int] = []
  let mut x = v
  let mut more = true
  while more {
    let b = x & 0x7F
    x = x >> 7
    if (x == 0 && (b & 0x40) == 0) || (x == -1 && (b & 0x40) != 0) {
      out.push(b)
      more = false
    } else {
      out.push(b | 0x80)
    }
  }
  out
}

///|
fn loc_expr(loc : DbgLoc) -> Array[Int] {
  let out : Array[Int] = []
  if loc.kind == 0 {
    out.push(0x90) // DW_OP_regx
    for b in uleb(loc.value) {
      out.push(b)
    }
  } else {
    out.push(0x91) // DW_OP_fbreg
    for b in sleb(loc.value) {
      out.push(b)
    }
  }
  out
}

///|
fn emit_bytes(sb : StringBuilder, bs : Array[Int]) -> Unit {
  for b in bs {
    sb.write_string("\t.byte ")
    sb.write_string(b.to_string())
    sb.write_char('\n')
  }
}

///|
fn type_off_for(m : Map[String, Int], ty : @types.DbgType) -> Int {
  match ty {
    @types.DbgW => m["w"]
    @types.DbgL => m["l"]
    @types.DbgS => m["s"]
    @types.DbgD => m["d"]
    @types.DbgPtr => m["ptr"]
    @types.DbgAgg(n, _) => m["agg:" + n]
  }
}

///|
// Emit .debug_info and .debug_loc for the recorded functions. `text_label`
// marks the end of .text (the compilation unit range).
pub fn emit_debug_info(
  gasloc : String,
  cu_name : String,
  comp_dir : String,
  apple : Bool,
  sb : StringBuilder,
) -> Unit {
  let fns = dbg_fns[0]
  if fns.length() == 0 {
    return
  }
  let producer = "qbe.mbt"
  let text_label = gasloc + "dbgend"
  let cu_low = fns[0].start_label
  // Distinct aggregate names, and whether any pointer type is needed.
  let aggs : Array[String] = []
  let mut uses_ptr = false
  for f in fns {
    for l in f.locs {
      match l.ty {
        @types.DbgPtr => uses_ptr = true
        @types.DbgAgg(n, _) => if !aggs.contains(n) { aggs.push(n) }
        _ => ()
      }
    }
  }
  // DIE offsets (relative to the start of .debug_info).
  let tmap : Map[String, Int] = Map([], capacity=8)
  let mut off = 11 // unit header without its length field
  off = off +
    (
      1 +
      producer.length() +
      1 +
      2 +
      cu_name.length() +
      1 +
      comp_dir.length() +
      1 +
      8 +
      8 +
      4
    )
  let base_names = ["int", "long int", "float", "double"]
  let base_keys = ["w", "l", "s", "d"]
  let base_sizes = [4, 8, 4, 8]
  let base_enc = [5, 5, 4, 4]
  for i in 0..<4 {
    tmap[base_keys[i]] = off
    off = off + 1 + base_names[i].length() + 1 + 1 + 1
  }
  if uses_ptr {
    tmap["ptr"] = off
    off = off + 1
  }
  for n in aggs {
    tmap["agg:" + n] = off
    off = off + 1 + n.length() + 1 + 1
  }
  let sub_off : Array[Int] = []
  for f in fns {
    sub_off.push(off)
    off = off + 1 + (f.name.length() + 1) + 8 + 8 + 2
    for l in f.locs {
      off = off + 1 + (l.name.length() + 1) + 4 + 4
    }
    off = off + 1
  }
  off = off + 1
  let unit_len = off - 4
  // .debug_loc offsets (one range per variable, in emission order).
  let loc_offs : Array[Int] = []
  let mut lo = 0
  for f in fns {
    for l in f.locs {
      loc_offs.push(lo)
      let e = loc_expr(l)
      // DWARF4 .debug_loc entries use a 2-byte expression length.
      lo = lo + 8 + 8 + 2 + e.length()
    }
  }
  // ---- .text end label ----
  sb.write_string(".text\n")
  sb.write_string(text_label)
  sb.write_string(":\n")
  // ---- .debug_abbrev ----
  if apple {
    sb.write_string(".section __DWARF,__debug_abbrev,regular,debug\n")
  } else {
    sb.write_string(".section .debug_abbrev,\"\",@progbits\n")
  }
  emit_bytes(sb, [
    1, 0x11, 1, 0x25, 0x08, 0x13, 0x05, 0x03, 0x08, 0x1b, 0x08, 0x11, 0x01, 0x12,
    0x01, 0x10, 0x17, 0, 0,
  ])
  emit_bytes(sb, [
    2, 0x2e, 1, 0x03, 0x08, 0x11, 0x01, 0x12, 0x01, 0x40, 0x18, 0, 0,
  ])
  emit_bytes(sb, [3, 0x34, 0, 0x03, 0x08, 0x49, 0x13, 0x02, 0x17, 0, 0])
  emit_bytes(sb, [4, 0x24, 0, 0x03, 0x08, 0x0b, 0x0b, 0x3e, 0x0b, 0, 0])
  emit_bytes(sb, [5, 0x0f, 0, 0, 0])
  emit_bytes(sb, [6, 0x13, 0, 0x03, 0x08, 0x0b, 0x0b, 0, 0])
  emit_bytes(sb, [0])
  // ---- .debug_info ----
  if apple {
    sb.write_string(".section __DWARF,__debug_info,regular,debug\n")
  } else {
    sb.write_string(".section .debug_info,\"\",@progbits\n")
  }
  sb.write_string("\t.long " + unit_len.to_string() + "\n")
  sb.write_string("\t.short 4\n\t.long 0\n\t.byte 8\n")
  sb.write_string("\t.byte 0x01\n")
  sb.write_string("\t.asciz \"" + producer + "\"\n")
  sb.write_string("\t.short 12\n")
  sb.write_string("\t.asciz \"" + cu_name + "\"\n")
  sb.write_string("\t.asciz \"" + comp_dir + "\"\n")
  sb.write_string("\t.quad " + cu_low + "\n")
  sb.write_string("\t.quad " + text_label + "\n")
  sb.write_string("\t.long 0\n")
  // base types
  for i in 0..<4 {
    sb.write_string("\t.byte 4\n")
    sb.write_string("\t.asciz \"" + base_names[i] + "\"\n")
    sb.write_string("\t.byte " + base_sizes[i].to_string() + "\n")
    sb.write_string("\t.byte " + base_enc[i].to_string() + "\n")
  }
  if uses_ptr {
    sb.write_string("\t.byte 5\n")
  }
  for n in aggs {
    let mut size = 0
    for f in fns {
      for l in f.locs {
        match l.ty {
          @types.DbgAgg(nn, sz) => if nn == n { size = sz }
          _ => ()
        }
      }
    }
    sb.write_string("\t.byte 6\n")
    sb.write_string("\t.asciz \"" + n + "\"\n")
    sb.write_string("\t.byte " + size.to_string() + "\n")
  }
  // subprograms + variables
  let mut loc_i = 0
  for f in fns {
    sb.write_string("\t.byte 2\n")
    sb.write_string("\t.asciz \"" + f.name + "\"\n")
    sb.write_string("\t.quad " + f.start_label + "\n")
    sb.write_string("\t.quad " + f.end_label + "\n")
    sb.write_string("\t.byte 1\n\t.byte 0x9c\n") // DW_OP_call_frame_cfa
    for l in f.locs {
      sb.write_string("\t.byte 3\n")
      sb.write_string("\t.asciz \"" + l.name + "\"\n")
      sb.write_string("\t.long " + type_off_for(tmap, l.ty).to_string() + "\n")
      sb.write_string("\t.long " + loc_offs[loc_i].to_string() + "\n")
      loc_i = loc_i + 1
    }
    sb.write_string("\t.byte 0\n")
  }
  sb.write_string("\t.byte 0\n")
  // ---- .debug_loc ----
  if apple {
    sb.write_string(".section __DWARF,__debug_loc,regular,debug\n")
  } else {
    sb.write_string(".section .debug_loc,\"\",@progbits\n")
  }
  for f in fns {
    for l in f.locs {
      sb.write_string("\t.quad " + f.start_label + "\n")
      sb.write_string("\t.quad " + f.end_label + "\n")
      let e = loc_expr(l)
      sb.write_string("\t.short " + e.length().to_string() + "\n")
      emit_bytes(sb, e)
    }
  }
  sb.write_string("\t.quad 0\n\t.quad 0\n")
}