package regalloc

import (
	"fmt"
	"strings"
)

// Following mock types are used for testing.
type (
	// mockFunction implements Function.
	mockFunction struct {
		iter            int
		blocks          []*mockBlock
		befores, afters []storeOrReloadInfo
		lnfRoots        []*mockBlock
	}

	storeOrReloadInfo struct {
		reload bool
		v      VReg
		instr  Instr
	}

	// mockBlock implements Block.
	mockBlock struct {
		id             int32
		instructions   []*mockInstr
		preds, succs   []*mockBlock
		_preds, _succs []Block
		iter           int
		_entry         bool
		_loop          bool
		lnfChildren    []*mockBlock
		blockParams    []VReg
	}

	// mockInstr implements Instr.
	mockInstr struct {
		next, prev                 *mockInstr
		defs, uses                 []VReg
		isCopy, isCall, isIndirect bool
	}
)

func (m *mockFunction) LowestCommonAncestor(blk1, blk2 Block) Block { panic("TODO") }

func (m *mockFunction) Idom(blk Block) Block { panic("TODO") }

func (m *mockFunction) SwapBefore(x1, x2, tmp VReg, instr Instr) { panic("TODO") }

func (m *mockFunction) InsertMoveBefore(dst, src VReg, instr Instr) { panic("TODO") }

func newMockFunction(blocks ...*mockBlock) *mockFunction {
	return &mockFunction{blocks: blocks}
}

func (m *mockFunction) loopNestingForestRoots(blocks ...*mockBlock) {
	m.lnfRoots = blocks
}

func newMockBlock(id int32, instructions ...*mockInstr) *mockBlock {
	if len(instructions) > 0 {
		instructions[0].prev = nil
		for i := 1; i < len(instructions); i++ {
			instructions[i].prev = instructions[i-1]
			instructions[i-1].next = instructions[i]
		}
		instructions[len(instructions)-1].next = nil
	}
	return &mockBlock{id: id, instructions: instructions}
}

func newMockInstr() *mockInstr {
	return &mockInstr{}
}

// String implements fmt.Stringer for debugging.
func (m *mockFunction) String() string {
	var block []string
	for _, b := range m.blocks {
		block = append(block, "\t"+b.String())
	}
	return fmt.Sprintf("mockFunction:\n%s", strings.Join(block, ",\n"))
}

// String implements fmt.Stringer for debugging.
func (m *mockInstr) String() string {
	return fmt.Sprintf("mockInstr{defs=%v, uses=%v}", m.defs, m.uses)
}

// String implements fmt.Stringer for debugging.
func (m *mockBlock) String() string {
	var preds []int32
	for _, p := range m.preds {
		preds = append(preds, p.id)
	}
	return fmt.Sprintf("mockBlock{\n\tid=%v,\n\tinstructions=%v,\n\tpreds=%v,\n}", m.id, preds, m.instructions)
}

func (m *mockBlock) addPred(b *mockBlock) {
	m.preds = append(m.preds, b)
	m._preds = append(m._preds, b)
	b._succs = append(b._succs, m)
	b.succs = append(b.succs, m)
}

func (m *mockInstr) use(uses ...VReg) *mockInstr {
	m.uses = uses
	return m
}

func (m *mockInstr) def(defs ...VReg) *mockInstr {
	m.defs = defs
	return m
}

func (m *mockBlock) loop(children ...*mockBlock) *mockBlock {
	m._loop = true
	m.lnfChildren = children
	return m
}

func (m *mockBlock) entry() *mockBlock {
	m._entry = true
	return m
}

func (m *mockInstr) asCopy() *mockInstr {
	m.isCopy = true
	return m
}

func (m *mockInstr) asCall() *mockInstr { //nolint:unused
	m.isCall = true
	return m
}

func (m *mockInstr) asIndirectCall() *mockInstr { //nolint:unused
	m.isIndirect = true
	return m
}

// StoreRegisterAfter implements Function.StoreRegisterAfter.
func (m *mockFunction) StoreRegisterAfter(v VReg, instr Instr) {
	m.afters = append(m.afters, storeOrReloadInfo{false, v, instr})
}

// ReloadRegisterBefore implements Function.ReloadRegisterBefore.
func (m *mockFunction) ReloadRegisterBefore(v VReg, instr Instr) {
	m.befores = append(m.befores, storeOrReloadInfo{true, v, instr})
}

// StoreRegisterBefore implements Function.StoreRegisterBefore.
func (m *mockFunction) StoreRegisterBefore(v VReg, instr Instr) {
	m.befores = append(m.befores, storeOrReloadInfo{false, v, instr})
}

// ReloadRegisterAfter implements Function.ReloadRegisterAfter.
func (m *mockFunction) ReloadRegisterAfter(v VReg, instr Instr) {
	m.afters = append(m.afters, storeOrReloadInfo{true, v, instr})
}

// ClobberedRegisters implements Function.ClobberedRegisters.
func (m *mockFunction) ClobberedRegisters(regs []VReg) {
	// TODO implement me
	panic("implement me")
}

// Done implements Function.Done.
func (m *mockFunction) Done() {}

// PostOrderBlockIteratorBegin implements Block.
func (m *mockFunction) PostOrderBlockIteratorBegin() Block {
	m.iter = 1
	l := len(m.blocks)
	return m.blocks[l-1]
}

// PostOrderBlockIteratorNext implements Block.
func (m *mockFunction) PostOrderBlockIteratorNext() Block {
	if m.iter == len(m.blocks) {
		return nil
	}
	l := len(m.blocks)
	ret := m.blocks[l-m.iter-1]
	m.iter++
	return ret
}

// ReversePostOrderBlockIteratorBegin implements Block.
func (m *mockFunction) ReversePostOrderBlockIteratorBegin() Block {
	m.iter = 1
	return m.blocks[0]
}

// ReversePostOrderBlockIteratorNext implements Block.
func (m *mockFunction) ReversePostOrderBlockIteratorNext() Block {
	if m.iter == len(m.blocks) {
		return nil
	}
	ret := m.blocks[m.iter]
	m.iter++
	return ret
}

// ID implements Block.
func (m *mockBlock) ID() int32 {
	return m.id
}

// InstrIteratorBegin implements Block.
func (m *mockBlock) InstrIteratorBegin() Instr {
	if len(m.instructions) == 0 {
		return nil
	}
	m.iter = 1
	return m.instructions[0]
}

// InstrIteratorNext implements Block.
func (m *mockBlock) InstrIteratorNext() Instr {
	if m.iter == len(m.instructions) {
		return nil
	}
	ret := m.instructions[m.iter]
	m.iter++
	return ret
}

// InstrRevIteratorBegin implements Block.
func (m *mockBlock) InstrRevIteratorBegin() Instr {
	if len(m.instructions) == 0 {
		return nil
	}
	m.iter = len(m.instructions)
	return m.InstrRevIteratorNext()
}

// InstrRevIteratorNext implements Block.
func (m *mockBlock) InstrRevIteratorNext() Instr {
	m.iter--
	if m.iter < 0 {
		return nil
	}
	return m.instructions[m.iter]
}

// Preds implements Block.
func (m *mockBlock) Preds() int {
	return len(m._preds)
}

// BlockParams implements Block.
func (m *mockBlock) BlockParams(ret *[]VReg) []VReg {
	*ret = append((*ret)[:0], m.blockParams...)
	return *ret
}

func (m *mockBlock) blockParam(v VReg) {
	m.blockParams = append(m.blockParams, v)
}

// Pred implements Instr.
func (m *mockBlock) Pred(i int) Block { return m._preds[i] }

// Defs implements Instr.
func (m *mockInstr) Defs(ret *[]VReg) []VReg {
	*ret = append((*ret)[:0], m.defs...)
	return *ret
}

// AddedBeforeRegAlloc implements Instr.
func (m *mockInstr) AddedBeforeRegAlloc() bool { return true }

// Uses implements Instr.
func (m *mockInstr) Uses(ret *[]VReg) []VReg {
	*ret = append((*ret)[:0], m.uses...)
	return *ret
}

// IsCopy implements Instr.
func (m *mockInstr) IsCopy() bool { return m.isCopy }

// IsCall implements Instr.
func (m *mockInstr) IsCall() bool { return m.isCall }

// IsIndirectCall implements Instr.
func (m *mockInstr) IsIndirectCall() bool { return m.isIndirect }

// IsReturn implements Instr.
func (m *mockInstr) IsReturn() bool { return false }

// Next implements Instr.
func (m *mockInstr) Next() Instr { return m.next }

// Prev implements Instr.
func (m *mockInstr) Prev() Instr { return m.prev }

// Entry implements Entry.
func (m *mockBlock) Entry() bool { return m._entry }

// AssignUses implements Instr.
func (m *mockInstr) AssignUse(index int, reg VReg) {
	if index >= len(m.uses) {
		m.uses = append(m.uses, make([]VReg, 5)...)
	}
	m.uses[index] = reg
}

// AssignDef implements Instr.
func (m *mockInstr) AssignDef(reg VReg) {
	m.defs = []VReg{reg}
}

var (
	_ Function = (*mockFunction)(nil)
	_ Block    = (*mockBlock)(nil)
	_ Instr    = (*mockInstr)(nil)
)

func (m *mockFunction) LoopNestingForestRoots() int {
	return len(m.lnfRoots)
}

func (m *mockFunction) LoopNestingForestRoot(i int) Block {
	return m.lnfRoots[i]
}

func (m *mockBlock) LoopHeader() bool {
	return m._loop
}

func (m *mockBlock) Succs() int {
	return len(m.succs)
}

func (m *mockBlock) Succ(i int) Block {
	return m.succs[i]
}

func (m *mockBlock) LoopNestingForestChildren() int {
	return len(m.lnfChildren)
}

func (m *mockBlock) LoopNestingForestChild(i int) Block {
	return m.lnfChildren[i]
}

func (m *mockBlock) BeginInstr() Instr {
	if len(m.instructions) == 0 {
		return nil
	}
	return m.instructions[0]
}

func (m *mockBlock) EndInstr() Instr {
	if len(m.instructions) == 0 {
		return nil
	}
	return m.instructions[len(m.instructions)-1]
}

func (m *mockBlock) LastInstrForInsertion() Instr {
	if len(m.instructions) == 0 {
		return nil
	}
	return m.instructions[len(m.instructions)-1]
}

func (m *mockBlock) FirstInstr() Instr {
	return m.instructions[0]
}
