package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"strings"
)

func assign(s string, index []string, val string, envs []environ) string {
	if len(index) == 0 {
		return val
	}
	list := parseList(s)
	i := toInt(value{word: index[0]}, envs)
	return spliceList(list, i, 1, assign(indexOfList(list, i), index[1:], val, envs))
}

func setValue(s string, val value, envs []environ) (value, error) {
	if !isNameWithIndex(value{word: s}) {
		return value{}, fmt.Errorf("set: %s (%s)", errInvalidName, s)
	}
	name, index := toNameWithIndex(value{word: s})
	_, ok := reserved[name]
	if ok {
		return value{}, fmt.Errorf("set: %s (%s)", errNameReserved, name)
	}
	oldVal, ok := envs[0][name]
	if ok {
		delete(envs[0], name)
		if len(index) > 0 {
			val = value{word: assign(toString(oldVal), index, toString(val), envs)}
		}
	} else if len(index) > 0 {
		return value{}, fmt.Errorf("set: %s (%s)", errNameNotFound, name)
	}
	val = toValue(val, envs)
	envs[0][name] = val
	return val, nil
}

func getValue(s string, envs []environ) (value, error) {
	if !isNameWithIndex(value{word: s}) {
		return value{}, fmt.Errorf("get: %s (%s)", errInvalidName, s)
	}
	name, index := toNameWithIndex(value{word: s})
	for _, env := range envs {
		val, ok := env[name]
		if ok {
			if len(index) > 0 {
				val = value{word: toString(val)}
				for _, i := range index {
					list := parseList(val.word)
					r := strings.Split(i, ":")
					if len(r) == 1 {
						val = value{word: indexOfList(list, toInt(value{word: r[0]}, envs))}
						if !isList(val) {
							val.word = `"` + val.word
						}
					} else {
						num1, num2 := 0, len(list)
						if r[0] != "" {
							num1 = toInt(value{word: r[0]}, envs)
						}
						if r[1] != "" {
							num2 = toInt(value{word: r[1]}, envs)
						}
						val = value{word: rangeOfList(list, num1, num2)}
					}
				}
			}
			return val, nil
		}
	}
	return value{}, fmt.Errorf("get: %s (%s)", errNameNotFound, name)
}

func opMake(val1, val2 value, envs []environ) value {
	val, err := setValue(val1.word, val2, envs)
	if err != nil {
		panic(err)
	}
	return val
}

func opThing(val1 value, envs []environ) value {
	val, err := getValue(val1.word, envs)
	if err != nil {
		panic(err)
	}
	return val
}

func opErase(val1 value, envs []environ) value {
	name := toName(val1)
	for _, env := range envs {
		val, ok := env[name]
		if ok {
			delete(env, name)
			return val
		}
	}
	panic(fmt.Errorf("erase: %s (%s)", errNameNotFound, name))
}

func opExport(val1 value, envs []environ) value {
	name := toName(val1)
	val, ok := envs[0][name]
	if ok {
		if envs[2] != nil {
			envs[2][name] = val
		}
		return val
	}
	panic(fmt.Errorf("export: %s (%s)", errNameNotFound, name))
}

func opSave(val1 value, env environ) value {
	name := toName(val1)
	s := ""
	for name, val := range env {
		s += `make "` + name + " " + val.word + "\n"
	}
	err := ioutil.WriteFile(name, []byte(s), 0666)
	if err != nil {
		panic(fmt.Errorf("save: %s (%s)", errFileError, name))
	}
	return val1
}

func opLoad(val1 value, env environ) value {
	name := toName(val1)
	s, err := ioutil.ReadFile(name)
	if err != nil {
		panic(fmt.Errorf("load: %s (%s)", errFileError, name))
	}
	scanner := bufio.NewScanner(strings.NewReader(string(s)))
	scanner.Split(splitFunc)
	interpret(&scanProvider{isList: false, scanner: scanner}, []environ{env, nil, nil})
	return value{tp: typeBool, b: true}
}

func opErAll(env environ) value {
	for name := range env {
		delete(env, name)
	}
	return value{tp: typeBool, b: true}
}

func opPoAll(env environ) value {
	list := []string{}
	for name := range env {
		list = append(list, name)
	}
	return value{tp: typeList, list: list}
}
