package commander

import (
	"fmt"
	"io"
	"time"

	"github.com/diamondburned/cchat"
	"github.com/diamondburned/cchat-gtk/internal/gts"
	"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
	"github.com/diamondburned/cchat-gtk/internal/ui/primitives/autoscroll"
	"github.com/diamondburned/cchat-gtk/internal/ui/primitives/completion"
	"github.com/diamondburned/cchat-gtk/internal/ui/primitives/scrollinput"
	"github.com/gotk3/gotk3/gdk"
	"github.com/gotk3/gotk3/gtk"
	"github.com/gotk3/gotk3/pango"
	"github.com/pkg/errors"
)

var monospace = primitives.PrepareCSS(`
	* {
		font-family: monospace;
		border-radius: 0;
	}
`)

var commandPadding = primitives.PrepareCSS(`
	* { padding: 8px 12px; }
`)

type Session struct {
	*gtk.Box

	cmder  cchat.Commander
	buffer *Buffer
	cmplt  *completer

	inputbuf *gtk.TextBuffer

	// words []string
	// index int
}

func SpawnDialog(buf *Buffer) {
	s := NewSession(buf.cmder, buf)
	s.Show()

	h, _ := gtk.HeaderBarNew()
	h.SetTitle(fmt.Sprintf(
		"Commander: %s on %s",
		buf.cmder.Name().Content, buf.svcname,
	))
	h.SetShowCloseButton(true)
	h.Show()

	d, _ := gts.NewEmptyModalDialog()
	d.SetDefaultSize(450, 250)
	d.SetTitlebar(h)
	d.Add(s)
	d.Show()
}

func NewSession(cmder cchat.Commander, buf *Buffer) *Session {
	view, _ := gtk.TextViewNewWithBuffer(buf.TextBuffer)
	view.SetEditable(false)
	view.SetProperty("monospace", true)
	view.SetPixelsAboveLines(1)
	view.SetWrapMode(gtk.WRAP_WORD_CHAR)
	view.Show()

	scroll := autoscroll.NewScrolledWindow()
	scroll.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
	scroll.Add(view)
	scroll.Show()

	input, _ := gtk.TextViewNew()
	input.SetSizeRequest(-1, 35) // magic height 35px
	primitives.AttachCSS(input, monospace)
	input.Show()

	inputbuf, _ := input.GetBuffer()

	inputscroll := scrollinput.NewH(input)
	inputscroll.Show()

	sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_HORIZONTAL)
	sep.Show()

	b, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
	b.PackStart(scroll, true, true, 0)
	b.PackStart(sep, false, false, 0)
	b.PackStart(inputscroll, false, false, 0)

	session := &Session{
		Box:      b,
		cmder:    cmder,
		buffer:   buf,
		cmplt:    newCompleter(input, cmder),
		inputbuf: inputbuf,
	}

	input.Connect("key-press-event", session.inputActivate)
	input.GrabFocus()

	primitives.AddClass(b, "commander")
	primitives.AddClass(view, "command-buffer")
	primitives.AddClass(input, "command-input")
	primitives.AttachCSS(view, commandPadding)
	primitives.AttachCSS(input, commandPadding)

	return session
}

func (s *Session) inputActivate(v *gtk.TextView, ev *gdk.Event) bool {
	// If the keypress is not enter, then ignore.
	if kev := gdk.EventKeyNewFromEvent(ev); kev.KeyVal() != gdk.KEY_Return {
		return false
	}

	// If the input is empty, then ignore.
	if len(s.cmplt.Words) == 0 {
		return true
	}

	r, err := s.cmder.RunCommand(s.cmplt.Words)
	if err != nil {
		s.buffer.WriteError(err)
		return true
	}

	// Clear the entry.
	s.inputbuf.Delete(s.inputbuf.GetBounds())

	var then = time.Now()
	s.buffer.Printlnf("%s: Running command...", then.Format(time.Kitchen))

	go func() {
		_, err := io.Copy(s.buffer, r)
		r.Close()

		gts.ExecAsync(func() {
			if err != nil {
				s.buffer.WriteError(errors.Wrap(err, "Internal error"))
			}

			var now = time.Now()
			s.buffer.Printlnf(
				"%s: Finished running command, took %s.",
				now.Format(time.Kitchen),
				now.Sub(then).String(),
			)
		})
	}()

	return true
}

type completer struct {
	*completion.Completer

	completer cchat.CommandCompleter
	choices   []string
}

func newCompleter(input *gtk.TextView, v cchat.Commander) *completer {
	completer := &completer{}
	completer.Completer = completion.NewCompleter(input, completer)

	c, ok := v.(cchat.CommandCompleter)
	if ok {
		completer.completer = c
	}

	return completer
}

func (c *completer) Update(words []string, offset int) []gtk.IWidget {
	if c.completer == nil {
		return nil
	}

	c.choices = c.completer.CompleteCommand(words, offset)
	var widgets = make([]gtk.IWidget, 0, len(c.choices))

	for _, choice := range c.choices {
		l, _ := gtk.LabelNew(choice)
		l.SetXAlign(0)
		l.SetEllipsize(pango.ELLIPSIZE_END)
		primitives.AttachCSS(l, monospace)
		l.Show()

		widgets = append(widgets, l)
	}

	return widgets
}

func (c *completer) Word(i int) string {
	return c.choices[i]
}
