package diag

import (
	
	
	

	
)

// Context is a range of text in a source code. It is typically used for
// errors that can be associated with a part of the source code, like parse
// errors and a traceback entry.
type Context struct {
	Name   string
	Source string
	Ranging

	savedShowInfo *rangeShowInfo
}

// NewContext creates a new Context.
func (,  string,  Ranger) *Context {
	return &Context{, , .Range(), nil}
}

// Information about the source range that are needed for showing.
type rangeShowInfo struct {
	// Head is the piece of text immediately before Culprit, extending to, but
	// not including the closest line boundary. If Culprit already starts after
	// a line boundary, Head is an empty string.
	Head string
	// Culprit is Source[Begin:End], with any trailing newlines stripped.
	Culprit string
	// Tail is the piece of text immediately after Culprit, extending to, but
	// not including the closet line boundary. If Culprit already ends before a
	// line boundary, Tail is an empty string.
	Tail string
	// BeginLine is the (1-based) line number that the first character of Culprit is on.
	BeginLine int
	// EndLine is the (1-based) line number that the last character of Culprit is on.
	EndLine int
}

// Variables controlling the style of the culprit.
var (
	culpritLineBegin   = "\033[1;4m"
	culpritLineEnd     = "\033[m"
	culpritPlaceHolder = "^"
)

func ( *Context) () string {
	return .Source[.From:.To]
}

func ( *Context) () *rangeShowInfo {
	if .savedShowInfo != nil {
		return .savedShowInfo
	}

	 := .Source[:.From]
	 := .Source[.From:.To]
	 := .Source[.To:]

	 := lastLine()
	 := strings.Count(, "\n") + 1

	// If the culprit ends with a newline, stripe it. Otherwise, tail is nonempty.
	var  string
	if strings.HasSuffix(, "\n") {
		 = [:len()-1]
	} else {
		 = firstLine()
	}

	 :=  + strings.Count(, "\n")

	.savedShowInfo = &rangeShowInfo{, , , , }
	return .savedShowInfo
}

// Show shows a SourceContext.
func ( *Context) ( string) string {
	if  := .checkPosition();  != nil {
		return .Error()
	}
	return (.Name + ", " + .lineRange() +
		"\n" +  + .relevantSource())
}

// ShowCompact shows a SourceContext, with no line break between the
// source position range description and relevant source excerpt.
func ( *Context) ( string) string {
	if  := .checkPosition();  != nil {
		return .Error()
	}
	 := .Name + ", " + .lineRange() + " "
	// Extra indent so that following lines line up with the first line.
	 := strings.Repeat(" ", wcwidth.Of())
	return  + .relevantSource(+)
}

func ( *Context) () error {
	if .From == -1 {
		return fmt.Errorf("%s, unknown position", .Name)
	} else if .From < 0 || .To > len(.Source) || .From > .To {
		return fmt.Errorf("%s, invalid position %d-%d", .Name, .From, .To)
	}
	return nil
}

func ( *Context) () string {
	 := .showInfo()

	if .BeginLine == .EndLine {
		return fmt.Sprintf("line %d:", .BeginLine)
	}
	return fmt.Sprintf("line %d-%d:", .BeginLine, .EndLine)
}

func ( *Context) ( string) string {
	 := .showInfo()

	var  bytes.Buffer
	.WriteString(.Head)

	 := .Culprit
	if  == "" {
		 = culpritPlaceHolder
	}

	for ,  := range strings.Split(, "\n") {
		if  > 0 {
			.WriteByte('\n')
			.WriteString()
		}
		.WriteString(culpritLineBegin)
		.WriteString()
		.WriteString(culpritLineEnd)
	}

	.WriteString(.Tail)
	return .String()
}

func ( string) string {
	 := strings.IndexByte(, '\n')
	if  == -1 {
		return 
	}
	return [:]
}

func ( string) string {
	// When s does not contain '\n', LastIndexByte returns -1, which happens to
	// be what we want.
	return [strings.LastIndexByte(, '\n')+1:]
}