package asm

//go:generate stringer -output alu_string.go -type=Source,Endianness,ALUOp

// Source of ALU / ALU64 / Branch operations
//
//    msb      lsb
//    +----+-+---+
//    |op  |S|cls|
//    +----+-+---+
type Source uint8

const sourceMask OpCode = 0x08

// Source bitmask
const (
	// InvalidSource is returned by getters when invoked
	// on non ALU / branch OpCodes.
	InvalidSource Source = 0xff
	// ImmSource src is from constant
	ImmSource Source = 0x00
	// RegSource src is from register
	RegSource Source = 0x08
)

// The Endianness of a byte swap instruction.
type Endianness uint8

const endianMask = sourceMask

// Endian flags
const (
	InvalidEndian Endianness = 0xff
	// Convert to little endian
	LE Endianness = 0x00
	// Convert to big endian
	BE Endianness = 0x08
)

// ALUOp are ALU / ALU64 operations
//
//    msb      lsb
//    +----+-+---+
//    |OP  |s|cls|
//    +----+-+---+
type ALUOp uint8

const aluMask OpCode = 0xf0

const (
	// InvalidALUOp is returned by getters when invoked
	// on non ALU OpCodes
	InvalidALUOp ALUOp = 0xff
	// Add - addition
	Add ALUOp = 0x00
	// Sub - subtraction
	Sub ALUOp = 0x10
	// Mul - multiplication
	Mul ALUOp = 0x20
	// Div - division
	Div ALUOp = 0x30
	// Or - bitwise or
	Or ALUOp = 0x40
	// And - bitwise and
	And ALUOp = 0x50
	// LSh - bitwise shift left
	LSh ALUOp = 0x60
	// RSh - bitwise shift right
	RSh ALUOp = 0x70
	// Neg - sign/unsign signing bit
	Neg ALUOp = 0x80
	// Mod - modulo
	Mod ALUOp = 0x90
	// Xor - bitwise xor
	Xor ALUOp = 0xa0
	// Mov - move value from one place to another
	Mov ALUOp = 0xb0
	// ArSh - arithmatic shift
	ArSh ALUOp = 0xc0
	// Swap - endian conversions
	Swap ALUOp = 0xd0
)

// HostTo converts from host to another endianness.
func HostTo(endian Endianness, dst Register, size Size) Instruction {
	var imm int64
	switch size {
	case Half:
		imm = 16
	case Word:
		imm = 32
	case DWord:
		imm = 64
	default:
		return Instruction{OpCode: InvalidOpCode}
	}

	return Instruction{
		OpCode:   OpCode(ALUClass).SetALUOp(Swap).SetSource(Source(endian)),
		Dst:      dst,
		Constant: imm,
	}
}

// Op returns the OpCode for an ALU operation with a given source.
func (op ALUOp) Op(source Source) OpCode {
	return OpCode(ALU64Class).SetALUOp(op).SetSource(source)
}

// Reg emits `dst (op) src`.
func (op ALUOp) Reg(dst, src Register) Instruction {
	return Instruction{
		OpCode: op.Op(RegSource),
		Dst:    dst,
		Src:    src,
	}
}

// Imm emits `dst (op) value`.
func (op ALUOp) Imm(dst Register, value int32) Instruction {
	return Instruction{
		OpCode:   op.Op(ImmSource),
		Dst:      dst,
		Constant: int64(value),
	}
}

// Op32 returns the OpCode for a 32-bit ALU operation with a given source.
func (op ALUOp) Op32(source Source) OpCode {
	return OpCode(ALUClass).SetALUOp(op).SetSource(source)
}

// Reg32 emits `dst (op) src`, zeroing the upper 32 bit of dst.
func (op ALUOp) Reg32(dst, src Register) Instruction {
	return Instruction{
		OpCode: op.Op32(RegSource),
		Dst:    dst,
		Src:    src,
	}
}

// Imm32 emits `dst (op) value`, zeroing the upper 32 bit of dst.
func (op ALUOp) Imm32(dst Register, value int32) Instruction {
	return Instruction{
		OpCode:   op.Op32(ImmSource),
		Dst:      dst,
		Constant: int64(value),
	}
}
