package main

import (
	"fmt"
	"strings"
	"unicode"

	"github.com/jroimartin/gocui"
)

type CommandFunc func(*gocui.Gui, *gocui.View) error

var COMMANDS map[string]func(string, *App) CommandFunc = map[string]func(string, *App) CommandFunc{
	"submit": func(_ string, a *App) CommandFunc {
		return a.SubmitRequest
	},
	"save": func(_ string, a *App) CommandFunc {
		return a.OpenSaveDialog
	},
	"history": func(_ string, a *App) CommandFunc {
		return a.ToggleHistory
	},
	"quit": func(_ string, _ *App) CommandFunc {
		return quit
	},
	"focus": func(args string, a *App) CommandFunc {
		return func(g *gocui.Gui, _ *gocui.View) error {
			return a.setViewByName(g, args)
		}
	},
	"nextView": func(_ string, a *App) CommandFunc {
		return a.NextView
	},
	"prevView": func(_ string, a *App) CommandFunc {
		return a.PrevView
	},
	"scrollDown": func(_ string, _ *App) CommandFunc {
		return scrollViewDown
	},
	"scrollUp": func(_ string, _ *App) CommandFunc {
		return scrollViewUp
	},
	"pageDown": func(_ string, _ *App) CommandFunc {
		return pageDown
	},
	"pageUp": func(_ string, _ *App) CommandFunc {
		return pageUp
	},
	"deleteLine": func(_ string, _ *App) CommandFunc {
		return deleteLine
	},
	"deleteWord": func(_ string, _ *App) CommandFunc {
		return deleteWord
	},
}

func scrollView(v *gocui.View, dy int) error {
	v.Autoscroll = false
	ox, oy := v.Origin()
	if oy+dy < 0 {
		dy = -oy
	}
	if _, err := v.Line(dy); dy > 0 && err != nil {
		dy = 0
	}
	v.SetOrigin(ox, oy+dy)
	return nil
}

func scrollViewUp(_ *gocui.Gui, v *gocui.View) error {
	return scrollView(v, -1)
}

func scrollViewDown(_ *gocui.Gui, v *gocui.View) error {
	return scrollView(v, 1)
}

func pageUp(_ *gocui.Gui, v *gocui.View) error {
	_, height := v.Size()
	scrollView(v, -height*2/3)
	return nil
}

func pageDown(_ *gocui.Gui, v *gocui.View) error {
	_, height := v.Size()
	scrollView(v, height*2/3)
	return nil
}

func deleteLine(_ *gocui.Gui, v *gocui.View) error {
	if !v.Editable {
		return nil
	}
	_, curY := v.Cursor()
	_, oY := v.Origin()
	currentLine := curY + oY
	viewLines := strings.Split(strings.TrimSpace(v.Buffer()), "\n")
	if currentLine >= len(viewLines) {
		return nil
	}
	v.Clear()
	if currentLine > 0 {
		fmt.Fprintln(v, strings.Join(viewLines[:currentLine], "\n"))
	}
	fmt.Fprint(v, strings.Join(viewLines[currentLine+1:], "\n"))
	v.SetCursor(0, currentLine)
	v.SetOrigin(0, oY)
	return nil
}

func deleteWord(_ *gocui.Gui, v *gocui.View) error {
	cX, cY := v.Cursor()
	oX, _ := v.Origin()
	cX = cX - 1 + oX
	line, err := v.Line(cY)
	if err != nil || line == "" || cX < 0 {
		return nil
	}
	if cX >= len(line) {
		cX = len(line) - 1
	}
	origCharCateg := getCharCategory(rune(line[cX]))
	v.EditDelete(true)
	cX -= 1
	for cX >= 0 {
		c := rune(line[cX])
		if origCharCateg != getCharCategory(c) {
			break
		}
		v.EditDelete(true)
		cX -= 1
	}
	return nil
}

func getCharCategory(chr rune) int {
	switch {
	case unicode.IsDigit(chr):
		return 0
	case unicode.IsLetter(chr):
		return 1
	case unicode.IsSpace(chr):
		return 2
	case unicode.IsPunct(chr):
		return 3
	}
	return int(chr)
}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrQuit
}
