package compiler

type (
	stack struct {
		*stackEntry
	}
	stackEntry struct {
		str  string
		prev *stackEntry
	}
)

func (stk stack) isEmpty() bool {
	return stk.stackEntry == nil
}

func (stk stack) top() string {
	if stk.isEmpty() {
		return ""
	}
	return stk.str
}

func (stk stack) add(str string) stack {
	e := &stackEntry{
		str:  str,
		prev: stk.stackEntry,
	}
	return stack{e}
}

func (stk stack) addFromStack(other stack) stack {
	if other.isEmpty() {
		return stk
	}
	res := stk.addFromStack(other.drop())
	return res.add(other.top())
}

func (stk stack) drop() stack {
	if !stk.isEmpty() {
		stk = stack{stk.prev}
	}
	return stk
}

func (stk stack) dropN(n int) stack {
	for n > 0 {
		stk = stk.drop()
		n--
	}
	return stk
}

func (stk stack) recurFind(str string) int {
	if stk.isEmpty() {
		return -1
	}
	if stk.str == str {
		return 0
	}
	res := stk.drop().recurFind(str)
	if res < 0 {
		return res
	}
	return res + 1
}

func (stk stack) count() map[string]int {
	if stk.isEmpty() {
		return nil
	}
	stackCounts := make(map[string]int)
	for {
		stackCounts[stk.str]++
		stk = stack{stk.prev}
		if stk.stackEntry == nil {
			break
		}
	}
	return stackCounts
}

func (stk stack) find(str string) int {
	stackCounts := stk.count()
	if stk.isEmpty() || stackCounts[str] == 0 {
		return -1
	}

	var pos int
	for {
		if stk.str == str {
			if stackCounts[str] == 1 {
				break
			}
			stackCounts[str]--
		}
		stk = stack{stk.prev}
		if stk.stackEntry == nil {
			break
		}
		pos++
	}
	return pos
}

func (stk stack) roll(n int) stack {
	var x func(stack, int) (stack, string)
	x = func(stk stack, n int) (stack, string) {
		if n == 0 {
			return stk.drop(), stk.top()
		}
		stk2, entry := x(stk.drop(), n-1)
		return stk2.add(stk.top()), entry
	}
	stk, entry := x(stk, n)
	return stk.add(entry)
}

func (stk stack) swap() stack {
	a := stk.top()
	stk = stk.drop()
	b := stk.top()
	stk = stk.drop()
	return stk.add(a).add(b)
}

func (stk stack) dup() stack {
	return stk.add(stk.top())
}

func (stk stack) over() stack {
	t := stk.drop().top()
	return stk.add(t)
}

func (stk stack) pick(n int) stack {
	t := stk.dropN(n).top()
	return stk.add(t)
}

func (stk stack) String() string {
	if stk.stackEntry == nil {
		return "[]"
	}
	var x func(stk stack) string
	x = func(stk stack) string {
		if stk.stackEntry == nil {
			return ""
		}
		return x(stk.drop()) + " " + stk.stackEntry.str
	}
	return "[..." + x(stk) + "]"
}
