package qline

// qline is a line input library for terminals that utilize
// vt100 compatible escape sequences
//
// Copyright © 2021 Brian Evans
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the “Software”), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
// KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

const (
	UpArrow rune = iota - 20
	DownArrow
	LeftArrow
	RightArrow
	Delete
	Home
	End
	PageUp
	PageDown
	Escape rune = 27
	NewLine rune = 10
	CarriageReturn rune = 13
	BackSpace rune = 127
)

var (
	width int
)

type buffer struct {
	buf []rune
	cursor int
	maxWidth int
	offset int
	cursorStart int
	prompt string
}

func GetInput(prompt string, content string, cols int) string {
	b := buffer{make([]rune, 0, (len(content)+1)*2), 0, cols-len(prompt), 0, 0, prompt}

	var ch rune
	var err error
	reader := bufio.NewReader(os.Stdin)

	// b.seedContent(content)
	// b.printBuf()

	for {
		ch, err = ReadKey(reader)
		if err != nil {
			continue
		}
		if ch == CarriageReturn || ch == NewLine {
			break
		}

		if isControl(ch) {
			b.controlInput(ch)
		} else {
			b.addChar(ch, true)
		}
	}
	return b.string()
}


func (lb buffer) string() string {
	return string(lb.buf)
}

func (lb *buffer) deleteChar() {
	if lb.cursor == len(lb.buf) {
		return
	} else if lb.cursor == len(lb.buf)-1 {
		lb.buf = lb.buf[:len(lb.buf)-1]
	} else {
		lb.buf = append(lb.buf[:lb.cursor], lb.buf[lb.cursor+1:]...)
	}
	if lb.offset > 0 {
		lb.offset--
	}
}

func (lb *buffer) addChar(c rune, echo bool) {
	if c < 9 || (c > 10 && c < 13) || (c > 13 && c < 32) {
		return
	}
	if lb.cursor == len(lb.buf) {
		lb.buf = append(lb.buf, c)
		lb.cursor++
	} else {
		lb.buf = append(lb.buf[:lb.cursor+1], lb.buf[lb.cursor:]...)
		lb.buf[lb.cursor] = c
		lb.cursor++
	}
	if lb.cursor - lb.offset > lb.maxWidth {
		lb.offset++
	}
	if echo {
		lb.printBuf()
	}
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func (lb buffer) printBuf() {
	out := lb.buf[lb.offset:min(len(lb.buf), lb.offset+lb.maxWidth)]
	fmt.Printf("\r%s%s\033[0K\r\033[%dC", lb.prompt, string(out), lb.cursor - lb.offset + len(lb.prompt))
}

func (lb *buffer) controlInput(c rune) {
	switch c {
	case Delete:
		lb.deleteChar()
	case BackSpace:
		if lb.cursor > 0 {
			lb.cursor--
			lb.deleteChar()
		}
	case LeftArrow:
		if lb.offset > 0 && lb.cursor - lb.offset == 0 {
			lb.offset--
		}
		if lb.cursor > 0 {
			lb.cursor--
		}
	case RightArrow:
		// This is still mildly funky, but works enough
		for ;lb.cursor - lb.offset >= lb.maxWidth && lb.cursor < len(lb.buf); {
			lb.offset++
		}
		if lb.cursor < len(lb.buf) {
			lb.cursor++
		}
	case Home:
		lb.offset = 0
		lb.cursor = 0
	case End:
		lb.cursor = len(lb.buf)
		lb.offset = max(lb.cursor - lb.maxWidth, 0)
	}
	lb.printBuf()
}

func (lb *buffer) seedContent(s string) {
	for _, r := range s {
		lb.addChar(r, false)
	}
}

func parseCursorPosition(esc string) (int, int, error) {
	var row, col int
	r := strings.NewReader(esc)
	_, err := fmt.Fscanf(r, "\033[%d;%dR", &row, &col)
	return row, col, err
}


func ReadKey(reader *bufio.Reader) (rune, error) {
	char, _, err := reader.ReadRune()
	if err != nil {
		return 0, err
	}
	avail := reader.Buffered()
	if char == Escape && avail > 0 {
		var b strings.Builder
		b.WriteRune(27)
		for ; avail > 0; avail-- {
			c, _, e := reader.ReadRune()
			if e != nil {
				break
			}
			b.WriteRune(c)
		}
		escSeq := b.String()
		switch true {
		case escSeq == "\033[A":
			char = UpArrow
		case escSeq == "\033[B":
			char = DownArrow
		case escSeq == "\033[C":
			char = RightArrow
		case escSeq == "\033[D":
			char = LeftArrow
		case escSeq == "\033[5~":
			char = PageUp
		case escSeq == "\033[6~":
			char = PageDown
		case isHomeKey(escSeq):
			char = Home
		case isEndKey(escSeq):
			char = End
		case isDeleteKey(escSeq):
			char = Delete
		case escSeq[len(escSeq)-1] == 'R':
			// This is a request for cursor position
			_, cols, err := parseCursorPosition(escSeq)
			if err == nil {
				err = fmt.Errorf("response")
			}
			return rune(cols), err
		}
	}
	return char, nil
}

func isControl(c rune) bool {
	switch c {
		case UpArrow, DownArrow, RightArrow, LeftArrow, PageUp, PageDown, Home, End, Delete, BackSpace, CarriageReturn, NewLine, Escape:
			return true
		default:
			return false
	}
}

func isDeleteKey(seq string) bool {
	switch seq {
	case "\033[3~", "\033[P":
		return true
	default:
		return false
	}
}

func isHomeKey(seq string) bool {
	switch seq {
	case "\033[1~", "\033[7~", "\033[H", "\033OH":
		return true
	default:
		return false
	}
}

func isEndKey(seq string) bool {
	switch seq {
	case "\033[4~", "\033[8~", "\033[F", "\033OF":
		return true
	default:
		return false
	}
}

