package main

import (
	"errors"
	"strings"

	"github.com/nsf/termbox-go"
)

type CliProc struct {
	History     []string
	HistoryIdx  int
	Suggestions []string // Suggestions are 'autocomplete' commands
	Proposals   []string // Proposals are proposed 'next' commands

	Buffer string
	Cursor int

	Complete bool
}

func NewCLI() *CliProc {
	c := CliProc{}
	return &c
}

func (c *CliProc) handleEvent(event termbox.Event) error {
	if c.Complete {
		return nil
	}
	if event.Ch == 0 {
		switch event.Key {
		case termbox.KeyEnter:
			// Execute command
			c.Complete = true

		case termbox.KeySpace:
			c.putInBuffer(" ")

		case termbox.KeyBackspace, termbox.KeyBackspace2:
			if c.Cursor > 0 {
				c.Buffer = c.Buffer[:c.Cursor-1] + c.Buffer[c.Cursor:]
				c.Cursor = c.Cursor - 1
			}
		case termbox.KeyDelete:
			if c.Cursor < len(c.Buffer) {
				c.Buffer = c.Buffer[:c.Cursor] + c.Buffer[c.Cursor+1:]
			}

		case termbox.KeyArrowLeft:
			if c.Cursor > 0 {
				c.Cursor = c.Cursor - 1
			}
		case termbox.KeyArrowRight:
			if c.Cursor < len(c.Buffer) {
				c.Cursor = c.Cursor + 1
			} else {
				// Complete suggestion
			}
		case termbox.KeyHome:
			c.Cursor = 0
		case termbox.KeyEnd:
			c.Cursor = len(c.Buffer)

		case termbox.KeyArrowUp:
			val, err := c.HistoryBack()
			if err != nil {
				return err
			}
			c.Buffer = val
			c.Cursor = len(c.Buffer)

		case termbox.KeyArrowDown:
			val, err := c.HistoryForward()
			if err != nil {
				return err
			}
			c.Buffer = val
			c.Cursor = len(c.Buffer)

		case termbox.KeyTab:
			// Complete suggestion
			args := strings.Split(c.Buffer, " ")
			acText := strings.TrimPrefix(c.GetAutocompleteText(), args[len(args)-1])
			if len(acText) > 0 {
				c.Buffer = c.Buffer + acText
				c.Cursor = len(c.Buffer)
			}

		case termbox.KeyCtrlU:
			// Delete everything from the cursor forward
			c.Buffer = c.Buffer[c.Cursor:]
			c.Cursor = 0
		}
	} else {
		c.putInBuffer(string(event.Ch))
		/*
			if c.Cursor == len(c.Buffer) {
				c.Buffer = c.Buffer + string(event.Ch)
			} else {
				c.Buffer = c.Buffer[:c.Cursor] + string(event.Ch) + c.Buffer[c.Cursor:]
			}
			c.Cursor = c.Cursor + 1
		*/
	}
	return nil
}

func (c *CliProc) putInBuffer(v string) {
	c.Buffer = c.Buffer[:c.Cursor] + v + c.Buffer[c.Cursor:]
	c.Cursor = c.Cursor + 1
}

func (c *CliProc) ClearBuffer() {
	c.Buffer = ""
	c.Cursor = 0
	c.Complete = false
}

func (c *CliProc) GetBuffer() string {
	return c.Buffer
}

func (c *CliProc) HistoryBack() (string, error) {
	if len(c.History) < c.HistoryIdx+1 {
		return "", errors.New("Already at oldest command")
	}
	c.HistoryIdx = c.HistoryIdx + 1
	return c.History[len(c.History)-c.HistoryIdx], nil
}

func (c *CliProc) HistoryForward() (string, error) {
	if c.HistoryIdx == 0 {
		return "", errors.New("Already at most recent command")
	}
	c.HistoryIdx = c.HistoryIdx - 1
	if c.HistoryIdx == 0 {
		return "", nil
	}
	return c.History[len(c.History)-c.HistoryIdx], nil
}

func (c *CliProc) Suggest(cmd string) string {
	allSuggestions := c.GetAllSuggestions(cmd)
	if len(allSuggestions) == 0 {
		return ""
	}
	return allSuggestions[0]
}

func (c *CliProc) GetDisplaySuggestions() []string {
	var ret []string
	v := c.GetAllSuggestions(c.Buffer)
	if len(c.Buffer) > 0 {
		for k := range v {
			if strings.HasPrefix(v[k], c.Buffer) {
				ret = append(ret, v[k])
			}
		}
	} else {
		// Normally, filter out 'drop's
		for k := range v {
			if !strings.HasPrefix(v[k], "drop ") {
				ret = append(ret, v[k])
			}
		}
	}
	return ret
}

func (c *CliProc) GetAllSuggestions(cmd string) []string {
	var ret []string
	args := strings.Fields(cmd)
	if len(args) == 0 {
		// Return all commands
		for _, v := range c.Suggestions {
			ret = append(ret, v)
		}
		return ret
	} else if len(args) == 1 && !strings.HasSuffix(cmd, " ") {
		for _, v := range c.Suggestions {
			if strings.HasPrefix(v, args[0]) {
				ret = append(ret, v)
			}
		}
	}
	return ret
}

func (c *CliProc) Draw(x, y int, bg, fg termbox.Attribute) {
	DrawString("[ "+strings.Join(c.GetDisplaySuggestions(), ", ")+" ]", x, y, bg, fg)
	y = y + 1
	prompt := '>'
	termbox.SetCell(x, y, prompt, bg, fg)
	x = x + 2
	for k := range c.Buffer {
		useFg, useBg := bg, fg
		if k == c.Cursor {
			useFg, useBg = fg, bg
		}
		termbox.SetCell(x+k, y, rune(c.Buffer[k]), useFg, useBg)
	}
	args := strings.Split(c.Buffer, " ")
	acText := strings.TrimPrefix(c.GetAutocompleteText(), args[len(args)-1])
	if len(acText) > 0 {
		for k := range acText {
			useFg, useBg := bg|termbox.AttrBold, fg
			if k == 0 && c.Cursor == len(c.Buffer) {
				useFg, useBg = fg|termbox.AttrBold, bg
			}
			termbox.SetCell(x+len(c.Buffer)+k, y, rune(acText[k]), useFg, useBg)
		}
	} else {
		if c.Cursor == len(c.Buffer) {
			termbox.SetCell(x+len(c.Buffer), y, ' ', fg, bg)
		}
	}
}

func (c *CliProc) GetAutocompleteText() string {
	suggestions := c.GetAllSuggestions(c.Buffer)
	if len(suggestions) == 1 {
		args := strings.Split(c.Buffer, " ")
		return strings.TrimPrefix(suggestions[0], args[len(args)-1])
	} else if len(suggestions) > 1 {
		// Find how many characters we _can_ autocomplete
		var ret string
		for k := range suggestions[0] {
			allHave := true
			for wrk := range suggestions {
				if len(suggestions[wrk]) <= k || suggestions[0][k] != suggestions[wrk][k] {
					allHave = false
					break
				}
			}
			if allHave {
				ret = ret + string(suggestions[0][k])
			}
		}
		return ret
	}
	return ""
}

func (c *CliProc) ClearSuggestions() {
	c.Suggestions = nil
}

func (c *CliProc) AddSuggestion(s string) {
	c.AddSuggestions([]string{s})
}
func (c *CliProc) AddSuggestions(s []string) {
	c.Suggestions = append(c.Suggestions, s...)
}

func (c *CliProc) RemoveSuggestion(s string) {
	var i int
	for i = range c.Suggestions {
		if c.Suggestions[i] == s {
			break
		}
	}
	c.Suggestions = append(c.Suggestions[:i], c.Suggestions[i+1:]...)
}
