package info

import (
	"fmt"

	"github.com/zyedidia/micro/v2/internal/buffer"
)

// The InfoBuf displays messages and other info at the bottom of the screen.
// It is respresented as a buffer and a message with a style.
type InfoBuf struct {
	*buffer.Buffer

	HasPrompt  bool
	HasMessage bool
	HasError   bool
	HasYN      bool

	PromptType string

	Msg    string
	YNResp bool

	// This map stores the history for all the different kinds of uses Prompt has
	// It's a map of history type -> history array
	History    map[string][]string
	HistoryNum int

	// Is the current message a message from the gutter
	HasGutter bool

	PromptCallback func(resp string, canceled bool)
	EventCallback  func(resp string)
	YNCallback     func(yes bool, canceled bool)
}

// NewBuffer returns a new infobuffer
func NewBuffer() *InfoBuf {
	ib := new(InfoBuf)
	ib.History = make(map[string][]string)

	ib.Buffer = buffer.NewBufferFromString("", "", buffer.BTInfo)
	ib.LoadHistory()

	return ib
}

// Close performs any cleanup necessary when shutting down the infobuffer
func (i *InfoBuf) Close() {
	i.SaveHistory()
}

// Message sends a message to the user
func (i *InfoBuf) Message(msg ...interface{}) {
	// only display a new message if there isn't an active prompt
	// this is to prevent overwriting an existing prompt to the user
	if !i.HasPrompt {
		displayMessage := fmt.Sprint(msg...)
		// if there is no active prompt then style and display the message as normal
		i.Msg = displayMessage
		i.HasMessage, i.HasError = true, false
	}
}

// GutterMessage displays a message and marks it as a gutter message
func (i *InfoBuf) GutterMessage(msg ...interface{}) {
	i.Message(msg...)
	i.HasGutter = true
}

// ClearGutter clears the info bar and unmarks the message
func (i *InfoBuf) ClearGutter() {
	i.HasGutter = false
	i.Message("")
}

// Error sends an error message to the user
func (i *InfoBuf) Error(msg ...interface{}) {
	// only display a new message if there isn't an active prompt
	// this is to prevent overwriting an existing prompt to the user
	if !i.HasPrompt {
		// if there is no active prompt then style and display the message as normal
		i.Msg = fmt.Sprint(msg...)
		i.HasMessage, i.HasError = false, true
	}
	// TODO: add to log?
}

// Prompt starts a prompt for the user, it takes a prompt, a possibly partially filled in msg
// and callbacks executed when the user executes an event and when the user finishes the prompt
// The eventcb passes the current user response as the argument and donecb passes the user's message
// and a boolean indicating if the prompt was canceled
func (i *InfoBuf) Prompt(prompt string, msg string, ptype string, eventcb func(string), donecb func(string, bool)) {
	// If we get another prompt mid-prompt we cancel the one getting overwritten
	if i.HasPrompt {
		i.DonePrompt(true)
	}

	if _, ok := i.History[ptype]; !ok {
		i.History[ptype] = []string{""}
	} else {
		i.History[ptype] = append(i.History[ptype], "")
	}
	i.HistoryNum = len(i.History[ptype]) - 1

	i.PromptType = ptype
	i.Msg = prompt
	i.HasPrompt = true
	i.HasMessage, i.HasError, i.HasYN = false, false, false
	i.HasGutter = false
	i.PromptCallback = donecb
	i.EventCallback = eventcb
	i.Buffer.Insert(i.Buffer.Start(), msg)
}

// YNPrompt creates a yes or no prompt, and the callback returns the yes/no result and whether
// the prompt was canceled
func (i *InfoBuf) YNPrompt(prompt string, donecb func(bool, bool)) {
	if i.HasPrompt {
		i.DonePrompt(true)
	}

	i.Msg = prompt
	i.HasPrompt = true
	i.HasYN = true
	i.HasMessage, i.HasError = false, false
	i.HasGutter = false
	i.YNCallback = donecb
}

// DonePrompt finishes the current prompt and indicates whether or not it was canceled
func (i *InfoBuf) DonePrompt(canceled bool) {
	hadYN := i.HasYN
	i.HasPrompt = false
	i.HasYN = false
	i.HasGutter = false
	if !hadYN {
		if i.PromptCallback != nil {
			if canceled {
				i.Replace(i.Start(), i.End(), "")
				i.PromptCallback("", true)
				h := i.History[i.PromptType]
				i.History[i.PromptType] = h[:len(h)-1]
			} else {
				resp := string(i.LineBytes(0))
				i.Replace(i.Start(), i.End(), "")
				i.PromptCallback(resp, false)
				h := i.History[i.PromptType]
				h[len(h)-1] = resp

				// avoid duplicates
				for j := len(h) - 2; j >= 0; j-- {
					if h[j] == h[len(h)-1] {
						i.History[i.PromptType] = append(h[:j], h[j+1:]...)
						break
					}
				}
			}
			// i.PromptCallback = nil
		}
	}
	if i.YNCallback != nil && hadYN {
		i.YNCallback(i.YNResp, canceled)
	}
}

// Reset resets the infobuffer's msg and info
func (i *InfoBuf) Reset() {
	i.Msg = ""
	i.HasPrompt, i.HasMessage, i.HasError = false, false, false
	i.HasGutter = false
}
