package stario

import (
	"bufio"
	"fmt"
	"golang.org/x/crypto/ssh/terminal"
	"os"
	"runtime"
	"strconv"
	"strings"
)

type InputMsg struct {
	msg             string
	err             error
	skipSliceSigErr bool
}

func Passwd(hint string, defaultVal string) InputMsg {
	return passwd(hint, defaultVal, "", false)
}

func PasswdWithMask(hint string, defaultVal string, mask string) InputMsg {
	return passwd(hint, defaultVal, mask, false)
}

func PasswdResponseSignal(hint string, defaultVal string) InputMsg {
	return passwd(hint, defaultVal, "", true)
}

func PasswdResponseSignalWithMask(hint string, defaultVal string, mask string) InputMsg {
	return passwd(hint, defaultVal, mask, true)
}

func MessageBoxRaw(hint string, defaultVal string) InputMsg {
	return messageBox(hint, defaultVal)
}

func messageBox(hint string, defaultVal string) InputMsg {
	var ioBuf []rune
	if hint != "" {
		fmt.Print(hint)
	}
	if strings.Index(hint, "\n") >= 0 {
		hint = strings.TrimSpace(hint[strings.LastIndex(hint, "\n"):])
	}
	fd := int(os.Stdin.Fd())
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		return InputMsg{msg: "", err: err}
	}
	defer fmt.Println()
	defer terminal.Restore(fd, state)
	inputReader := bufio.NewReader(os.Stdin)
	for {
		b, _, err := inputReader.ReadRune()
		if err != nil {
			return InputMsg{msg: "", err: err}
		}
		if b == 0x0d {
			strValue := strings.TrimSpace(string(ioBuf))
			if len(strValue) == 0 {
				strValue = defaultVal
			}
			return InputMsg{msg: strValue, err: err}
		}
		if b == 0x08 || b == 0x7F {
			if len(ioBuf) > 0 {
				ioBuf = ioBuf[:len(ioBuf)-1]
			}
			fmt.Print("\r")
			for i := 0; i < len(ioBuf)+2+len(hint); i++ {
				fmt.Print(" ")
			}
		} else {
			ioBuf = append(ioBuf, b)
		}
		fmt.Print("\r")
		if hint != "" {
			fmt.Print(hint)
		}
		fmt.Print(string(ioBuf))
	}
}

func isSiganl(s rune) bool {
	switch s {
	case 0x03, 0x1a, 0x1c:
		return true
	default:
		return false
	}
}

func passwd(hint string, defaultVal string, mask string, handleSignal bool) InputMsg {
	var ioBuf []rune
	if hint != "" {
		fmt.Print(hint)
	}
	if strings.Index(hint, "\n") >= 0 {
		hint = strings.TrimSpace(hint[strings.LastIndex(hint, "\n"):])
	}
	fd := int(os.Stdin.Fd())
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		return InputMsg{msg: "", err: err}
	}
	defer fmt.Println()
	defer terminal.Restore(fd, state)
	inputReader := bufio.NewReader(os.Stdin)
	for {
		b, _, err := inputReader.ReadRune()
		if err != nil {
			return InputMsg{msg: "", err: err}
		}
		if handleSignal && isSiganl(b) {
			if runtime.GOOS != "windows" {
				terminal.Restore(fd, state)
			}
			if err := signal(b); err != nil {
				return InputMsg{
					msg: "",
					err: err,
				}
			}
		}
		if b == 0x0d {
			strValue := strings.TrimSpace(string(ioBuf))
			if len(strValue) == 0 {
				strValue = defaultVal
			}
			return InputMsg{msg: strValue, err: err}
		}
		if b == 0x08 || b == 0x7F {
			if len(ioBuf) > 0 {
				ioBuf = ioBuf[:len(ioBuf)-1]
			}
			fmt.Print("\r")
			for i := 0; i < len(ioBuf)+2+len(hint); i++ {
				fmt.Print(" ")
			}
		} else {
			ioBuf = append(ioBuf, b)
		}
		fmt.Print("\r")
		if hint != "" {
			fmt.Print(hint)
		}
		for i := 0; i < len(ioBuf); i++ {
			fmt.Print(mask)
		}
	}
}

func MessageBox(hint string, defaultVal string) InputMsg {
	if hint != "" {
		fmt.Print(hint)
	}
	inputReader := bufio.NewReader(os.Stdin)
	str, err := inputReader.ReadString('\n')
	if err != nil {
		return InputMsg{msg: str, err: err}
	}
	str = strings.TrimSpace(str)
	if len(str) == 0 {
		str = defaultVal
	}
	return InputMsg{msg: str, err: err}
}

func (im InputMsg) IgnoreSliceParseError(i bool) InputMsg {
	im.skipSliceSigErr = i
	return im
}

func (im InputMsg) String() (string, error) {
	if im.err != nil {
		return "", im.err
	}
	return im.msg, nil
}

func (im InputMsg) MustString() string {
	res, _ := im.String()
	return res
}

func (im InputMsg) SliceString(sep string) ([]string, error) {
	if im.err != nil {
		return nil, im.err
	}
	return strings.Split(im.msg, sep), nil
}

func (im InputMsg) MustSliceString(sep string) []string {
	res, _ := im.SliceString(sep)
	return res
}

func (im InputMsg) sliceFn(sep string, fn func(string) (interface{}, error)) ([]interface{}, error) {
	var res []interface{}
	data, err := im.SliceString(sep)
	if err != nil {
		return res, err
	}
	for _, v := range data {
		code, err := fn(v)
		if err != nil && !im.skipSliceSigErr {
			return nil, err
		} else if err == nil {
			res = append(res, code)
		}
	}
	return res, nil
}

func (im InputMsg) Int() (int, error) {
	if im.err != nil {
		return 0, im.err
	}
	return strconv.Atoi(im.msg)
}

func (im InputMsg) SliceInt(sep string) ([]int, error) {
	data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
		return strconv.Atoi(v)
	})
	var res []int
	for _, v := range data {
		res = append(res, v.(int))
	}
	return res, err
}

func (im InputMsg) MustSliceInt(sep string) []int {
	res, _ := im.SliceInt(sep)
	return res
}

func (im InputMsg) MustInt() int {
	res, _ := im.Int()
	return res
}

func (im InputMsg) Int64() (int64, error) {
	if im.err != nil {
		return 0, im.err
	}
	return strconv.ParseInt(im.msg, 10, 64)
}

func (im InputMsg) MustInt64() int64 {
	res, _ := im.Int64()
	return res
}

func (im InputMsg) SliceInt64(sep string) ([]int64, error) {
	data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
		return strconv.ParseInt(v, 10, 64)
	})
	var res []int64
	for _, v := range data {
		res = append(res, v.(int64))
	}
	return res, err
}

func (im InputMsg) MustSliceInt64(sep string) []int64 {
	res, _ := im.SliceInt64(sep)
	return res
}

func (im InputMsg) Uint64() (uint64, error) {
	if im.err != nil {
		return 0, im.err
	}
	return strconv.ParseUint(im.msg, 10, 64)
}

func (im InputMsg) MustUint64() uint64 {
	res, _ := im.Uint64()
	return res
}
func (im InputMsg) SliceUint64(sep string) ([]uint64, error) {
	data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
		return strconv.ParseUint(v, 10, 64)
	})
	var res []uint64
	for _, v := range data {
		res = append(res, v.(uint64))
	}
	return res, err
}

func (im InputMsg) MustSliceUint64(sep string) []uint64 {
	res, _ := im.SliceUint64(sep)
	return res
}

func (im InputMsg) Bool() (bool, error) {
	if im.err != nil {
		return false, im.err
	}
	return strconv.ParseBool(im.msg)
}

func (im InputMsg) MustBool() bool {
	res, _ := im.Bool()
	return res
}

func (im InputMsg) SliceBool(sep string) ([]bool, error) {
	data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
		return strconv.ParseBool(v)
	})
	var res []bool
	for _, v := range data {
		res = append(res, v.(bool))
	}
	return res, err
}

func (im InputMsg) MustSliceBool(sep string) []bool {
	res, _ := im.SliceBool(sep)
	return res
}

func (im InputMsg) Float64() (float64, error) {
	if im.err != nil {
		return 0, im.err
	}
	return strconv.ParseFloat(im.msg, 64)
}

func (im InputMsg) MustFloat64() float64 {
	res, _ := im.Float64()
	return res
}

func (im InputMsg) SliceFloat64(sep string) ([]float64, error) {
	data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
		return strconv.ParseFloat(v, 64)
	})
	var res []float64
	for _, v := range data {
		res = append(res, v.(float64))
	}
	return res, err
}

func (im InputMsg) MustSliceFloat64(sep string) []float64 {
	res, _ := im.SliceFloat64(sep)
	return res
}

func (im InputMsg) Float32() (float32, error) {
	if im.err != nil {
		return 0, im.err
	}
	res, err := strconv.ParseFloat(im.msg, 32)
	return float32(res), err
}

func (im InputMsg) MustFloat32() float32 {
	res, _ := im.Float32()
	return res
}

func (im InputMsg) SliceFloat32(sep string) ([]float32, error) {
	data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
		return strconv.ParseFloat(v, 32)
	})
	var res []float32
	for _, v := range data {
		res = append(res, v.(float32))
	}
	return res, err
}

func (im InputMsg) MustSliceFloat32(sep string) []float32 {
	res, _ := im.SliceFloat32(sep)
	return res
}

func YesNo(hint string, defaults bool) bool {
	for {
		res := strings.ToUpper(MessageBox(hint, "").MustString())
		if res == "" {
			return defaults
		}
		res = res[0:1]
		if res == "Y" {
			return true
		} else if res == "N" {
			return false
		}
	}
}

func StopUntil(hint string, trigger string, repeat bool) error {
	pressLen := len([]rune(trigger))
	if trigger == "" {
		pressLen = 1
	}
	fd := int(os.Stdin.Fd())
	if hint != "" {
		fmt.Print(hint)
	}
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		return err
	}
	defer terminal.Restore(fd, state)
	inputReader := bufio.NewReader(os.Stdin)
	//ioBuf := make([]byte, pressLen)
	i := 0
	for {
		b, _, err := inputReader.ReadRune()
		if err != nil {
			return err
		}
		if trigger == "" {
			break
		}
		if b == []rune(trigger)[i] {
			i++
			if i == pressLen {
				break
			}
			continue
		}
		i = 0
		if hint != "" && repeat {
			fmt.Print("\r\n")
			fmt.Print(hint)
		}
	}
	return nil
}
