package main

import (
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
)

const (
	_name      = `([A-Za-z_]\w*)`
	_nameOrInt = "(" + _name + `|\d+)`
	_index     = "(" + _nameOrInt + "|:|" + _nameOrInt + ":|:" + _nameOrInt + "|" + _nameOrInt + ":" + _nameOrInt + ")"
)

var (
	nameExp          = regexp.MustCompile("^" + _name + "$")
	nameWithIndexExp = regexp.MustCompile("^" + _name + `((\[` + _index + `])*\[` + _index + `\\])?$`)
)

func isNumber(val value) bool {
	if _, ok := val.(*_number); ok {
		return true
	}
	s := val.String()
	if len(s) > 0 && s[0] == '"' {
		s = s[1:]
	}
	_, err := strconv.ParseFloat(s, 64)
	return err == nil
}

func toNumber(val value) float64 {
	if val, ok := val.(*_number); ok {
		return val.Value()
	}
	s := val.String()
	if len(s) > 0 && s[0] == '"' {
		s = s[1:]
	}
	if num, err := strconv.ParseFloat(s, 64); err == nil {
		return num
	}
	panic(fmt.Errorf("number: %s (%s)", errInvalidNumber, s))
}

func toInt(val value, envs []environ) int {
	if isName(val) {
		if newVal, err := getValue(val.String(), envs); err == nil {
			val = newVal
			if val, ok := val.(*_number); ok {
				num := val.Value()
				if num != math.Floor(num) {
					panic(fmt.Errorf("int: %s (%f)", errInvalidInteger, num))
				}
				return int(num)
			}
		}
	}
	s := val.String()
	if len(s) > 0 && s[0] == '"' {
		s = s[1:]
	}
	if num, err := strconv.ParseInt(s, 10, 64); err == nil {
		return int(num)
	}
	panic(fmt.Errorf("int: %s (%s)", errInvalidInteger, s))
}

func isBool(val value) bool {
	if _, ok := val.(*_bool); ok {
		return true
	}
	if val, ok := val.(*_number); ok && (val.Value() == 0 || val.Value() == 1) {
		return true
	}
	s := val.String()
	if len(s) > 0 && s[0] == '"' {
		s = s[1:]
	}
	return s == "false" || s == "true"
}

func toBool(val value) bool {
	if val, ok := val.(*_bool); ok {
		return val.Value()
	}
	if val, ok := val.(*_number); ok && (val.Value() == 0 || val.Value() == 1) {
		return val.Value() == 1
	}
	s := val.String()
	if len(s) > 0 && s[0] == '"' {
		s = s[1:]
	}
	if s == "false" || s == "true" {
		return s == "true"
	}
	panic(fmt.Errorf("bool: %s (%s)", errInvalidBool, s))
}

func isWord(val value) bool {
	if _, ok := val.(*_word); ok {
		return true
	}
	if val, ok := val.(*_number); ok && len(val.word) > 0 {
		return true
	}
	if val, ok := val.(*_bool); ok && len(val.word) > 0 {
		return true
	}
	s := val.String()
	return len(s) > 0 && s[0] == '"'
}

func toWord(val value) string {
	if val, ok := val.(*_word); ok {
		return val.Value()
	}
	if val, ok := val.(*_number); ok && len(val.word) > 0 {
		return val.word
	}
	if val, ok := val.(*_bool); ok && len(val.word) > 0 {
		return val.word
	}
	s := val.String()
	if len(s) > 0 && s[0] == '"' {
		return parseWord(s).Value()
	}
	panic(fmt.Errorf("word: %s (%s)", errInvalidWord, s))
}

func parseWord(s string) *_word {
	if len(s) > 0 && s[0] == '"' {
		s = s[1:]
		if len(s) > 0 {
			if s[0] == '[' {
				s = `\[` + s[1:]
			}
			if s[len(s)-1] == ']' {
				s = s[:len(s)-1]
				escape := len(s) - len(strings.TrimRight(s, `\`))
				if (escape & 1) == 0 {
					s += `\`
				}
				s += "]"
			}
			if s[len(s)-1] == '\\' {
				escape := len(s) - len(strings.TrimRight(s, `\`))
				if (escape & 1) == 1 {
					panic(fmt.Errorf("word: %s (%s)", errUnmatchedEscape, s))
				}
			}
		}
	}
	return &_word{word: s}
}

func escapeWord(s string) string {
	for i := 0; i < len(s); i++ {
		if s[i] == '\\' {
			s = s[:i] + s[i+1:]
		}
	}
	return s
}

func isName(val value) bool {
	s := val.String()
	return nameExp.MatchString(s)
}

func toName(val value) string {
	s := val.String()
	if nameExp.MatchString(s) {
		return s
	}
	panic(fmt.Errorf("name: %s (%s)", errInvalidName, s))
}

func isNameWithIndex(val value) bool {
	s := val.String()
	return nameWithIndexExp.MatchString(s)
}

func toNameWithIndex(val value) (string, []string) {
	s := val.String()
	if nameWithIndexExp.MatchString(s) {
		index := strings.Split(s, "[")
		name := index[0]
		if cnt := len(index) - 1; cnt > 0 {
			for i := 1; i < cnt; i++ {
				index[i] = index[i][:len(index[i])-1]
			}
			index[cnt] = index[cnt][:len(index[cnt])-2]
		}
		return name, index[1:]
	}
	panic(fmt.Errorf("name: %s (%s)", errInvalidName, s))
}

func isList(val value) bool {
	if _, ok := val.(*_list); ok {
		return true
	}
	s := val.String()
	return len(s) > 0 && s[0] == '['
}

func toList(val value) []string {
	if val, ok := val.(*_list); ok {
		return val.Value()
	}
	s := val.String()
	if len(s) > 0 && s[0] == '[' {
		return parseFunc(s, []environ{nil, nil, nil}).Value()
	}
	panic(fmt.Errorf("list: %s (%s)", errInvalidList, s))
}

func parseFunc(s string, envs []environ) *_list {
	list := parseList(s)
	if len(list) != 2 || !isList(&_unknown{s: list[0]}) || !isList(&_unknown{s: list[1]}) {
		return &_list{list: list}
	}
	param := parseList(list[0])
	for _, name := range param {
		if !isName(&_unknown{s: name}) {
			return &_list{list: list}
		}
	}
	local := environ{}
	if envs[1] != nil {
		for name, val := range envs[0] {
			local[name] = val
		}
	}
	return &_list{list: list, param: param, body: parseList(list[1]), env: local}
}

func isEmpty(val value) bool {
	if val, ok := val.(*_word); ok {
		return len(val.Value()) == 0
	}
	if val, ok := val.(*_list); ok {
		return len(val.Value()) == 0
	}
	s := val.String()
	return s == `"` || s == "[ ]"
}

func toValue(val value, envs []environ) value {
	if _, ok := val.(*_unknown); !ok {
		return val
	}
	s := val.String()
	if isNumber(val) {
		if isWord(val) {
			return &_number{num: toNumber(val), word: s[1:]}
		}
		return &_number{num: toNumber(val)}
	}
	if isBool(val) {
		if isWord(val) {
			return &_bool{b: toBool(val), word: s[1:]}
		}
		return &_bool{b: toBool(val)}
	}
	if isWord(val) {
		return parseWord(s)
	}
	if isList(val) {
		return parseFunc(s, envs)
	}
	panic(fmt.Errorf("value: %s (%s)", errValueExpected, s))
}

func opIsNumber(val1 value) *_bool {
	return &_bool{b: isNumber(val1)}
}

func opIsBool(val1 value) *_bool {
	return &_bool{b: isBool(val1)}
}

func opIsWord(val1 value) *_bool {
	return &_bool{b: isWord(val1)}
}

func opIsName(val1 value, envs []environ) *_bool {
	if !isName(val1) {
		return &_bool{b: false}
	}
	name := toName(val1)
	for _, env := range envs {
		_, ok := env[name]
		if ok {
			return &_bool{b: true}
		}
	}
	return &_bool{b: false}
}

func opIsList(val1 value) *_bool {
	return &_bool{b: isList(val1)}
}

func opIsEmpty(val1 value) *_bool {
	return &_bool{b: isEmpty(val1)}
}
