package action

import (
	"bytes"

	"github.com/micro-editor/tcell/v2"
	"github.com/zyedidia/micro/v2/internal/buffer"
	"github.com/zyedidia/micro/v2/internal/config"
	"github.com/zyedidia/micro/v2/internal/display"
	"github.com/zyedidia/micro/v2/internal/info"
	"github.com/zyedidia/micro/v2/internal/util"
)

type InfoKeyAction func(*InfoPane)

var InfoBindings *KeyTree
var InfoBufBindings *KeyTree

func init() {
	InfoBindings = NewKeyTree()
	InfoBufBindings = NewKeyTree()
}

func InfoMapEvent(k Event, action string) {
	config.Bindings["command"][k.Name()] = action

	switch e := k.(type) {
	case KeyEvent, KeySequenceEvent, RawEvent:
		infoMapKey(e, action)
	case MouseEvent:
		infoMapMouse(e, action)
	}
}

func infoMapKey(k Event, action string) {
	if f, ok := InfoKeyActions[action]; ok {
		InfoBindings.RegisterKeyBinding(k, InfoKeyActionGeneral(f))
	} else if f, ok := BufKeyActions[action]; ok {
		InfoBufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(f))
	}
}

func infoMapMouse(k MouseEvent, action string) {
	// TODO: map mouse
	if f, ok := BufMouseActions[action]; ok {
		InfoBufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
	} else {
		infoMapKey(k, action)
	}
}

func InfoKeyActionGeneral(a InfoKeyAction) PaneKeyAction {
	return func(p Pane) bool {
		a(p.(*InfoPane))
		return true
	}
}

type InfoPane struct {
	*BufPane
	*info.InfoBuf
}

func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
	ip := new(InfoPane)
	ip.InfoBuf = ib
	ip.BufPane = NewBufPane(ib.Buffer, w, tab)
	ip.BufPane.bindings = InfoBufBindings

	return ip
}

func NewInfoBar() *InfoPane {
	ib := info.NewBuffer()
	w := display.NewInfoWindow(ib)
	return NewInfoPane(ib, w, nil)
}

func (h *InfoPane) Close() {
	h.InfoBuf.Close()
	h.BufPane.Close()
}

func (h *InfoPane) HandleEvent(event tcell.Event) {
	switch e := event.(type) {
	case *tcell.EventResize:
		// TODO
	case *tcell.EventKey:
		ke := keyEvent(e)

		done := h.DoKeyEvent(ke)
		hasYN := h.HasYN
		if e.Key() == tcell.KeyRune && hasYN {
			y := e.Rune() == 'y' || e.Rune() == 'Y'
			n := e.Rune() == 'n' || e.Rune() == 'N'
			if y || n {
				h.YNResp = y
				h.DonePrompt(false)

				InfoBindings.ResetEvents()
				InfoBufBindings.ResetEvents()
			}
		}
		if e.Key() == tcell.KeyRune && !done && !hasYN {
			h.DoRuneInsert(e.Rune())
			done = true
		}
		if done && h.HasPrompt && !hasYN {
			resp := string(h.LineBytes(0))
			hist := h.History[h.PromptType]
			if resp != hist[h.HistoryNum] {
				h.HistoryNum = len(hist) - 1
				hist[h.HistoryNum] = resp
				h.HistorySearch = false
			}
			if h.EventCallback != nil {
				h.EventCallback(resp)
			}
		}
	default:
		h.BufPane.HandleEvent(event)
	}
}

// DoKeyEvent executes a key event for the command bar, doing any overridden actions.
// Returns true if the action was executed OR if there are more keys remaining
// to process before executing an action (if this is a key sequence event).
// Returns false if no action found.
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
	action, more := InfoBindings.NextEvent(e, nil)
	if action != nil && !more {
		action(h)
		InfoBindings.ResetEvents()

		return true
	} else if action == nil && !more {
		InfoBindings.ResetEvents()
		// return false //TODO:?
	}

	if !more {
		// If no infopane action found, try to find a bufpane action.
		//
		// TODO: this is buggy. For example, if the command bar has the following
		// two bindings:
		//
		//   "<Ctrl-x><Ctrl-p>": "HistoryUp",
		//   "<Ctrl-x><Ctrl-v>": "Paste",
		//
		// the 2nd binding (with a bufpane action) doesn't work, since <Ctrl-x>
		// has been already consumed by the 1st binding (with an infopane action).
		//
		// We should either iterate both InfoBindings and InfoBufBindings keytrees
		// together, or just use the same keytree for both infopane and bufpane
		// bindings.
		action, more = InfoBufBindings.NextEvent(e, nil)
		if action != nil && !more {
			action(h.BufPane)
			InfoBufBindings.ResetEvents()
			return true
		} else if action == nil && !more {
			InfoBufBindings.ResetEvents()
		}
	}

	return more
}

// HistoryUp cycles history up
func (h *InfoPane) HistoryUp() {
	h.UpHistory(h.History[h.PromptType])
}

// HistoryDown cycles history down
func (h *InfoPane) HistoryDown() {
	h.DownHistory(h.History[h.PromptType])
}

// HistorySearchUp fetches the previous history item beginning with the text
// in the infobuffer before cursor
func (h *InfoPane) HistorySearchUp() {
	h.SearchUpHistory(h.History[h.PromptType])
}

// HistorySearchDown fetches the next history item beginning with the text
// in the infobuffer before cursor
func (h *InfoPane) HistorySearchDown() {
	h.SearchDownHistory(h.History[h.PromptType])
}

// Autocomplete begins autocompletion
func (h *InfoPane) CommandComplete() {
	b := h.Buf
	if b.HasSuggestions {
		b.CycleAutocomplete(true)
		return
	}

	c := b.GetActiveCursor()
	l := b.LineBytes(0)
	l = util.SliceStart(l, c.X)

	args := bytes.Split(l, []byte{' '})
	cmd := string(args[0])

	if h.PromptType == "Command" {
		if len(args) == 1 {
			b.Autocomplete(CommandComplete)
		} else if action, ok := commands[cmd]; ok {
			if action.completer != nil {
				b.Autocomplete(action.completer)
			}
		}
	} else {
		// by default use filename autocompletion
		b.Autocomplete(buffer.FileComplete)
	}
}

// ExecuteCommand completes the prompt
func (h *InfoPane) ExecuteCommand() {
	if !h.HasYN {
		h.DonePrompt(false)
	}
}

// AbortCommand cancels the prompt
func (h *InfoPane) AbortCommand() {
	h.DonePrompt(true)
}

// InfoKeyActions contains the list of all possible key actions the infopane could execute
var InfoKeyActions = map[string]InfoKeyAction{
	"HistoryUp":         (*InfoPane).HistoryUp,
	"HistoryDown":       (*InfoPane).HistoryDown,
	"HistorySearchUp":   (*InfoPane).HistorySearchUp,
	"HistorySearchDown": (*InfoPane).HistorySearchDown,
	"CommandComplete":   (*InfoPane).CommandComplete,
	"ExecuteCommand":    (*InfoPane).ExecuteCommand,
	"AbortCommand":      (*InfoPane).AbortCommand,
}
