package symbol

import (
	"debug/elf"
	"io"
	"sort"
)

// A spanner
type spanner interface {
	span() addrSpan
}

type addrSpan struct {
	addr uint64
	size uint64
}

func (as addrSpan) span() addrSpan { return as }

func (as addrSpan) contains(addr uint64) bool { return as.addr <= addr && addr-as.addr < as.size }

func spanLess(i, j addrSpan) bool {
	if i.addr != j.addr {
		return i.addr < j.addr
	}
	// there should not be ties, or even overlapping data ranges .. but in case there are, break them
	if i.size != j.size {
		return i.size < j.size
	}
	return false
}

// A section
type section struct {
	addrSpan

	id      elf.SectionIndex
	symbols []*symbol
}

type symbol struct {
	addrSpan

	name string
}

// The sections type allows sorting sections by start address
type sections []*section

func (s sections) Len() int           { return len(s) }
func (s sections) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s sections) Less(i, j int) bool { return spanLess(s[i].span(), s[j].span()) }

// The symbols type allows sorting symbols by start address
type symbols []*symbol

func (s symbols) Len() int           { return len(s) }
func (s symbols) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s symbols) Less(i, j int) bool { return spanLess(s[i].span(), s[j].span()) }

//

type location struct {
	addr uint64
	name string
	file string
	line int
}

type object struct {
	sections []*section
}

func newObject(r io.ReaderAt) (*object, error) {
	var obj object

	f, err := elf.NewFile(r)
	if err != nil {
		return nil, err
	}

	for i, s := range f.Sections {
		if bits := elf.SHF_ALLOC | elf.SHF_EXECINSTR; s.Flags&bits != bits {
			continue
		}
		obj.sections = append(obj.sections, &section{
			addrSpan: addrSpan{addr: s.Addr, size: s.Size},

			id: elf.SectionIndex(i),
		})
	}
	sort.Sort(sections(obj.sections))
	secs := make(map[elf.SectionIndex]*section)
	for _, s := range obj.sections {
		secs[s.id] = s
	}

	syms, err := f.Symbols()
	if err != nil {
		return nil, err
	}

	for _, sym := range syms {
		sec, ok := secs[sym.Section]
		if !ok {
			continue
		}
		sec.symbols = append(sec.symbols, &symbol{
			addrSpan: addrSpan{addr: sym.Value, size: sym.Size},
			name:     sym.Name,
		})
	}
	for _, s := range obj.sections {
		sort.Sort(symbols(s.symbols))
	}

	return &obj, nil
}

func (obj *object) Resolve(addr uint64) (location, error) {
	var l location
	l.addr = addr

	for _, s := range obj.sections {
		if !s.contains(addr) {
			continue
		}
		for _, sym := range s.symbols {
			if !sym.contains(addr) {
				continue
			}
			l.name = sym.name
		}
	}

	return l, nil
}

// A proc tracks mapped memory segments
type proc struct {
	maps []*mapping
}

// A mapping represents an executable file mapped into memory
type mapping struct {
	addrSpan

	fileOffset uint64
	filename   string

	obj *object
}
