// Copyright 2016 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.

// This file implements the compressed encoding of source
// positions using a lookup table.

package src

// XPos is a more compact representation of Pos.
type XPos struct {
	index int32
	lico
}

// NoXPos is a valid unknown position.
var NoXPos XPos

// IsKnown reports whether the position p is known.
// XPos.IsKnown() matches Pos.IsKnown() for corresponding
// positions.
func (p XPos) IsKnown() bool {
	return p.index != 0 || p.Line() != 0
}

// Before reports whether the position p comes before q in the source.
// For positions with different bases, ordering is by base index.
func (p XPos) Before(q XPos) bool {
	n, m := p.index, q.index
	return n < m || n == m && p.lico < q.lico
}

// SameFile reports whether p and q are positions in the same file.
func (p XPos) SameFile(q XPos) bool {
	return p.index == q.index
}

// SameFileAndLine reports whether p and q are positions on the same line in the same file.
func (p XPos) SameFileAndLine(q XPos) bool {
	return p.index == q.index && p.lico.SameLine(q.lico)
}

// After reports whether the position p comes after q in the source.
// For positions with different bases, ordering is by base index.
func (p XPos) After(q XPos) bool {
	n, m := p.index, q.index
	return n > m || n == m && p.lico > q.lico
}

// WithNotStmt returns the same location to be marked with DWARF is_stmt=0
func (p XPos) WithNotStmt() XPos {
	p.lico = p.lico.withNotStmt()
	return p
}

// WithDefaultStmt returns the same location with undetermined is_stmt
func (p XPos) WithDefaultStmt() XPos {
	p.lico = p.lico.withDefaultStmt()
	return p
}

// WithIsStmt returns the same location to be marked with DWARF is_stmt=1
func (p XPos) WithIsStmt() XPos {
	p.lico = p.lico.withIsStmt()
	return p
}

// WithBogusLine returns a bogus line that won't match any recorded for the source code.
// Its use is to disrupt the statements within an infinite loop so that the debugger
// will not itself loop infinitely waiting for the line number to change.
// gdb chooses not to display the bogus line; delve shows it with a complaint, but the
// alternative behavior is to hang.
func (p XPos) WithBogusLine() XPos {
	if p.index == 0 {
		// See #35652
		panic("Assigning a bogus line to XPos with no file will cause mysterious downstream failures.")
	}
	p.lico = makeBogusLico()
	return p
}

// WithXlogue returns the same location but marked with DWARF function prologue/epilogue
func (p XPos) WithXlogue(x PosXlogue) XPos {
	p.lico = p.lico.withXlogue(x)
	return p
}

// LineNumber returns a string for the line number, "?" if it is not known.
func (p XPos) LineNumber() string {
	if !p.IsKnown() {
		return "?"
	}
	return p.lico.lineNumber()
}

// FileIndex returns a smallish non-negative integer corresponding to the
// file for this source position.  Smallish is relative; it can be thousands
// large, but not millions.
func (p XPos) FileIndex() int32 {
	return p.index
}

func (p XPos) LineNumberHTML() string {
	if !p.IsKnown() {
		return "?"
	}
	return p.lico.lineNumberHTML()
}

// AtColumn1 returns the same location but shifted to column 1.
func (p XPos) AtColumn1() XPos {
	p.lico = p.lico.atColumn1()
	return p
}

// A PosTable tracks Pos -> XPos conversions and vice versa.
// Its zero value is a ready-to-use PosTable.
type PosTable struct {
	baseList []*PosBase
	indexMap map[*PosBase]int
	nameMap  map[string]int // Maps file symbol name to index for debug information.
}

// XPos returns the corresponding XPos for the given pos,
// adding pos to t if necessary.
func (t *PosTable) XPos(pos Pos) XPos {
	return XPos{t.baseIndex(pos.base), pos.lico}
}

func (t *PosTable) baseIndex(base *PosBase) int32 {
	if base == nil {
		return 0
	}

	if i, ok := t.indexMap[base]; ok {
		return int32(i)
	}

	if base.fileIndex >= 0 {
		panic("PosBase already registered with a PosTable")
	}

	if t.indexMap == nil {
		t.baseList = append(t.baseList, nil)
		t.indexMap = make(map[*PosBase]int)
		t.nameMap = make(map[string]int)
	}

	i := len(t.baseList)
	t.indexMap[base] = i
	t.baseList = append(t.baseList, base)

	fileIndex, ok := t.nameMap[base.absFilename]
	if !ok {
		fileIndex = len(t.nameMap)
		t.nameMap[base.absFilename] = fileIndex
	}
	base.fileIndex = fileIndex

	return int32(i)
}

// Pos returns the corresponding Pos for the given p.
// If p cannot be translated via t, the function panics.
func (t *PosTable) Pos(p XPos) Pos {
	var base *PosBase
	if p.index != 0 {
		base = t.baseList[p.index]
	}
	return Pos{base, p.lico}
}

// FileTable returns a slice of all files used to build this package.
func (t *PosTable) FileTable() []string {
	// Create a LUT of the global package level file indices. This table is what
	// is written in the debug_lines header, the file[N] will be referenced as
	// N+1 in the debug_lines table.
	fileLUT := make([]string, len(t.nameMap))
	for str, i := range t.nameMap {
		fileLUT[i] = str
	}
	return fileLUT
}
