package asm

//go:generate stringer -output jump_string.go -type=JumpOp

// JumpOp affect control flow.
//
//    msb      lsb
//    +----+-+---+
//    |OP  |s|cls|
//    +----+-+---+
type JumpOp uint8

const jumpMask OpCode = aluMask

const (
	// InvalidJumpOp is returned by getters when invoked
	// on non branch OpCodes
	InvalidJumpOp JumpOp = 0xff
	// Ja jumps by offset unconditionally
	Ja JumpOp = 0x00
	// JEq jumps by offset if r == imm
	JEq JumpOp = 0x10
	// JGT jumps by offset if r > imm
	JGT JumpOp = 0x20
	// JGE jumps by offset if r >= imm
	JGE JumpOp = 0x30
	// JSet jumps by offset if r & imm
	JSet JumpOp = 0x40
	// JNE jumps by offset if r != imm
	JNE JumpOp = 0x50
	// JSGT jumps by offset if signed r > signed imm
	JSGT JumpOp = 0x60
	// JSGE jumps by offset if signed r >= signed imm
	JSGE JumpOp = 0x70
	// Call builtin or user defined function from imm
	Call JumpOp = 0x80
	// Exit ends execution, with value in r0
	Exit JumpOp = 0x90
	// JLT jumps by offset if r < imm
	JLT JumpOp = 0xa0
	// JLE jumps by offset if r <= imm
	JLE JumpOp = 0xb0
	// JSLT jumps by offset if signed r < signed imm
	JSLT JumpOp = 0xc0
	// JSLE jumps by offset if signed r <= signed imm
	JSLE JumpOp = 0xd0
)

// Return emits an exit instruction.
//
// Requires a return value in R0.
func Return() Instruction {
	return Instruction{
		OpCode: OpCode(JumpClass).SetJumpOp(Exit),
	}
}

// Op returns the OpCode for a given jump source.
func (op JumpOp) Op(source Source) OpCode {
	return OpCode(JumpClass).SetJumpOp(op).SetSource(source)
}

// Imm compares dst to value, and adjusts PC by offset if the condition is fulfilled.
func (op JumpOp) Imm(dst Register, value int32, label string) Instruction {
	if op == Exit || op == Call || op == Ja {
		return Instruction{OpCode: InvalidOpCode}
	}

	return Instruction{
		OpCode:    OpCode(JumpClass).SetJumpOp(op).SetSource(ImmSource),
		Dst:       dst,
		Offset:    -1,
		Constant:  int64(value),
		Reference: label,
	}
}

// Reg compares dst to src, and adjusts PC by offset if the condition is fulfilled.
func (op JumpOp) Reg(dst, src Register, label string) Instruction {
	if op == Exit || op == Call || op == Ja {
		return Instruction{OpCode: InvalidOpCode}
	}

	return Instruction{
		OpCode:    OpCode(JumpClass).SetJumpOp(op).SetSource(RegSource),
		Dst:       dst,
		Src:       src,
		Offset:    -1,
		Reference: label,
	}
}

// Label adjusts PC to the address of the label.
func (op JumpOp) Label(label string) Instruction {
	if op == Call {
		return Instruction{
			OpCode:    OpCode(JumpClass).SetJumpOp(Call),
			Src:       R1,
			Constant:  -1,
			Reference: label,
		}
	}

	return Instruction{
		OpCode:    OpCode(JumpClass).SetJumpOp(op),
		Offset:    -1,
		Reference: label,
	}
}
