package loader

import (
	"debug/elf"
	"fmt"
	"io"

	"go.firedancer.io/radiance/pkg/sbf"
)

// The following ELF loading rules seem mostly arbitrary.
// For the sake of cleanliness, this loader doesn't process
// some badly malformed ELFs that would pass on Solana mainnet.
// The Solana protocol is being improved in this area.

// copy allocates program buffers and copies ELF contents.
func (l *Loader) copy() error {
	l.progRange = newAddrRange()
	l.rodatas = make([]addrRange, 0, 4)
	if err := l.getText(); err != nil {
		return err
	}
	if err := l.mapSections(); err != nil {
		return err
	}
	if err := l.copySections(); err != nil {
		return err
	}
	return nil
}

// getText remembers the range of .text in the program buffer
func (l *Loader) getText() error {
	if err := l.checkSectionAddrs(l.shText); err != nil {
		return fmt.Errorf("invalid .text: %w", err)
	}
	l.textRange = addrRange{min: l.shText.Off, max: l.shText.Off + l.shText.Size}
	return nil
}

// mapRodataLike reserves ranges for sections in the program buffer
func (l *Loader) mapSections() error {
	// Walk all non-standard rodata sections
	iter := l.newShTableIter()
	for iter.Next() && iter.Err() == nil {
		i, sh := iter.Index(), iter.Item()

		// Skip standard sections
		sectionName, err := l.getString(&l.shShstrtab, sh.Name, maxSectionNameLen)
		if err != nil {
			return fmt.Errorf("getString: %w", err)
		}
		switch sectionName {
		case ".text", ".rodata", ".data.rel.ro", ".eh_frame":
			// ok
		default:
			continue
		}

		if err := l.checkSectionAddrs(&sh); err != nil {
			return fmt.Errorf("invalid rodata-like section %d: %w", i, err)
		}

		// Section overlap check & bounds tracking
		section := addrRange{min: sh.Off, max: sh.Off + sh.Size}
		if section.len() == 0 {
			continue
		}
		if l.progRange.containsRange(section) {
			// TODO rbpf probably doesn't have this restriction
			return fmt.Errorf("rodata section %d overlaps with other section", i)
		}
		l.progRange.insert(section)

		if section.min != l.textRange.min {
			l.rodatas = append(l.rodatas, section)
		}
	}
	return iter.Err()
}

func (l *Loader) checkSectionAddrs(sh *elf.Section64) error {
	// TODO Support true vaddr ELFs

	if sh.Size > l.fileSize {
		return io.ErrUnexpectedEOF
	}
	if sh.Addr != sh.Off {
		return fmt.Errorf("section physical address out-of-place")
	}

	// Ensure section within VM program range
	vaddr := clampAddUint64(sbf.VaddrProgram, sh.Addr)
	vaddrEnd := vaddr + sh.Size
	if vaddrEnd < vaddr || vaddrEnd > sbf.VaddrStack {
		return fmt.Errorf("section virtual address out-of-bounds")
	}

	return nil
}

// copySections copies text and rodata-like sections from the ELF into VM memory.
func (l *Loader) copySections() error {
	if l.progRange.len() == 0 {
		// TODO what is the correct behavior here?
		return fmt.Errorf("program is empty (???)")
	}
	l.progRange.extendToFit(0)

	// Allocate!
	l.program = make([]byte, l.progRange.len())

	// Read data from ELF file
	for _, section := range l.rodatas {
		if err := l.copySection(section); err != nil {
			return err
		}
	}
	if err := l.copySection(l.textRange); err != nil {
		return err
	}

	// Special sub-slice for text
	l.text = l.getRange(l.textRange)

	return nil
}

func (l *Loader) copySection(section addrRange) (err error) {
	off, size := int64(section.min), int64(section.len())
	rd := io.NewSectionReader(l.rd, off, size)
	_, err = io.ReadFull(rd, l.program[section.min:section.max])
	return
}

func (l *Loader) getRange(section addrRange) []byte {
	return l.program[section.min:section.max]
}
