package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"
)

var reserved = map[string]int{
	"make": 2, "thing": 1, "print": 1, "read": 0, "add": 2,
	"sub": 2, "mul": 2, "div": 2, "mod": 2, "erase": 1,
	"isname": 1, "run": 1, "eq": 2, "gt": 2, "lt": 2,
	"and": 2, "or": 2, "not": 1, "if": 3, "isnumber": 1,
	"isword": 1, "islist": 1, "isbool": 1, "isempty": 1, "return": 1,
	"export": 1, "readlist": 0, "word": 2, "sentence": 2, "list": 2,
	"join": 2, "first": 1, "last": 1, "butfirst": 1, "butlast": 1,
	"random": 1, "int": 1, "sqrt": 1, "save": 1, "load": 1,
	"erall": 0, "poall": 0}

type environ map[string]value
type value struct {
	val   string
	list  []string
	param []string
	env   environ
}

var env []environ
var stack []value

func interpret(scanner *bufio.Scanner) (val value, returned bool) {
	stackTop, returned := len(stack), false
	scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		start := 0
		for start < len(data) {
			r, width := utf8.DecodeRune(data[start:])
			if r == '\n' {
				return start + width, data[start : start+width], nil
			}
			if !unicode.IsSpace(r) {
				break
			}
			start += width
		}
		for i := start; i < len(data); {
			r, width := utf8.DecodeRune(data[i:])
			if r == '\n' {
				return i, data[start:i], nil
			}
			if unicode.IsSpace(r) {
				return i + width, data[start:i], nil
			}
			i += width
		}
		if atEOF && len(data) > start {
			return len(data), data[start:], nil
		}
		return start, nil, nil
	})
	for !returned && scanner.Scan() {
		// for _, v := range stack {
		// 	fmt.Println(v.val)
		// }
		// fmt.Println()
		s := scanner.Text()
		if s == "\n" {
			continue
		}
		if s[0] == ':' {
			leading := len(s) - len(strings.TrimLeft(s, ":"))
			val := toValue(value{val: `"` + s[leading:]})
			for i := 0; i < leading; i++ {
				val = opThing(val)
			}
			stack = append(stack, val)
		} else if s[0] == '[' {
			list, bracketCnt := []string{}, 0
			for {
				leading := len(s) - len(strings.TrimLeft(s, "["))
				trailing := len(s) - len(strings.TrimRight(s, "]"))
				escape := len(s) - len(strings.TrimRight(strings.TrimRight(s, "]"), "\\")) - trailing
				trailing -= escape & 1
				s = s[leading : len(s)-trailing]
				for i := 0; i < leading; i++ {
					list = append(list, "[")
				}
				if s != "" {
					list = append(list, s)
				}
				for i := 0; i < trailing; i++ {
					list = append(list, "]")
				}
				bracketCnt += leading - trailing
				if bracketCnt < 0 {
					panic(fmt.Errorf("list: %s", errUnmatchedBracket))
				}
				if bracketCnt == 0 {
					break
				}
				s = "\n"
				for s == "\n" && scanner.Scan() {
					s = scanner.Text()
				}
				if s == "\n" {
					panic(fmt.Errorf("list: %s", errUnmatchedBracket))
				}
			}
			stack = append(stack, value{val: strings.Join(list, " ")})
		} else if s[0] >= '0' && s[0] <= '9' || s[0] == '-' {
			toNumber(s)
			stack = append(stack, value{val: s})
		} else {
			stack = append(stack, value{val: s})
		}
		for !returned {
			updated := false
			for i := len(stack) - 1; i >= stackTop; i-- {
				if !isNameWithIndex(`"` + stack[i].val) {
					continue
				}
				paramCnt, ok := reserved[stack[i].val]
				if ok {
					if paramCnt == len(stack)-1-i {
						for j := i + 1; j < len(stack); j++ {
							stack[j] = toValue(stack[j])
						}
						switch stack[i].val {
						case "make":
							stack[i] = opMake(stack[i+1], stack[i+2])
						case "thing":
							stack[i] = opThing(stack[i+1])
						case "print":
							stack[i] = opPrint(stack[i+1])
						case "read":
							stack[i] = opRead(scanner)
						case "add":
							stack[i] = opAdd(stack[i+1], stack[i+2])
						case "sub":
							stack[i] = opSub(stack[i+1], stack[i+2])
						case "mul":
							stack[i] = opMul(stack[i+1], stack[i+2])
						case "div":
							stack[i] = opDiv(stack[i+1], stack[i+2])
						case "mod":
							stack[i] = opMod(stack[i+1], stack[i+2])
						case "erase":
							stack[i] = opErase(stack[i+1])
						case "isname":
							stack[i] = opIsName(stack[i+1])
						case "run":
							stack[i], returned = opRun(stack[i+1])
						case "eq":
							stack[i] = opEq(stack[i+1], stack[i+2])
						case "gt":
							stack[i] = opGt(stack[i+1], stack[i+2])
						case "lt":
							stack[i] = opLt(stack[i+1], stack[i+2])
						case "and":
							stack[i] = opAnd(stack[i+1], stack[i+2])
						case "or":
							stack[i] = opOr(stack[i+1], stack[i+2])
						case "not":
							stack[i] = opNot(stack[i+1])
						case "if":
							stack[i], returned = opIf(stack[i+1], stack[i+2], stack[i+3])
						case "isnumber":
							stack[i] = opIsNumber(stack[i+1])
						case "isword":
							stack[i] = opIsWord(stack[i+1])
						case "islist":
							stack[i] = opIsList(stack[i+1])
						case "isbool":
							stack[i] = opIsBool(stack[i+1])
						case "isempty":
							stack[i] = opIsEmpty(stack[i+1])
						case "return":
							stack[i], returned = stack[i+1], true
						case "export":
							stack[i] = opExport(stack[i+1])
						case "readlist":
							stack[i] = opReadList(scanner)
						case "word":
							stack[i] = opWord(stack[i+1], stack[i+2])
						case "sentence":
							stack[i] = opSentence(stack[i+1], stack[i+2])
						case "list":
							stack[i] = opList(stack[i+1], stack[i+2])
						case "join":
							stack[i] = opJoin(stack[i+1], stack[i+2])
						case "first":
							stack[i] = opFirst(stack[i+1])
						case "last":
							stack[i] = opLast(stack[i+1])
						case "butfirst":
							stack[i] = opButFirst(stack[i+1])
						case "butlast":
							stack[i] = opButLast(stack[i+1])
						case "random":
							stack[i] = opRandom(stack[i+1])
						case "int":
							stack[i] = opInt(stack[i+1])
						case "sqrt":
							stack[i] = opSqrt(stack[i+1])
						case "save":
							stack[i] = opSave(stack[i+1])
						case "load":
							stack[i] = opLoad(stack[i+1])
						case "erall":
							stack[i] = opErAll()
						case "poall":
							stack[i] = opPoAll()
						}
						stack = stack[:i+1]
						updated = true
					}
					break
				}
				val, _ := getValue(`"` + stack[i].val)
				if val.param != nil {
					if len(val.param) == len(stack)-1-i {
						for j := i + 1; j < len(stack); j++ {
							stack[j] = toValue(stack[j])
						}
						local := environ{stack[i].val: val}
						for j, name := range val.param {
							local[name] = stack[i+1+j]
						}
						env = append(env, val.env, local)
						stack[i], _ = opRun(value{val: val.list[1]})
						env = env[:len(env)-2]
						stack = stack[:i+1]
						updated = true
					}
					break
				}
			}
			if !updated {
				break
			}
		}
	}
	if len(stack) == stackTop {
		stack = append(stack, value{val: "[ ]"})
	}
	val = toValue(stack[len(stack)-1])
	stack = stack[:stackTop]
	return
}

func main() {
	rand.Seed(time.Now().UnixNano())
	env = append(env, nil, map[string]value{"pi": {val: "3.14159"}})

	if len(os.Args) > 1 {
		file, err := os.Open(os.Args[1])
		if err != nil {
			panic(fmt.Errorf("mua: %s (%s)", errFileError, os.Args[1]))
		}
		os.Stdin = file
	}

	scanner := bufio.NewScanner(os.Stdin)
	interpret(scanner)
}
