// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module x64

pub struct Gen {
	out_name             string
mut:
	buf                  []byte
	sect_header_name_pos int
	offset               i64
	str_pos              []i64
	strings              []string // TODO use a map and don't duplicate strings
	file_size_pos        i64
	main_fn_addr         i64
	code_start_pos       i64 // location of the start of the assembly instructions
	fn_addr              map[string]i64
	// string_addr map[string]i64
}

enum Register {
	eax
	edi
	rax
	rdi
	rsi
	edx
	rdx
	r12
}

enum Size {
	_8
	_16
	_32
	_64
}

pub fn new_gen(out_name string) &Gen {
	return &Gen{
		sect_header_name_pos: 0
		buf: []
		out_name: out_name
	}
}

pub fn (g &Gen) pos() i64 {
	return g.buf.len
}

fn (g mut Gen) write8(n int) {
	// write 1 byte
	g.buf << byte(n)
}

fn (g mut Gen) write16(n int) {
	// write 2 bytes
	g.buf << byte(n)
	g.buf << byte(n>>8)
}

fn (g mut Gen) write32(n int) {
	// write 4 bytes
	g.buf << byte(n)
	g.buf << byte(n>>8)
	g.buf << byte(n>>16)
	g.buf << byte(n>>24)
}

fn (g mut Gen) write64(n i64) {
	// write 8 bytes
	g.buf << byte(n)
	g.buf << byte(n>>8)
	g.buf << byte(n>>16)
	g.buf << byte(n>>24)
	g.buf << byte(n>>32)
	g.buf << byte(n>>40)
	g.buf << byte(n>>48)
	g.buf << byte(n>>56)
}

fn (g mut Gen) write64_at(n i64, at i64) {
	// write 8 bytes
	g.buf[at] = byte(n)
	g.buf[at + 1] = byte(n>>8)
	g.buf[at + 2] = byte(n>>16)
	g.buf[at + 3] = byte(n>>24)
	g.buf[at + 4] = byte(n>>32)
	g.buf[at + 5] = byte(n>>40)
	g.buf[at + 6] = byte(n>>48)
	g.buf[at + 7] = byte(n>>56)
}

fn (g mut Gen) write_string(s string) {
	for c in s {
		g.write8(int(c))
	}
}

fn (g mut Gen) inc(reg Register) {
	g.write16(0xff49)
	match reg {
		.r12 {
			g.write8(0xc4)
		}
		else {
			panic('unhandled inc $reg')
		}}
}

fn (g mut Gen) cmp(reg Register, size Size, val i64) {
	g.write8(0x49)
	// Second byte depends on the size of the value
	match size {
		._8 {
			g.write8(0x83)
		}
		._32 {
			g.write8(0x81)
		}
		else {
			panic('unhandled cmp')
		}}
	// Third byte depends on the register being compared to
	match reg {
		.r12 {
			g.write8(0xfc)
		}
		else {
			panic('unhandled cmp')
		}}
	g.write8(int(val))
}

fn abs(a i64) i64 {
	return if a < 0 { -a } else { a }
}

fn (g mut Gen) jle(addr i64) {
	// Calculate the relative offset to jump to
	// (`addr` is absolute address)
	offset := 0xff - int(abs(addr - g.buf.len)) - 1
	g.write8(0x7e)
	g.write8(offset)
}

fn (g mut Gen) jl(addr i64) {
	offset := 0xff - int(abs(addr - g.buf.len)) - 1
	g.write8(0x7c)
	g.write8(offset)
}

fn (g &Gen) abs_to_rel_addr(addr i64) int {
	return int(abs(addr - g.buf.len)) - 1
}

fn (g mut Gen) jmp(addr i64) {
	offset := 0xff - g.abs_to_rel_addr(addr)
	g.write8(0xe9)
	g.write8(offset)
}

fn (g mut Gen) mov64(reg Register, val i64) {
	match reg {
		.rsi {
			g.write8(0x48)
			g.write8(0xbe)
		}
		else {
			println('unhandled mov $reg')
		}}
	g.write64(val)
}

fn (g mut Gen) call(addr int) {
	// rel := g.abs_to_rel_addr(addr)
	// rel := 0xffffffff - int(abs(addr - g.buf.len))-1
	println('call addr=$addr rel_addr=$addr pos=$g.buf.len')
	g.write8(0xe8)
	g.write32(addr)
}

fn (g mut Gen) syscall() {
	// g.write(0x050f)
	g.write8(0x0f)
	g.write8(0x05)
}

pub fn (g mut Gen) ret() {
	g.write8(0xc3)
}

// returns label's relative address
pub fn (g mut Gen) gen_loop_start(from int) int {
	g.mov(.r12, from)
	label := g.buf.len
	g.inc(.r12)
	return label
}

pub fn (g mut Gen) gen_loop_end(to int, label int) {
	g.cmp(.r12, ._8, to)
	g.jl(label)
}

pub fn (g mut Gen) save_main_fn_addr() {
	g.main_fn_addr = g.buf.len
}

pub fn (g mut Gen) gen_print(s string) {
	g.strings << s + '\n'
	// g.string_addr[s] = str_pos
	g.mov(.eax, 1)
	g.mov(.edi, 1)
	str_pos := g.buf.len + 2
	g.str_pos << str_pos
	g.mov64(.rsi, 0) // segment_start +  0x9f) // str pos // PLACEHOLDER
	g.mov(.edx, s.len + 1) // len
	g.syscall()
}

pub fn (g mut Gen) gen_exit() {
	// Return 0
	g.mov(.edi, 0) // ret value
	g.mov(.eax, 60)
	g.syscall()
}

fn (g mut Gen) mov(reg Register, val int) {
	match reg {
		.eax {
			g.write8(0xb8)
		}
		.edi {
			g.write8(0xbf)
		}
		.edx {
			g.write8(0xba)
		}
		.rsi {
			g.write8(0x48)
			g.write8(0xbe)
		}
		.r12 {
			g.write8(0x41)
			g.write8(0xbc) // r11 is 0xbb etc
		}
		else {
			panic('unhandled mov $reg')
		}}
	g.write32(val)
}

pub fn (g mut Gen) register_function_address(name string) {
	addr := g.pos()
	println('reg fn addr $name $addr')
	g.fn_addr[name] = addr
}

pub fn (g mut Gen) call_fn(name string) {
	if !name.contains('__') {
		return
	}
	addr := g.fn_addr[name]
	g.call(int(addr))
	println('call $name $addr')
}

