// assembler.v — assembles VuurRaaf assembly (.vasm) into VROBJ object files.
//
// Assembly syntax:
//   ; comment
//   .global main            ; export the label as a linkable symbol
//   main:                   ; label definition (local unless .global)
//       push_int 42
//       push_str "hello"
//       call helper 1       ; call <symbol-or-label> <argc>
//       println
//       halt
//
// Jumps (jmp/jz/jnz) target local labels and are resolved here; calls to
// exported symbols become relocations resolved by the linker.
module assembler

import os
import obj

pub fn assemble(src string) !obj.Obj {
	lines := src.split_into_lines()
	// pass 1: collect labels, globals, and code offsets
	mut labels := map[string]int{}
	mut globals := map[string]bool{}
	mut offset := 0
	for raw in lines {
		line := clean(raw)
		if line.len == 0 {
			continue
		}
		if line.starts_with('.global') {
			name := line.all_after('.global').trim_space()
			if name.len == 0 {
				return error('`.global` needs a name')
			}
			globals[name] = true
			continue
		}
		if line.ends_with(':') {
			name := line[..line.len - 1].trim_space()
			if name in labels {
				return error('duplicate label "${name}"')
			}
			labels[name] = offset
			continue
		}
		offset += instr_len(line)!
	}
	// pass 2: emit code
	mut o := obj.Obj{}
	for raw in lines {
		line := clean(raw)
		if line.len == 0 || line.starts_with('.global') || line.ends_with(':') {
			continue
		}
		parts := split_line(line)!
		op := parts[0]
		arg := if parts.len > 1 { parts[1] } else { '' }
		match op {
			'halt' {
				o.code << u8(0)
			}
			'push_int' {
				val := parse_int(arg, 'push_int')!
				o.code << u8(1)
				o.code << obj.encode_i64(val)
			}
			'push_str' {
				s := unquote(arg)!
				o.code << u8(2)
				o.code << obj.encode_i64(0) // placeholder — rebased by the linker
				o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: s, kind: 1 }
			}
			'load' {
				o.code << u8(3)
				o.code << obj.encode_i64(parse_int(arg, 'load')!)
			}
			'store' {
				o.code << u8(4)
				o.code << obj.encode_i64(parse_int(arg, 'store')!)
			}
			'pop' {
				o.code << u8(5)
			}
			'dup' {
				o.code << u8(6)
			}
			'add' {
				o.code << u8(7)
			}
			'sub' {
				o.code << u8(8)
			}
			'mul' {
				o.code << u8(9)
			}
			'div' {
				o.code << u8(10)
			}
			'mod' {
				o.code << u8(11)
			}
			'neg' {
				o.code << u8(12)
			}
			'eq' {
				o.code << u8(13)
			}
			'ne' {
				o.code << u8(14)
			}
			'lt' {
				o.code << u8(15)
			}
			'le' {
				o.code << u8(16)
			}
			'gt' {
				o.code << u8(17)
			}
			'ge' {
				o.code << u8(18)
			}
			'and' {
				o.code << u8(19)
			}
			'or' {
				o.code << u8(20)
			}
			'not' {
				o.code << u8(21)
			}
			'jmp' {
				o.code << u8(22)
				o.code << obj.encode_i64(i64(label(labels, arg)!))
			}
			'jz' {
				o.code << u8(23)
				o.code << obj.encode_i64(i64(label(labels, arg)!))
			}
			'jnz' {
				o.code << u8(24)
				o.code << obj.encode_i64(i64(label(labels, arg)!))
			}
			'call' {
				o.code << u8(25)
				o.code << obj.encode_i64(0) // placeholder for the target
				if arg in labels && arg !in globals {
					obj.patch_i64(mut o.code, u32(o.code.len) - 8, i64(labels[arg]))
				} else {
					// reference to an exported symbol — resolved at link time
					o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: arg, kind: 0 }
				}
				argc := if parts.len > 2 { parse_int(parts[2], 'call argc')! } else { i64(0) }
				o.code << obj.encode_i64(argc)
			}
			'ret' {
				o.code << u8(26)
			}
			'retv' {
				o.code << u8(27)
			}
			'print' {
				o.code << u8(28)
			}
			'println' {
				o.code << u8(29)
			}
			'assert' {
				o.code << u8(30)
			}
			'enter' {
				o.code << u8(31)
				o.code << obj.encode_i64(parse_int(arg, 'enter')!)
			}
			'mkarray' {
				o.code << u8(32)
				o.code << obj.encode_i64(parse_int(arg, 'mkarray')!)
			}
			'aget' {
				o.code << u8(33)
			}
			'aset' {
				o.code << u8(34)
			}
			'alen' {
				o.code << u8(35)
			}
			'apush' {
				o.code << u8(36)
			}
			'mkstruct' {
				o.code << u8(37)
				o.code << obj.encode_i64(parse_int(arg, 'mkstruct')!)
			}
			'sget' {
				// sget "name"  →  push_str "name"; sget
				s := unquote(arg)!
				o.code << u8(2)
				o.code << obj.encode_i64(0) // placeholder — rebased by the linker
				o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: s, kind: 1 }
				o.code << u8(38)
			}
			'sset' {
				// sset "name"  →  push_str "name"; sset
				s := unquote(arg)!
				o.code << u8(2)
				o.code << obj.encode_i64(0) // placeholder — rebased by the linker
				o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: s, kind: 1 }
				o.code << u8(39)
			}
			else {
				return error('unknown instruction "${op}"')
			}
		}
	}
	// export the globals as symbols
	for name in globals.keys() {
		entry := labels[name] or { return error('.global "${name}" is not a defined label') }
		o.symbols << obj.Symbol{ name: name, entry: entry }
	}
	return o
}

pub fn assemble_file(path string) !obj.Obj {
	src := os.read_file(path)!
	return assemble(src)!
}

// ---------------------------------------------------------------------------

fn clean(line string) string {
	// strip ';' comments and trim
	if idx := line.index(';') {
		return line[..idx].trim_space()
	}
	return line.trim_space()
}

// split_line splits an assembly line into whitespace-separated fields while
// keeping a double-quoted string (which may contain spaces) as a single field.
fn split_line(line string) ![]string {
	mut parts := []string{}
	mut i := 0
	for i < line.len {
		for i < line.len && (line[i] == ` ` || line[i] == `\t`) {
			i++
		}
		if i >= line.len {
			break
		}
		if line[i] == `"` {
			// scan to the closing quote
			start := i
			i++
			for i < line.len && line[i] != `"` {
				i++
			}
			i++
			parts << line[start..if i <= line.len { i } else { line.len }]
		} else {
			start := i
			for i < line.len && line[i] != ` ` && line[i] != `\t` {
				i++
			}
			parts << line[start..i]
		}
	}
	return parts
}

fn instr_len(line string) !int {
	parts := split_line(line)!
	op := parts[0]
	match op {
		'halt', 'pop', 'dup', 'add', 'sub', 'mul', 'div', 'mod', 'neg', 'eq', 'ne', 'lt', 'le',
		'gt', 'ge', 'and', 'or', 'not', 'ret', 'retv', 'print', 'println', 'assert', 'aget',
		'aset', 'alen', 'apush' {
			return 1
		}
		'sget', 'sset' {
			return 10 // push_str (9) + the opcode (1)
		}
		'push_int', 'push_str', 'load', 'store', 'jmp', 'jz', 'jnz', 'enter', 'mkarray',
		'mkstruct' {
			return 9
		}
		'call' {
			return 17 // opcode + target(8) + argc(8)
		}
		else {
			return error('unknown instruction "${op}"')
		}
	}
}

fn label(labels map[string]int, name string) !int {
	return labels[name] or { return error('unknown label "${name}"') }
}

fn parse_int(s string, what string) !i64 {
	if s.len == 0 {
		return error('${what}: bad integer "${s}"')
	}
	start := if s[0] == `-` { 1 } else { 0 }
	if start >= s.len {
		return error('${what}: bad integer "${s}"')
	}
	for i in start..s.len {
		if s[i] < `0` || s[i] > `9` {
			return error('${what}: bad integer "${s}"')
		}
	}
	return s.i64()
}

fn unquote(s string) !string {
	if s.len < 2 || s[0] != `"` || s[s.len - 1] != `"` {
		return error('expected a string literal, got "${s}"')
	}
	inner := s[1..s.len - 1]
	mut out := ''
	mut i := 0
	for i < inner.len {
		c := inner[i]
		if c == `\\` && i + 1 < inner.len {
			i++
			match inner[i] {
				`n` {
					out += '\n'
				}
				`t` {
					out += '\t'
				}
				`"` {
					out += '"'
				}
				`\\` {
					out += '\\'
				}
				else {
					out += '\\${inner[i].ascii_str()}'
				}
			}
		} else {
			out += c.ascii_str()
		}
		i++
	}
	return out
}

