package tk

import (
	
	
	
	
	

	
	
	
)

// CodeArea is a Widget for displaying and editing code.
type CodeArea interface {
	Widget
	// CopyState returns a copy of the state.
	CopyState() CodeAreaState
	// MutateState calls the given the function while locking StateMutex.
	MutateState(f func(*CodeAreaState))
	// Submit triggers the OnSubmit callback.
	Submit()
}

// CodeAreaSpec specifies the configuration and initial state for CodeArea.
type CodeAreaSpec struct {
	// Key bindings.
	Bindings Bindings
	// A function that highlights the given code and returns any errors it has
	// found when highlighting. If this function is not given, the Widget does
	// not highlight the code nor show any errors.
	Highlighter func(code string) (ui.Text, []error)
	// Prompt callback.
	Prompt func() ui.Text
	// Right-prompt callback.
	RPrompt func() ui.Text
	// A function that calls the callback with string pairs for abbreviations
	// and their expansions. If this function is not given, the Widget does not
	// expand any abbreviations.
	Abbreviations          func(f func(abbr, full string))
	SmallWordAbbreviations func(f func(abbr, full string))
	// A function that returns whether pasted texts (from bracketed pastes)
	// should be quoted. If this function is not given, the Widget defaults to
	// not quoting pasted texts.
	QuotePaste func() bool
	// A function that is called on the submit event.
	OnSubmit func()

	// State. When used in New, this field specifies the initial state.
	State CodeAreaState
}

// CodeAreaState keeps the mutable state of the CodeArea widget.
type CodeAreaState struct {
	Buffer      CodeBuffer
	Pending     PendingCode
	HideRPrompt bool
}

// CodeBuffer represents the buffer of the CodeArea widget.
type CodeBuffer struct {
	// Content of the buffer.
	Content string
	// Position of the dot (more commonly known as the cursor), as a byte index
	// into Content.
	Dot int
}

// PendingCode represents pending code, such as during completion.
type PendingCode struct {
	// Beginning index of the text area that the pending code replaces, as a
	// byte index into RawState.Code.
	From int
	// End index of the text area that the pending code replaces, as a byte
	// index into RawState.Code.
	To int
	// The content of the pending code.
	Content string
}

// ApplyPending applies pending code to the code buffer, and resets pending code.
func ( *CodeAreaState) () {
	.Buffer, _, _ = patchPending(.Buffer, .Pending)
	.Pending = PendingCode{}
}

func ( *CodeBuffer) ( string) {
	* = CodeBuffer{
		Content: .Content[:.Dot] +  + .Content[.Dot:],
		Dot:     .Dot + len(),
	}
}

type codeArea struct {
	// Mutex for synchronizing access to State.
	StateMutex sync.RWMutex
	// Configuration and state.
	CodeAreaSpec

	// Consecutively inserted text. Used for expanding abbreviations.
	inserts string
	// Value of State.CodeBuffer when handleKeyEvent was last called. Used for
	// detecting whether insertion has been interrupted.
	lastCodeBuffer CodeBuffer
	// Whether the widget is in the middle of bracketed pasting.
	pasting bool
	// Buffer for keeping Pasted text during bracketed pasting.
	pasteBuffer bytes.Buffer
}

// NewCodeArea creates a new CodeArea from the given spec.
func ( CodeAreaSpec) CodeArea {
	if .Bindings == nil {
		.Bindings = DummyBindings{}
	}
	if .Highlighter == nil {
		.Highlighter = func( string) (ui.Text, []error) { return ui.T(), nil }
	}
	if .Prompt == nil {
		.Prompt = func() ui.Text { return nil }
	}
	if .RPrompt == nil {
		.RPrompt = func() ui.Text { return nil }
	}
	if .Abbreviations == nil {
		.Abbreviations = func(func(,  string)) {}
	}
	if .SmallWordAbbreviations == nil {
		.SmallWordAbbreviations = func(func(,  string)) {}
	}
	if .QuotePaste == nil {
		.QuotePaste = func() bool { return false }
	}
	if .OnSubmit == nil {
		.OnSubmit = func() {}
	}
	return &codeArea{CodeAreaSpec: }
}

// Submit emits a submit event with the current code content.
func ( *codeArea) () {
	.OnSubmit()
}

// Render renders the code area, including the prompt and rprompt, highlighted
// code, the cursor, and compilation errors in the code content.
func ( *codeArea) (,  int) *term.Buffer {
	 := getView()
	 := term.NewBufferBuilder()
	renderView(, )
	 := .Buffer()
	truncateToHeight(, )
	return 
}

// Handle handles KeyEvent's of non-function keys, as well as PasteSetting
// events.
func ( *codeArea) ( term.Event) bool {
	switch event := .(type) {
	case term.PasteSetting:
		return .handlePasteSetting(bool())
	case term.KeyEvent:
		return .handleKeyEvent(ui.Key())
	}
	return false
}

func ( *codeArea) ( func(*CodeAreaState)) {
	.StateMutex.Lock()
	defer .StateMutex.Unlock()
	(&.State)
}

func ( *codeArea) () CodeAreaState {
	.StateMutex.RLock()
	defer .StateMutex.RUnlock()
	return .State
}

func ( *codeArea) () {
	.inserts = ""
	.lastCodeBuffer = CodeBuffer{}
}

func ( *codeArea) ( bool) bool {
	.resetInserts()
	if  {
		.pasting = true
	} else {
		 := .pasteBuffer.String()
		if .QuotePaste() {
			 = parse.Quote()
		}
		.MutateState(func( *CodeAreaState) { .Buffer.InsertAtDot() })

		.pasting = false
		.pasteBuffer = bytes.Buffer{}
	}
	return true
}

// Tries to expand a simple abbreviation. This function assumes that the state
// mutex is already being held.
func ( *codeArea) () {
	var ,  string
	// Find the longest matching abbreviation.
	.Abbreviations(func(,  string) {
		if strings.HasSuffix(.inserts, ) && len() > len() {
			,  = , 
		}
	})
	if len() > 0 {
		 := &.State.Buffer
		* = CodeBuffer{
			Content: .Content[:.Dot-len()] +  + .Content[.Dot:],
			Dot:     .Dot - len() + len(),
		}
		.resetInserts()
	}
}

// Tries to expand a word abbreviation. This function assumes that the state
// mutex is already being held.
func ( *codeArea) ( rune,  func(rune) int) {
	 := &.State.Buffer
	if .Dot < len(.Content) {
		// Word abbreviations are only expanded at the end of the buffer.
		return
	}
	 := len(string())
	if  >= len(.inserts) {
		// Only the trigger has been inserted, or a simple abbreviation was just
		// expanded. In either case, there is nothing to expand.
		return
	}
	// The trigger is only used to determine word boundary; when considering
	// what to expand, we only consider the part that was inserted before it.
	 := .inserts[:len(.inserts)-]

	var ,  string
	// Find the longest matching abbreviation.
	.SmallWordAbbreviations(func(,  string) {
		if len() <= len() {
			// This abbreviation can't be the longest.
			return
		}
		if !strings.HasSuffix(, ) {
			// This abbreviation was not inserted.
			return
		}
		// Verify the trigger rune creates a word boundary.
		,  := utf8.DecodeLastRuneInString()
		if () == () {
			return
		}
		// Verify the rune preceding the abbreviation, if any, creates a word
		// boundary.
		if len(.Content) > len()+ {
			,  := utf8.DecodeLastRuneInString(.Content[:len(.Content)-len()-])
			,  := utf8.DecodeRuneInString()
			if () == () {
				return
			}
		}
		,  = , 
	})
	if len() > 0 {
		* = CodeBuffer{
			Content: .Content[:.Dot-len()-] +  + string(),
			Dot:     .Dot - len() + len(),
		}
		.resetInserts()
	}
}

func ( *codeArea) ( ui.Key) bool {
	 := .Mod != 0 || .Rune < 0
	if .pasting {
		if  {
			// TODO: Notify the user of the error, or insert the original
			// character as is.
		} else {
			.pasteBuffer.WriteRune(.Rune)
		}
		return true
	}

	if .Bindings.Handle(, term.KeyEvent()) {
		return true
	}

	// We only implement essential keybindings here. Other keybindings can be
	// added via handler overlays.
	switch  {
	case ui.K('\n'):
		.resetInserts()
		.Submit()
		return true
	case ui.K(ui.Backspace), ui.K('H', ui.Ctrl):
		.resetInserts()
		.MutateState(func( *CodeAreaState) {
			 := &.Buffer
			// Remove the last rune.
			,  := utf8.DecodeLastRuneInString(.Content[:.Dot])
			* = CodeBuffer{
				Content: .Content[:.Dot-] + .Content[.Dot:],
				Dot:     .Dot - ,
			}
		})
		return true
	default:
		if  || !unicode.IsGraphic(.Rune) {
			.resetInserts()
			return false
		}
		.StateMutex.Lock()
		defer .StateMutex.Unlock()
		if .lastCodeBuffer != .State.Buffer {
			// Something has happened between the last insert and this one;
			// reset the state.
			.resetInserts()
		}
		 := string(.Rune)
		.State.Buffer.InsertAtDot()
		.inserts += 
		.lastCodeBuffer = .State.Buffer
		.expandSimpleAbbr()
		.expandWordAbbr(.Rune, CategorizeSmallWord)
		return true
	}
}

// IsAlnum determines if the rune is an alphanumeric character.
func ( rune) bool {
	return unicode.IsLetter() || unicode.IsNumber()
}

// CategorizeSmallWord determines if the rune is whitespace, alphanum, or
// something else.
func ( rune) int {
	switch {
	case unicode.IsSpace():
		return 0
	case IsAlnum():
		return 1
	default:
		return 2
	}
}