package ir

import "github.com/marzeq/qk/types"

type ValueID uint32
type SlotID uint32
type BlockID uint32

type OperandKind uint8

const (
	OperandValue OperandKind = iota
	OperandIntConst
	OperandFloatConst
	OperandBoolConst
	OperandNullConst
)

type Operand struct {
	Kind OperandKind
	Type types.Type

	Value ValueID

	IntValue   string
	FloatValue string
	BoolValue  bool
}

func ValueOperand(id ValueID, ty types.Type) Operand {
	return Operand{Kind: OperandValue, Value: id, Type: ty}
}

func IntConstOperand(value string, ty types.Type) Operand {
	return Operand{Kind: OperandIntConst, IntValue: value, Type: ty}
}

func FloatConstOperand(value string, ty types.Type) Operand {
	return Operand{Kind: OperandFloatConst, FloatValue: value, Type: ty}
}

func BoolConstOperand(value bool) Operand {
	return Operand{Kind: OperandBoolConst, BoolValue: value, Type: types.PrimitiveBool}
}

func NullConstOperand(ty types.Type) Operand {
	return Operand{Kind: OperandNullConst, Type: ty}
}

type Slot struct {
	ID   SlotID
	Type types.Type
	Name string
}

type Block struct {
	ID    BlockID
	Name  string
	Instr []Instr
}

type Parameter struct {
	Index int
	Name  string
	Type  types.Type
	Slot  SlotID
}

type FunctionSignature struct {
	ParamTypes []types.Type
	ReturnType types.Type
	Variadic   bool
}

type Function struct {
	Name       string
	Signature  FunctionSignature
	Parameters []Parameter
	Slots      []Slot
	Values     map[ValueID]types.Type

	Blocks []*Block
	Entry  BlockID

	nextValue ValueID
	nextSlot  SlotID
	nextBlock BlockID
}

func NewFunction(name string) *Function {
	return &Function{
		Name:   name,
		Values: make(map[ValueID]types.Type),
	}
}

func (f *Function) NewValue() ValueID {
	return f.NewValueOfType(nil)
}

func (f *Function) NewValueOfType(ty types.Type) ValueID {
	f.nextValue++
	id := f.nextValue
	f.Values[id] = ty
	return id
}

func (f *Function) ValueType(id ValueID) types.Type {
	return f.Values[id]
}

func (f *Function) NewSlot(ty types.Type, name string) SlotID {
	f.nextSlot++
	id := f.nextSlot
	f.Slots = append(f.Slots, Slot{ID: id, Type: ty, Name: name})
	return id
}

func (f *Function) NewBlock(name string) *Block {
	f.nextBlock++
	b := &Block{ID: f.nextBlock, Name: name}
	f.Blocks = append(f.Blocks, b)
	return b
}

func (f *Function) AddParameter(name string, ty types.Type, slot SlotID) {
	f.Parameters = append(f.Parameters, Parameter{
		Index: len(f.Parameters),
		Name:  name,
		Type:  ty,
		Slot:  slot,
	})
}

type Module struct {
	Functions []*Function
}

func (m *Module) AddFunction(fn *Function) {
	m.Functions = append(m.Functions, fn)
}

type Instr interface {
	isInstr()
}

type Add struct {
	Dest        ValueID
	Left, Right Operand
}

func (Add) isInstr() {}

type Sub struct {
	Dest        ValueID
	Left, Right Operand
}

func (Sub) isInstr() {}

type Mul struct {
	Dest        ValueID
	Left, Right Operand
}

func (Mul) isInstr() {}

type Div struct {
	Dest        ValueID
	Left, Right Operand
}

func (Div) isInstr() {}

type CmpEq struct {
	Dest        ValueID
	Left, Right Operand
}

func (CmpEq) isInstr() {}

type CmpNe struct {
	Dest        ValueID
	Left, Right Operand
}

func (CmpNe) isInstr() {}

type CmpLt struct {
	Dest        ValueID
	Left, Right Operand
}

func (CmpLt) isInstr() {}

type CmpLe struct {
	Dest        ValueID
	Left, Right Operand
}

func (CmpLe) isInstr() {}

type CmpGt struct {
	Dest        ValueID
	Left, Right Operand
}

func (CmpGt) isInstr() {}

type CmpGe struct {
	Dest        ValueID
	Left, Right Operand
}

func (CmpGe) isInstr() {}

type Alloca struct {
	Slot SlotID
}

func (Alloca) isInstr() {}

type Load struct {
	Dest ValueID
	Slot SlotID
}

func (Load) isInstr() {}

type Store struct {
	Slot  SlotID
	Value Operand
}

func (Store) isInstr() {}

type LoadPtr struct {
	Dest ValueID
	Ptr  Operand
}

func (LoadPtr) isInstr() {}

type StorePtr struct {
	Ptr   Operand
	Value Operand
}

func (StorePtr) isInstr() {}

type AddressOf struct {
	Dest ValueID
	Slot SlotID
}

func (AddressOf) isInstr() {}

type FieldAddress struct {
	Dest  ValueID
	Base  Operand
	Field string
}

func (FieldAddress) isInstr() {}

type Call struct {
	Dest      ValueID
	Name      string
	Args      []Operand
	Signature FunctionSignature
}

func (Call) isInstr() {}

type Jump struct {
	Target BlockID
}

func (Jump) isInstr() {}

type Branch struct {
	Cond       Operand
	Then, Else BlockID
}

func (Branch) isInstr() {}

type Return struct {
	HasValue bool
	Value    Operand
}

func (Return) isInstr() {}

type Cast struct {
	Dest ValueID
	From Operand
	To   types.Type
}

func (Cast) isInstr() {}