package main

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

const (
	typeUnknown = iota
	typeNumber
	typeBool
	typeWord
	typeList
)

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

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

func isNumber(val value) bool {
	if val.tp == typeNumber {
		return true
	}
	if isWord(val) {
		val.word = val.word[1:]
	}
	_, err := strconv.ParseFloat(val.word, 64)
	return err == nil
}

func toNumber(val value) float64 {
	if val.tp == typeNumber {
		return val.num
	}
	if isWord(val) {
		val.word = val.word[1:]
	}
	num, err := strconv.ParseFloat(val.word, 64)
	if err != nil {
		panic(fmt.Errorf("number: %s (%s)", errInvalidNumber, toString(val)))
	}
	return num
}

func toInt(val value, envs []environ) int {
	if isName(value{word: `"` + val.word}) {
		newVal, err := getValue(`"`+val.word, envs)
		if err == nil {
			val = newVal
			if val.tp == typeNumber {
				if val.num != math.Floor(val.num) {
					panic(fmt.Errorf("int: %s (%f)", errInvalidInteger, val.num))
				}
				return int(val.num)
			}
		}
	}
	if isWord(val) {
		val.word = val.word[1:]
	}
	num, err := strconv.ParseInt(val.word, 10, 64)
	if err != nil {
		panic(fmt.Errorf("int: %s (%s)", errInvalidInteger, toString(val)))
	}
	return int(num)
}

func isBool(val value) bool {
	if val.tp == typeBool {
		return true
	}
	if val.tp == typeNumber && (val.num == 0 || val.num == 1) {
		return true
	}
	if isWord(val) {
		val.word = val.word[1:]
	}
	return val.word == "false" || val.word == "true"
}

func toBool(val value) bool {
	if val.tp == typeBool {
		return val.b
	}
	if val.tp == typeNumber && (val.num == 0 || val.num == 1) {
		return val.num == 1
	}
	if isWord(val) {
		val.word = val.word[1:]
	}
	if val.word == "false" || val.word == "true" {
		return val.word == "true"
	}
	panic(fmt.Errorf("bool: %s (%s)", errInvalidBool, toString(val)))
}

func isWord(val value) bool {
	if val.tp == typeWord {
		return true
	}
	return val.word != "" && val.word[0] == '"'
}

func parseWord(s string) value {
	if s != `"` {
		if s[1] == '[' {
			s = `"\[` + s[2:]
		}
		if s[len(s)-1] == ']' {
			s = s[:len(s)-1]
			escape := len(s) - len(strings.TrimRight(s, `\`))
			if (escape & 1) == 0 {
				s += `\`
			}
			s += "]"
		}
	}
	return value{tp: typeWord, 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 {
	return nameExp.MatchString(val.word)
}

func toName(val value) string {
	if !isName(val) {
		panic(fmt.Errorf("name: %s (%s)", errInvalidName, toString(val)))
	}
	return val.word[1:]
}

func isNameWithIndex(val value) bool {
	return nameWithIndexExp.MatchString(val.word)
}

func toNameWithIndex(val value) (string, []string) {
	if !isNameWithIndex(val) {
		panic(fmt.Errorf("name: %s (%s)", errInvalidName, toString(val)))
	}
	index := strings.Split(val.word, "[")
	name, cnt := index[0][1:], len(index)-1
	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:]
}

func isList(val value) bool {
	if val.tp == typeList {
		return true
	}
	return val.word != "" && val.word[0] == '['
}

func parseFunc(s string, envs []environ) value {
	list := parseList(s)
	if len(list) != 2 || !isList(value{word: list[0]}) || !isList(value{word: list[1]}) {
		return value{tp: typeList, list: list}
	}
	param := parseList(list[0])
	for _, name := range param {
		if !isName(value{word: `"` + name}) {
			return value{tp: typeList, list: list}
		}
	}
	local := environ{}
	if envs[2] != nil {
		for name, val := range envs[0] {
			local[name] = val
		}
	}
	return value{tp: typeList, list: list, param: param, body: parseList(list[1]), env: local}
}

func isEmpty(val value) bool {
	if val.tp == typeWord {
		return val.word == `"`
	}
	if val.tp == typeList {
		return len(val.list) == 0
	}
	return val.word == `"` || val.word == "[ ]"
}

func toValue(val value, envs []environ) value {
	if val.tp != typeUnknown {
		return val
	}
	if isNumber(val) {
		if isWord(val) {
			return value{tp: typeNumber, word: val.word, num: toNumber(val)}
		}
		return value{tp: typeNumber, num: toNumber(val)}
	}
	if isBool(val) {
		if isWord(val) {
			return value{tp: typeBool, word: val.word, b: toBool(val)}
		}
		return value{tp: typeBool, b: toBool(val)}
	}
	if isWord(val) {
		return parseWord(val.word)
	}
	if isList(val) {
		return parseFunc(val.word, envs)
	}
	panic(fmt.Errorf("value: %s (%s)", errValueExpected, val.word))
}

func toString(val value) string {
	if val.tp == typeNumber {
		if val.word != "" {
			return val.word[1:]
		}
		return strconv.FormatFloat(val.num, 'g', -1, 64)
	}
	if val.tp == typeBool {
		if val.word != "" {
			return val.word[1:]
		}
		return strconv.FormatBool(val.b)
	}
	if val.tp == typeWord {
		return val.word[1:]
	}
	if val.tp == typeList {
		return makeList(val.list)
	}
	panic(fmt.Errorf("string: %s (%s)", errValueExpected, val.word))
}

func opIsNumber(val1 value) value {
	return value{tp: typeBool, b: isNumber(val1)}
}

func opIsBool(val1 value) value {
	return value{tp: typeBool, b: isBool(val1)}
}

func opIsWord(val1 value) value {
	return value{tp: typeBool, b: isWord(val1)}
}

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

func opIsList(val1 value) value {
	return value{tp: typeBool, b: isList(val1)}
}

func opIsEmpty(val1 value) value {
	return value{tp: typeBool, b: isEmpty(val1)}
}
