package readline

import (
	"strconv"
	"strings"
)

// History is an interface to allow you to write your own history logging
// tools. eg sqlite backend instead of a file system.
// By default readline will just use the dummyLineHistory interface which only
// logs the history to memory ([]string to be precise).
type History interface {
	// Append takes the line and returns an updated number of lines or an error
	Write(string) (int, error)

	// GetLine takes the historic line number and returns the line or an error
	GetLine(int) (string, error)

	// Len returns the number of history lines
	Len() int

	// Dump returns everything in readline. The return is an interface{} because
	// not all LineHistory implementations will want to structure the history in
	// the same way. And since Dump() is not actually used by the readline API
	// internally, this methods return can be structured in whichever way is most
	// convenient for your own applications (or even just create an empty
	//function which returns `nil` if you don't require Dump() either)
	Dump() interface{}
}

// SetHistoryCtrlR - Set the history source triggered with Ctrl-r combination
func (rl *Instance) SetHistoryCtrlR(name string, history History) {
	rl.mainHistName = name
	rl.mainHistory = history
}

// GetHistoryCtrlR - Returns the history source triggered by Ctrl-r
func (rl *Instance) GetHistoryCtrlR() History {
	return rl.mainHistory
}

// SetHistoryAltR - Set the history source triggered with Alt-r combination
func (rl *Instance) SetHistoryAltR(name string, history History) {
	rl.altHistName = name
	rl.altHistory = history
}

// GetHistoryAltR - Returns the history source triggered by Alt-r
func (rl *Instance) GetHistoryAltR() History {
	return rl.altHistory
}

// ExampleHistory is an example of a LineHistory interface:
type ExampleHistory struct {
	items []string
}

// Write to history
func (h *ExampleHistory) Write(s string) (int, error) {
	h.items = append(h.items, s)
	return len(h.items), nil
}

// GetLine returns a line from history
func (h *ExampleHistory) GetLine(i int) (string, error) {
	return h.items[i], nil
}

// Len returns the number of lines in history
func (h *ExampleHistory) Len() int {
	return len(h.items)
}

// Dump returns the entire history
func (h *ExampleHistory) Dump() interface{} {
	return h.items
}

// NullHistory is a null History interface for when you don't want line
// entries remembered eg password input.
type NullHistory struct{}

// Write to history
func (h *NullHistory) Write(s string) (int, error) {
	return 0, nil
}

// GetLine returns a line from history
func (h *NullHistory) GetLine(i int) (string, error) {
	return "", nil
}

// Len returns the number of lines in history
func (h *NullHistory) Len() int {
	return 0
}

// Dump returns the entire history
func (h *NullHistory) Dump() interface{} {
	return []string{}
}

// Browse historic lines:
func (rl *Instance) walkHistory(i int) {
	var (
		old, new string
		dedup    bool
		err      error
	)

	// Work with correct history source (depends on CtrlR/CtrlE)
	var history History
	if !rl.mainHist {
		history = rl.altHistory
	} else {
		history = rl.mainHistory
	}

	// Nothing happens if the history is nil
	if history == nil {
		return
	}

	// When we are exiting the current line buffer to move around
	// the history, we make buffer the current line
	if rl.histOffset == 0 && rl.histOffset + i == 1 {
		rl.lineBuf = string(rl.line)
	}

	rl.histOffset += i
	historyLen := history.Len()
	if rl.histOffset == 0 {
		rl.line = []rune(rl.lineBuf)
		rl.pos = len(rl.lineBuf)
	} else if rl.histOffset <= -1 {
		rl.histOffset = 0
	} else if rl.histOffset > historyLen {
		// TODO: should this wrap around?s
		rl.histOffset = 0
	} else {
		dedup = true
		old = string(rl.line)
		new, err = history.GetLine(historyLen - rl.histOffset)
		if err != nil {
			rl.resetHelpers()
			print("\r\n" + err.Error() + "\r\n")
			print(rl.mainPrompt)
			return
		}

		rl.clearLine()
		rl.line = []rune(new)
		rl.pos = len(rl.line)
		if rl.pos > 0 {
			rl.pos--
		}
	}

	// Update the line, and any helpers
	rl.updateHelpers()

	// In order to avoid having to type j/k twice each time for history navigation,
	// we walk once again. This only ever happens when we aren't out of bounds and the last history item was not a empty string.
	if new != "" && dedup && old == new {
		rl.walkHistory(i)
	}
}

// completeHistory - Populates a CompletionGroup with history and returns it the shell
// we populate only one group, so as to pass it to the main completion engine.
func (rl *Instance) completeHistory() (hist []*CompletionGroup) {

	hist = make([]*CompletionGroup, 1)
	hist[0] = &CompletionGroup{
		DisplayType: TabDisplayMap,
		MaxLength:   10,
	}

	// Switch to completion flux first
	var history History
	if !rl.mainHist {
		if rl.altHistory == nil {
			return
		}
		history = rl.altHistory
		rl.histInfo = []rune(rl.altHistName + ": ")
	} else {
		if rl.mainHistory == nil {
			return
		}
		history = rl.mainHistory
		rl.histInfo = []rune(rl.mainHistName + ": ")
	}

	hist[0].init(rl)

	var (
		line string
		num  string
		err  error
	)

	rl.tcPrefix = string(rl.line) // We use the current full line for filtering

	for i := history.Len() - 1; i >= 1; i-- {
		line, err = history.GetLine(i)
		if err != nil {
			continue
		}

		if !strings.HasPrefix(line, rl.tcPrefix) {
			continue
		}

		line = strings.Replace(line, "\n", ` `, -1)

		if hist[0].Descriptions[line] != "" {
			continue
		}

		hist[0].Suggestions = append(hist[0].Suggestions, line)
		num = strconv.Itoa(i)

		hist[0].Descriptions[line] = "\033[38;5;237m" + num + RESET
	}

	return
}
