package main

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

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,
	"false": 0, "true": 0}

type environ map[string]value
type value struct {
	tp    int
	num   float64
	word  string
	list  []string
	param []string
	body  []string
	env   environ
	b     bool
}

func interpret(scanner *scanProvider, envs []environ) (val value, returned bool) {
	stack, returned := []value{}, false
	for !returned && scanner.Scan() {
		// for _, v := range stack {
		// 	fmt.Println(toString(v))
		// }
		// fmt.Println()
		// fmt.Println(envs[0])
		// fmt.Println()
		s := scanner.Text()
		if s == "\n" {
			continue
		}
		if s[0] == ':' {
			leading := len(s) - len(strings.TrimLeft(s, ":"))
			val := toValue(value{word: `"` + s[leading:]}, envs)
			for i := 0; i < leading; i++ {
				val = toValue(opThing(val, envs), envs)
			}
			stack = append(stack, val)
		} else if s[0] == '[' && !scanner.isList {
			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{word: strings.Join(list, " ")})
		} else {
			stack = append(stack, value{word: s})
		}
		updated := true
		for !returned && updated {
			updated = false
			for i := len(stack) - 1; i >= 0; i-- {
				if !isName(value{word: `"` + stack[i].word}) {
					continue
				}
				paramCnt, ok := reserved[stack[i].word]
				if ok {
					if stack[i].word == "false" || stack[i].word == "true" {
						continue
					}
					if paramCnt == len(stack)-1-i {
						for j := i + 1; j < len(stack); j++ {
							stack[j] = toValue(stack[j], envs)
						}
						switch stack[i].word {
						case "make":
							stack[i] = opMake(stack[i+1], stack[i+2], envs)
						case "thing":
							stack[i] = opThing(stack[i+1], envs)
						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], envs)
						case "isname":
							stack[i] = opIsName(stack[i+1], envs)
						case "run":
							stack[i], returned = opRun(stack[i+1], envs)
						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], envs)
						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], envs)
						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], envs[0])
						case "load":
							stack[i] = opLoad(stack[i+1], envs[0])
						case "erall":
							stack[i] = opErAll(envs[0])
						case "poall":
							stack[i] = opPoAll(envs[0])
						}
						stack = stack[:i+1]
						updated = true
					}
					break
				}
				val, _ := getValue(`"`+stack[i].word, envs)
				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], envs)
						}
						local := environ{stack[i].word: val}
						for j, name := range val.param {
							local[name] = stack[i+1+j]
						}
						ctx := environ{}
						for name, val := range val.env {
							ctx[name] = val
						}
						global := envs[0]
						if envs[2] != nil {
							global = envs[2]
						}
						stack[i], _ = opRun(value{tp: typeList, list: val.body}, []environ{local, ctx, global})
						stack = stack[:i+1]
						updated = true
					}
					break
				}
				panic(fmt.Errorf("func: %s (%s)", errFunctionExpected, stack[i].word))
			}
		}
	}
	if len(stack) == 0 {
		val = value{tp: typeList, list: []string{}}
	} else {
		val = toValue(stack[len(stack)-1], envs)
	}
	return
}

func main() {
	rand.Seed(time.Now().UnixNano())
	env := map[string]value{"pi": {tp: typeNumber, num: 3.14159}}
	if len(os.Args) > 1 {
		file, err := os.Open(os.Args[1])
		if err != nil {
			panic(fmt.Errorf("script: %s (%s)", errFileError, os.Args[1]))
		}
		os.Stdin = file
	}
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Split(splitFunc)
	interpret(&scanProvider{isList: false, scanner: scanner}, []environ{env, nil, nil})
}
