package arm

import (
	"encoding/binary"

	"git.urbach.dev/cli/q/src/cpu"
)

// MoveRegisterNumber moves a number into the given register.
func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
	instruction, encodable := MoveRegisterNumberSI(destination, number)

	if encodable {
		return binary.LittleEndian.AppendUint32(code, instruction)
	}

	return MoveRegisterNumberMI(code, destination, number)
}

// MoveRegisterNumberMI moves a number into the given register using movz and a series of movk instructions.
func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []byte {
	movz := MoveZero(destination, 0, uint16(number))
	code = binary.LittleEndian.AppendUint32(code, movz)
	num := uint64(number)
	halfword := 1

	for {
		num >>= 16

		if num == 0 {
			return code
		}

		movk := MoveKeep(destination, halfword, uint16(num))
		code = binary.LittleEndian.AppendUint32(code, movk)
		halfword++
	}
}

// MoveRegisterNumberSI moves a number into the given register using a single instruction.
func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) {
	if cpu.SizeInt(number) <= 2 {
		if number < 0 {
			return MoveInvertedNumber(destination, uint16(^number), 0), true
		}

		return MoveZero(destination, 0, uint16(number)), true
	}

	if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && cpu.SizeInt(number>>48) <= 2 {
		return MoveInvertedNumber(destination, uint16((^number)>>48), 3), true
	}

	code, encodable := MoveBitmaskNumber(destination, number)

	if encodable {
		return code, true
	}

	if (number&0xFFFFFFFF == 0xFFFFFFFF) && cpu.SizeInt(number>>32) <= 2 {
		return MoveInvertedNumber(destination, uint16((^number)>>32), 2), true
	}

	if (number&0xFFFF == 0xFFFF) && cpu.SizeInt(number>>16) <= 2 {
		return MoveInvertedNumber(destination, uint16((^number)>>16), 1), true
	}

	return 0, false
}

// MoveRegisterRegister copies a register to another register.
func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 {
	if source == SP || destination == SP {
		code, _ := AddRegisterNumber(destination, source, 0)
		return code
	}

	return OrRegisterRegister(destination, ZR, source)
}

// MoveBitmaskNumber moves a bitmask immediate value to a register.
func MoveBitmaskNumber(destination cpu.Register, number int) (uint32, bool) {
	return OrRegisterNumber(destination, ZR, number)
}

// MoveInvertedNumber moves an inverted 16-bit immediate value to a register.
func MoveInvertedNumber(destination cpu.Register, number uint16, shift uint32) uint32 {
	return 0b100100101<<23 | shift<<21 | regImm(destination, number)
}

// MoveKeep moves a 16-bit integer into the given register and keeps all other bits.
func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 {
	return 0b111100101<<23 | regImmHw(destination, halfword, number)
}

// MoveZero moves a 16-bit integer into the given register and clears all other bits to zero.
func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 {
	return 0b110100101<<23 | regImmHw(destination, halfword, number)
}