package main

import (
	"bufio"
	"flag"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

var Borges = flag.String("borges", "/home/strick/borges/", "dir with source module listings")
var FlagTraceOnModule = flag.String("trace_on_module", "", "start tracing when loading this module")
var ALLOW_OTHER_SECTIONS = flag.Bool("linked", false, "allow other sections")

type ModSrc struct {
	Src      map[uint]string
	Filename string
	Err      error
}

var Listings = make(map[string]*ModSrc)

func Lookup(module string, offset uint, startTrace func()) string {
	if *Borges == "" {
		return ""
	}
	if module == "" || module[0] == '(' {
		return "" // Handles "open ../borges/(fe): no such file or directory"
	}

	m, ok := Listings[module]
	if !ok {
		filename := filepath.Join(*Borges, module)
		m = LoadFile(filename)
		Listings[module] = m

		words := strings.Split(module, ".")
		if words[0] == strings.ToLower(*FlagTraceOnModule) {
			startTrace()
		}
	}

	if m.Err != nil {
		return "" // Module not found.
	}
	s, _ := m.Src[offset]
	return s // Empty if offset not found.
}

var parseLabel = regexp.MustCompile(`^([[:xdigit:]]{4})  +[(].*?[)]:[0-9]{5} +([A-Za-z0-9._$@]+[:]?) *$`)
var parse = regexp.MustCompile(`^([[:xdigit:]]{4}) [[:xdigit:]]+ +([(].*?[)]:[0-9]{5})         (.*)$`)
var parseSection = regexp.MustCompile(`^ +[(].*?[)]:[[:digit:]]{5} +(?i:section) +([.][.[:word:]]+)`)
var parseEndSection = regexp.MustCompile(`^ +[(].*?[)]:[[:digit:]]{5} +(?i:endsection)`)
var parseArea = regexp.MustCompile(`^ +[(].*?[)]:[[:digit:]]{5} +(?i:.area) +([.[:word:]]+)`)

func LoadFile(filename string) *ModSrc {
	d := make(map[uint]string)
	// Try overriding filename with ".mod" instead of version suffix.
	var fd *os.File
	var err error
	if len(filename) > 11 {
		fd, err = os.Open(filename[:len(filename)-11] + ".mod")
	}
	if err != nil {
		fd, err = os.Open(filename)
		if err != nil {
			log.Printf("Cannot open listing %q: %v", filename, err)

			return &ModSrc{
				Src:      nil,
				Filename: filename,
				Err:      err,
			}
		}
	}
	defer fd.Close()
	r := bufio.NewScanner(fd)
	inOtherSection := false

	var sawAddr, sawLabel, section string
	for r.Scan() {
		text := r.Text()
		m := parse.FindStringSubmatch(text)
		if m != nil && (*ALLOW_OTHER_SECTIONS || !inOtherSection) {
			hexaddr, where, line := m[1], m[2], m[3]
			addr, err := strconv.ParseUint(hexaddr, 16, 16)
			if err != nil {
				log.Panicf("Should have been a hex integer: %q: %v", hexaddr, err)
			}
			if sawLabel != "" && sawAddr == hexaddr && line[0] == ' ' {
				line = sawLabel + line
			}
			d[uint(addr)] = Format("%s %s {;*;%s;}", line, where, section)
			//log.Printf("FILE %s ADDR %x LINE %q", filename, addr, line)
			sawAddr = ""
			sawLabel = ""
		}
		m = parseLabel.FindStringSubmatch(text)
		if m != nil && !inOtherSection {
			sawAddr = m[1]
			sawLabel = m[2]
		}
		m = parseArea.FindStringSubmatch(text)
		if m != nil {
			section = m[1]
			inOtherSection = (section != "code")
		}
		m = parseSection.FindStringSubmatch(text)
		if m != nil {
			section = m[1]
			inOtherSection = (section != "code")
		}
		m = parseEndSection.FindStringSubmatch(text)
		if m != nil {
			section = ""
			inOtherSection = false
		}
		if strings.Contains(text, "section") || strings.Contains(text, ".area") {
			log.Printf("After %q NOW SECTION %q", text, section)
		}
	}
	log.Printf("BORGES: Loaded Source: %q (%d)", filename, len(d))
	return &ModSrc{
		Src:      d,
		Filename: filename,
		Err:      nil,
	}
}

type Section struct {
	Name     string
	Filename string
	Offset   uint
	Length   uint
}

// DEMO: "Section: .text2 (../../syscall_fs.o) load at 0C4D, length 057C"

var SectionPattern = regexp.MustCompile(`^Section: ([.][a-z0-9_.]+) [(](.*)[)] load at (....), length (....)`)

func ReadMap(filename string) []*Section {
	fd, err := os.Open(filename)
	if err != nil {
		return nil
	}
	defer fd.Close()
	r := bufio.NewScanner(fd)
	var z []*Section
	for r.Scan() {
		text := r.Text()
		m := SectionPattern.FindStringSubmatch(text)
		if m != nil {
			base := filepath.Base(m[2])
			base = strings.TrimSuffix(base, ".list")
			base = strings.TrimSuffix(base, ".bin")
			base = strings.TrimSuffix(base, ".o")
			sect := &Section{
				Name:     m[1],
				Filename: base,
				Offset:   parseHex(m[3]),
				Length:   parseHex(m[4]),
			}
			z = append(z, sect)
			log.Printf("FROM %q ...", text)
			log.Printf("MAP %s %s %04x %04x", sect.Name, sect.Filename, sect.Offset, sect.Length)
		}
	}
	return z
}

func parseHex(s string) uint {
	x, err := strconv.ParseUint(s, 16, 16)
	if err != nil {
		panic(err)
	}
	return uint(x)
}

var ParseSectionAnnotation = regexp.MustCompile("^(.*){;(.*);(.*);}$")

func ComputeLinkSrc(sectList []*Section, ll []*ModSrc, abs []*ModSrc) *ModSrc {
	sectmap := make(map[string]*Section)
	for _, sect := range sectList {
		base := strings.TrimSuffix(sect.Filename, ".list")
		base = strings.TrimSuffix(base, ".bin")
		base = strings.TrimSuffix(base, ".o")
		key := Format("%s;%s", sect.Name, base)
		sectmap[key] = sect
		log.Printf("sectmap key %q value %v", key, sect)
	}

	z := &ModSrc{
		Src: make(map[uint]string),
	}
	for _, l := range ll {
		base := filepath.Base(l.Filename)
		base = strings.TrimSuffix(base, ".list")
		base = strings.TrimSuffix(base, ".o")
		for a, s := range l.Src {
			m := ParseSectionAnnotation.FindStringSubmatch(s)
			if m != nil {
				line_, _, section_ := m[1], m[2], m[3]

				key_ := Format("%s;%s", section_, base)
				log.Printf("MATCH: %q :::: %q %q %q", s, line_, section_, key_)

				if sect_, ok := sectmap[key_]; ok {
					log.Printf("SAV? (length %d) key %q off=%d", sect_.Length, key_, sect_.Offset)
					if a-sect_.Offset <= sect_.Length {
						k := a + sect_.Offset

						if old, ok := z.Src[k]; ok {
							log.Printf("ComputeLinkSrc ALREADY %q ALREADY %04x IN %q", key_, k, old)
						}

						z.Src[k] = line_ // Format("%s#%s#%04x", line_, key_, a)
						z.Src[k] = Format("%s        ;;%s;;%04x;;", line_, key_, a)
						log.Printf("SAVED %04x -> %q", k, line_)
						// Format("%s#%s#%04x", line_, key_, a)
					}
				} else {
					log.Printf("ComputeLinkSrc CANNOT FIND KEY %q", key_)
				}

			} else {
				log.Printf("NO MATCH: %q", s)
			}
		}
	}
	for _, a := range abs {
		for k, v := range a.Src {
			z.Src[k] = v
		}
	}
	return z
}
