package input

import (
	"github.com/diamondburned/cchat"
	"github.com/diamondburned/cchat-gtk/internal/log"
	"github.com/diamondburned/cchat-gtk/internal/ui/messages/input/completion"
	"github.com/gotk3/gotk3/gtk"
	"github.com/pkg/errors"
)

// Controller is an interface to control message containers.
type Controller interface {
	AddPresendMessage(msg PresendMessage) (onErr func(error))
	LatestMessageFrom(userID string) (messageID string, ok bool)
}

type InputView struct {
	*gtk.Box
	*Field
	Completer *completion.View
}

func NewView(ctrl Controller) *InputView {
	text, _ := gtk.TextViewNew()
	text.SetSensitive(false)
	text.SetWrapMode(gtk.WRAP_WORD_CHAR)
	text.SetProperty("top-margin", inputmargin)
	text.SetProperty("left-margin", inputmargin)
	text.SetProperty("right-margin", inputmargin)
	text.SetProperty("bottom-margin", inputmargin)
	text.Show()

	// Bind the text event handler to text first.
	c := completion.New(text)
	c.Show()

	// Bind the input callback later.
	f := NewField(text, ctrl)
	f.Show()

	b, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
	b.PackStart(c, false, true, 0)
	b.PackStart(f, false, false, 0)
	b.Show()

	// Connect to the field's revealer. On resize, we want the autocompleter to
	// have the right padding too.
	f.username.Connect("size-allocate", func(w gtk.IWidget) {
		// Set the autocompleter's left margin to be the same.
		c.SetMarginStart(w.ToWidget().GetAllocatedWidth())
	})

	return &InputView{b, f, c}
}

func (v *InputView) SetSender(session cchat.Session, sender cchat.ServerMessageSender) {
	v.Field.SetSender(session, sender)

	// Ignore ok; completer can be nil.
	completer, _ := sender.(cchat.ServerMessageSendCompleter)
	v.Completer.SetCompleter(completer)
}

type Field struct {
	*gtk.Box
	username *usernameContainer

	TextScroll *gtk.ScrolledWindow
	text       *gtk.TextView
	buffer     *gtk.TextBuffer

	UserID string
	Sender cchat.ServerMessageSender
	editor cchat.ServerMessageEditor

	ctrl Controller

	// editing state
	editingID string // never empty
}

const inputmargin = 4

func NewField(text *gtk.TextView, ctrl Controller) *Field {
	username := newUsernameContainer()
	username.Show()

	buf, _ := text.GetBuffer()

	sw, _ := gtk.ScrolledWindowNew(nil, nil)
	sw.Add(text)
	sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
	sw.SetProperty("propagate-natural-height", true)
	sw.SetProperty("max-content-height", 150)
	sw.Show()

	box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
	box.PackStart(username, false, false, 0)
	box.PackStart(sw, true, true, 0)
	box.Show()

	field := &Field{
		Box:        box,
		username:   username,
		TextScroll: sw,
		text:       text,
		buffer:     buf,
		ctrl:       ctrl,
	}

	text.SetFocusHAdjustment(sw.GetHAdjustment())
	text.SetFocusVAdjustment(sw.GetVAdjustment())
	text.Connect("key-press-event", field.keyDown)

	return field
}

// Reset prepares the field before SetSender() is called.
func (f *Field) Reset() {
	// Paranoia.
	f.text.SetSensitive(false)

	f.UserID = ""
	f.Sender = nil
	f.editor = nil
	f.username.Reset()

	// reset the input
	f.buffer.Delete(f.buffer.GetBounds())
}

// SetSender changes the sender of the input field. If nil, the input will be
// disabled. Reset() should be called first.
func (f *Field) SetSender(session cchat.Session, sender cchat.ServerMessageSender) {
	// Update the left username container in the input.
	f.username.Update(session, sender)
	f.UserID = session.ID()

	// Set the sender.
	if sender != nil {
		f.Sender = sender
		f.text.SetSensitive(true)

		// Allow editor to be nil.
		ed, ok := sender.(cchat.ServerMessageEditor)
		if !ok {
			log.Printlnf("Editor is not implemented for %T", sender)
		}
		f.editor = ed
	}
}

// Editable returns whether or not the input field can be edited.
func (f *Field) Editable(msgID string) bool {
	return f.editor != nil && f.editor.MessageEditable(msgID)
}

func (f *Field) StartEditing(msgID string) bool {
	// Do we support message editing? If not, exit.
	if !f.Editable(msgID) {
		return false
	}

	// Try and request the old message content for editing.
	content, err := f.editor.RawMessageContent(msgID)
	if err != nil {
		// TODO: show error
		log.Error(errors.Wrap(err, "Failed to get message content"))
		return false
	}

	// Set the current editing state and set the input after requesting the
	// content.
	f.editingID = msgID
	f.buffer.SetText(content)

	return true
}

// StopEditing cancels the current editing message. It returns a false and does
// nothing if the editor is not editing anything.
func (f *Field) StopEditing() bool {
	if f.editingID == "" {
		return false
	}

	f.editingID = ""
	f.clearText()

	return true
}

// yankText cuts the text from the input field and returns it.
func (f *Field) yankText() string {
	start, end := f.buffer.GetBounds()

	text, _ := f.buffer.GetText(start, end, false)
	if text != "" {
		f.buffer.Delete(start, end)
	}

	return text
}

// clearText wipes the input field
func (f *Field) clearText() {
	f.buffer.Delete(f.buffer.GetBounds())
}

// getText returns the text from the input, but it doesn't cut it.
func (f *Field) getText() string {
	start, end := f.buffer.GetBounds()
	text, _ := f.buffer.GetText(start, end, false)
	return text
}

func (f *Field) textLen() int {
	return f.buffer.GetCharCount()
}