// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goobj

import (
	"cmd/internal/goobj2"
	"cmd/internal/objabi"
	"fmt"
	"strings"
)

// Read object file in new format. For now we still fill
// the data to the current goobj API.
func (r *objReader) readNew() {
	start := uint32(r.offset)

	length := r.limit - r.offset
	objbytes := make([]byte, length)
	r.readFull(objbytes)
	rr := goobj2.NewReaderFromBytes(objbytes, false)
	if rr == nil {
		panic("cannot read object file")
	}

	// Imports
	r.p.Imports = rr.Autolib()

	pkglist := rr.Pkglist()

	abiToVer := func(abi uint16) int64 {
		var vers int64
		if abi == goobj2.SymABIstatic {
			// Static symbol
			vers = r.p.MaxVersion
		}
		return vers
	}

	resolveSymRef := func(s goobj2.SymRef) SymID {
		var i int
		switch p := s.PkgIdx; p {
		case goobj2.PkgIdxInvalid:
			if s.SymIdx != 0 {
				panic("bad sym ref")
			}
			return SymID{}
		case goobj2.PkgIdxNone:
			i = int(s.SymIdx) + rr.NSym()
		case goobj2.PkgIdxBuiltin:
			name, abi := goobj2.BuiltinName(int(s.SymIdx))
			return SymID{name, int64(abi)}
		case goobj2.PkgIdxSelf:
			i = int(s.SymIdx)
		default:
			pkg := pkglist[p]
			return SymID{fmt.Sprintf("%s.<#%d>", pkg, s.SymIdx), 0}
		}
		sym := goobj2.Sym{}
		sym.Read(rr, rr.SymOff(i))
		return SymID{sym.Name, abiToVer(sym.ABI)}
	}

	// Read things for the current goobj API for now.

	// Symbols
	pcdataBase := start + rr.PcdataBase()
	n := rr.NSym() + rr.NNonpkgdef() + rr.NNonpkgref()
	ndef := rr.NSym() + rr.NNonpkgdef()
	for i := 0; i < n; i++ {
		osym := goobj2.Sym{}
		osym.Read(rr, rr.SymOff(i))
		if osym.Name == "" {
			continue // not a real symbol
		}
		// In a symbol name in an object file, "". denotes the
		// prefix for the package in which the object file has been found.
		// Expand it.
		name := strings.ReplaceAll(osym.Name, `"".`, r.pkgprefix)
		symID := SymID{Name: name, Version: abiToVer(osym.ABI)}
		r.p.SymRefs = append(r.p.SymRefs, symID)

		if i >= ndef {
			continue // not a defined symbol from here
		}

		// Symbol data
		dataOff := rr.DataOff(i)
		siz := int64(rr.DataSize(i))

		sym := Sym{
			SymID: symID,
			Kind:  objabi.SymKind(osym.Type),
			DupOK: osym.Dupok(),
			Size:  int64(osym.Siz),
			Data:  Data{int64(start + dataOff), siz},
		}
		r.p.Syms = append(r.p.Syms, &sym)

		// Reloc
		nreloc := rr.NReloc(i)
		sym.Reloc = make([]Reloc, nreloc)
		for j := 0; j < nreloc; j++ {
			rel := goobj2.Reloc{}
			rel.Read(rr, rr.RelocOff(i, j))
			sym.Reloc[j] = Reloc{
				Offset: int64(rel.Off),
				Size:   int64(rel.Siz),
				Type:   objabi.RelocType(rel.Type),
				Add:    rel.Add,
				Sym:    resolveSymRef(rel.Sym),
			}
		}

		// Aux symbol info
		isym := -1
		funcdata := make([]goobj2.SymRef, 0, 4)
		naux := rr.NAux(i)
		for j := 0; j < naux; j++ {
			a := goobj2.Aux{}
			a.Read(rr, rr.AuxOff(i, j))
			switch a.Type {
			case goobj2.AuxGotype:
				sym.Type = resolveSymRef(a.Sym)
			case goobj2.AuxFuncInfo:
				if a.Sym.PkgIdx != goobj2.PkgIdxSelf {
					panic("funcinfo symbol not defined in current package")
				}
				isym = int(a.Sym.SymIdx)
			case goobj2.AuxFuncdata:
				funcdata = append(funcdata, a.Sym)
			case goobj2.AuxDwarfInfo, goobj2.AuxDwarfLoc, goobj2.AuxDwarfRanges, goobj2.AuxDwarfLines:
				// nothing to do
			default:
				panic("unknown aux type")
			}
		}

		// Symbol Info
		if isym == -1 {
			continue
		}
		b := rr.BytesAt(rr.DataOff(isym), rr.DataSize(isym))
		info := goobj2.FuncInfo{}
		info.Read(b)

		info.Pcdata = append(info.Pcdata, info.PcdataEnd) // for the ease of knowing where it ends
		f := &Func{
			Args:     int64(info.Args),
			Frame:    int64(info.Locals),
			NoSplit:  info.NoSplit != 0,
			Leaf:     osym.Leaf(),
			TopFrame: osym.TopFrame(),
			PCSP:     Data{int64(pcdataBase + info.Pcsp), int64(info.Pcfile - info.Pcsp)},
			PCFile:   Data{int64(pcdataBase + info.Pcfile), int64(info.Pcline - info.Pcfile)},
			PCLine:   Data{int64(pcdataBase + info.Pcline), int64(info.Pcinline - info.Pcline)},
			PCInline: Data{int64(pcdataBase + info.Pcinline), int64(info.Pcdata[0] - info.Pcinline)},
			PCData:   make([]Data, len(info.Pcdata)-1), // -1 as we appended one above
			FuncData: make([]FuncData, len(info.Funcdataoff)),
			File:     make([]string, len(info.File)),
			InlTree:  make([]InlinedCall, len(info.InlTree)),
		}
		sym.Func = f
		for k := range f.PCData {
			f.PCData[k] = Data{int64(pcdataBase + info.Pcdata[k]), int64(info.Pcdata[k+1] - info.Pcdata[k])}
		}
		for k := range f.FuncData {
			symID := resolveSymRef(funcdata[k])
			f.FuncData[k] = FuncData{symID, int64(info.Funcdataoff[k])}
		}
		for k := range f.File {
			symID := resolveSymRef(info.File[k])
			f.File[k] = symID.Name
		}
		for k := range f.InlTree {
			inl := &info.InlTree[k]
			f.InlTree[k] = InlinedCall{
				Parent:   int64(inl.Parent),
				File:     resolveSymRef(inl.File).Name,
				Line:     int64(inl.Line),
				Func:     resolveSymRef(inl.Func),
				ParentPC: int64(inl.ParentPC),
			}
		}
	}
}
