calc.gno

package calc

import (
	"chain"
	"errors"
	"strconv"
	"strings"
)

// stack holds the shared RPN operand stack, bottom (index 0) to top.
var stack []int

// ErrUnderflow is raised when an operator needs more operands than available.
var ErrUnderflow = errors.New("stack underflow: operator needs two operands")

// ErrDivByZero is raised on division by zero.
var ErrDivByZero = errors.New("division by zero")

// Push consumes a single token: an integer is pushed onto the stack; an
// operator (+, -, *, /) pops two operands and pushes the result.
func Push(cur realm, token string) {
	token = strings.TrimSpace(token)
	if token == "" {
		panic("empty token")
	}

	switch token {
	case "+", "-", "*", "/":
		if len(stack) < 2 {
			panic(ErrUnderflow.Error())
		}
		b := stack[len(stack)-1]
		a := stack[len(stack)-2]
		stack = stack[:len(stack)-2]

		var r int
		switch token {
		case "+":
			r = a + b
		case "-":
			r = a - b
		case "*":
			r = a * b
		case "/":
			if b == 0 {
				panic(ErrDivByZero.Error())
			}
			r = a / b
		}
		stack = append(stack, r)
		chain.Emit("Op", "op", token, "result", strconv.Itoa(r))
	default:
		n, err := strconv.Atoi(token)
		if err != nil {
			panic("invalid token: expected integer or one of + - * /, got " + token)
		}
		stack = append(stack, n)
		chain.Emit("Push", "value", strconv.Itoa(n))
	}
}

// Clear empties the shared stack.
func Clear(cur realm) {
	stack = nil
	chain.Emit("Clear")
}

// Depth returns the number of values currently on the stack.
func Depth() int {
	return len(stack)
}

// Top returns the top value and whether the stack is non-empty.
func Top() (int, bool) {
	if len(stack) == 0 {
		return 0, false
	}
	return stack[len(stack)-1], true
}

// Render renders the current stack, the top value as the result, and a
// short usage guide.
func Render(path string) string {
	var sb strings.Builder
	sb.WriteString("# RPN Calculator\n\n")

	sb.WriteString("## Result\n\n")
	if top, ok := Top(); ok {
		sb.WriteString("**")
		sb.WriteString(strconv.Itoa(top))
		sb.WriteString("**\n\n")
	} else {
		sb.WriteString("_(empty stack)_\n\n")
	}

	sb.WriteString("## Stack (bottom → top)\n\n")
	if len(stack) == 0 {
		sb.WriteString("_empty_\n\n")
	} else {
		for i, v := range stack {
			sb.WriteString(strconv.Itoa(i))
			sb.WriteString(". ")
			sb.WriteString(strconv.Itoa(v))
			sb.WriteString("\n")
		}
		sb.WriteString("\n")
	}

	sb.WriteString("## Usage\n\n")
	sb.WriteString("Reverse Polish Notation: operands first, operator last.\n\n")
	sb.WriteString("- `Push(\"5\")` then `Push(\"3\")` then `Push(\"+\")` → `8`\n")
	sb.WriteString("- Supported operators: `+`, `-`, `*`, `/` (integer division)\n")
	sb.WriteString("- An operator pops the top two values (a, b) and pushes `a op b`.\n")
	sb.WriteString("- `Clear()` empties the stack.\n")
	sb.WriteString("- Divide-by-zero and underflow abort the transaction.\n")

	return sb.String()
}