package frame

import (
	"encoding/binary"
	"fmt"
	"sort"
)

// Represents a Common Information Entry in
// the Dwarf .debug_frame section.
type CommonInformationEntry struct {
	Length                uint32
	CIE_id                uint32
	Version               uint8
	Augmentation          string
	CodeAlignmentFactor   uint64
	DataAlignmentFactor   int64
	ReturnAddressRegister uint64
	InitialInstructions   []byte
}

// Represents a Frame Descriptor Entry in the
// Dwarf .debug_frame section.
type FrameDescriptionEntry struct {
	Length       uint32
	CIE          *CommonInformationEntry
	Instructions []byte
	begin, end   uint64
	order        binary.ByteOrder
}

// Returns whether or not the given address is within the
// bounds of this frame.
func (fde *FrameDescriptionEntry) Cover(addr uint64) bool {
	if (addr - fde.begin) < fde.end {
		return true
	}
	return false
}

// Address of first location for this frame.
func (fde *FrameDescriptionEntry) Begin() uint64 {
	return fde.begin
}

// Address of last location for this frame.
func (fde *FrameDescriptionEntry) End() uint64 {
	return fde.begin + fde.end
}

// Set up frame for the given PC.
func (fde *FrameDescriptionEntry) EstablishFrame(pc uint64) *FrameContext {
	return executeDwarfProgramUntilPC(fde, pc)
}

// Return the offset from the current SP that the return address is stored at.
func (fde *FrameDescriptionEntry) ReturnAddressOffset(pc uint64) (frameOffset, returnAddressOffset int64) {
	frame := fde.EstablishFrame(pc)
	return frame.cfa.offset, frame.regs[fde.CIE.ReturnAddressRegister].offset
}

type FrameDescriptionEntries []*FrameDescriptionEntry

func NewFrameIndex() FrameDescriptionEntries {
	return make(FrameDescriptionEntries, 0, 1000)
}

type NoFDEForPCError struct {
	PC uint64
}

func (err *NoFDEForPCError) Error() string {
	return fmt.Sprintf("could not find FDE for PC %#v", err.PC)
}

// Returns the Frame Description Entry for the given PC.
func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry, error) {
	idx := sort.Search(len(fdes), func(i int) bool {
		if fdes[i].Cover(pc) {
			return true
		}
		if fdes[i].LessThan(pc) {
			return false
		}
		return true
	})
	if idx == len(fdes) {
		return nil, &NoFDEForPCError{pc}
	}
	return fdes[idx], nil
}

func (frame *FrameDescriptionEntry) LessThan(pc uint64) bool {
	return frame.End() <= pc
}
