package asm

import (
	"encoding/binary"
	"fmt"
)

// BaseAssemblerImpl includes code common to all architectures.
//
// Note: When possible, add code here instead of in architecture-specific files to reduce drift:
// As this is internal, exporting symbols only to reduce duplication is ok.
type BaseAssemblerImpl struct {
	// SetBranchTargetOnNextNodes holds branch kind instructions (BR, conditional BR, etc.)
	// where we want to set the next coming instruction as the destination of these BR instructions.
	SetBranchTargetOnNextNodes []Node

	// JumpTableEntries holds the information to build jump tables.
	JumpTableEntries []JumpTableEntry
}

// JumpTableEntry is the necessary data to build a jump table.
// This is exported for testing purpose.
type JumpTableEntry struct {
	t                        *StaticConst
	labelInitialInstructions []Node
}

// SetJumpTargetOnNext implements AssemblerBase.SetJumpTargetOnNext
func (a *BaseAssemblerImpl) SetJumpTargetOnNext(node Node) {
	a.SetBranchTargetOnNextNodes = append(a.SetBranchTargetOnNextNodes, node)
}

// BuildJumpTable implements AssemblerBase.BuildJumpTable
func (a *BaseAssemblerImpl) BuildJumpTable(table *StaticConst, labelInitialInstructions []Node) {
	a.JumpTableEntries = append(a.JumpTableEntries, JumpTableEntry{
		t:                        table,
		labelInitialInstructions: labelInitialInstructions,
	})
}

// FinalizeJumpTableEntry finalizes the build tables inside the given code.
func (a *BaseAssemblerImpl) FinalizeJumpTableEntry(code []byte) (err error) {
	for i := range a.JumpTableEntries {
		ent := &a.JumpTableEntries[i]
		labelInitialInstructions := ent.labelInitialInstructions
		table := ent.t
		// Compile the offset table for each target.
		base := labelInitialInstructions[0].OffsetInBinary()
		for i, nop := range labelInitialInstructions {
			if nop.OffsetInBinary()-base >= JumpTableMaximumOffset {
				return fmt.Errorf("too large br_table")
			}
			// We store the offset from the beginning of the L0's initial instruction.
			binary.LittleEndian.PutUint32(code[table.OffsetInBinary+uint64(i*4):table.OffsetInBinary+uint64((i+1)*4)],
				uint32(nop.OffsetInBinary())-uint32(base))
		}
	}
	return
}
