package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"strconv"
	"strings"

	"tildegit.org/sloum/nums/qline"
	"tildegit.org/sloum/nums/termios"
)

const (
	add int = iota
	sub
	mul
	div
	pow
	rnd
	flr
	cil
	abs
	inv
	sqt
)

type stack struct {
	ptr int
	data [100]float64
}

func (s *stack) Push(v float64) {
	if s.ptr >= 99 {
		PrintError("Stack overflow")
		s.Clear()
		return
	}
	s.ptr++
	s.data[s.ptr] = v
}

func (s *stack) Pop() (float64, error) {
	if s.ptr < 0 {
		PrintError("Stack underflow")
		s.Clear()
		return 0.0, fmt.Errorf("Stack underflow")
	}
	s.ptr--
	return s.data[s.ptr+1], nil
}

func (s *stack) Clear() {
	s.ptr = -1
}

func (s stack) Len() int {
	return s.ptr+1
}

func (s *stack) Add() {
	n2, err := s.Pop()
	if err != nil {
		return
	}
	n1, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(n1+n2)
}

func (s *stack) Sub() {
	n2, err := s.Pop()
	if err != nil {
		return
	}
	n1, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(n1-n2)
}

func (s *stack) Mul() {
	n2, err := s.Pop()
	if err != nil {
		return
	}
	n1, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(n1*n2)
}

func (s *stack) Div() {
	n2, err := s.Pop()
	if err != nil {
		return
	}
	n1, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(n1/n2)
}

func (s *stack) Pow() {
	exp, err := s.Pop()
	if err != nil {
		return
	}
	base, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(math.Pow(base, exp))
}


func (s *stack) Floor() {
	n, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(math.Floor(n))
}

func (s *stack) Ceil() {
	n, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(math.Ceil(n))
}

func (s *stack) Round() {
	decimals, err := s.Pop()
	if err != nil {
		return
	}
	num, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(math.Round(num*math.Pow(10, decimals)) / math.Pow(10, decimals))
}

func (s *stack) Abs() {
	n, err := s.Pop()
	if err != nil {
		return
	}
	if n < 0 {
		s.Push(n*-1)
	} else {
		s.Push(n)
	}
}

func (s *stack) Inv() {
	n, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(n*-1)
}

func (s *stack) Sqt() {
	n, err := s.Pop()
	if err != nil {
		return
	}

	s.Push(math.Sqrt(n))
}

func (s *stack) Dup() {
	val, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(val)
	s.Push(val)
}

func (s *stack) Ovr() {
	tos, err := s.Pop()
	if err != nil {
		return
	}
	cpy, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(cpy)
	s.Push(tos)
	s.Push(cpy)
}

func (s *stack) Swp() {
	tos, err := s.Pop()
	if err != nil {
		return
	}
	newTos, err := s.Pop()
	if err != nil {
		return
	}
	s.Push(tos)
	s.Push(newTos)
}

func (s *stack) Drp() {
	s.Pop()
}

func (s stack) TOS() string {
	if s.ptr < 0 {
		return "0"
	}
	return strconv.FormatFloat(s.data[s.ptr], 'f', -1, 64)
}

func PrintError(s string) {
	fmt.Fprintf(os.Stderr, "\033[1;91m[Err] %s,\033[0m\n", s)
}

func PrintOperatorHelp() {
	txt := `# General

This calculator uses reverse polish notation. As such, the operators come after the operands. For example:

	5 7 + 1 -

The output of the above would be '11' (using infix notation this would be written ' 5 + 7 - 1'). For more information on reverse polish notation see: https://en.wikipedia.org/wiki/Reverse_Polish_notation

The output of a calculation will always be the top of the stack. If there is more than one value on the stack at the end of a calculation a warning will be output to stderr.

# Command List

The following commands all have a three character version and a single character version. Either one is valid. They are displayed as a comma separated values (single character version first) followed by a colon and a description of the operator. The operators are case insensitive.

	+, add: Adds the top two values on the stack
	-, sub: Subtracts the top stack value from the value underneath it
	/, div: Divides the top two stack values with the top of stack being the divisor and the value below it on the stack being the dividend
	*, mul: Multiplies the top two values on the stack
	^, pow: Raises the value underneath top of stack to the power of top of stack
	@, rnd: Rounds the value underneath top of stack to the number of decimal places represented by top of stack. If top of stack is not a whole number an error will result
	>, cil: Performs a ceiling function on the top of stack (rounds it up to the next whole number)
	<, flr: Performs a floor function on the top of stack (rounds it down to the next whole number)
	|, abs: Takes the top value from the stack and leaves its absolute value
	!, inv: Multiplies top of stack by -1
	V, sqt: Takes the top value from the stack and leaves its square root

The following commands operate on the stack, rather than as numerical operations:

	x, swp: Swap the position of the top two values on the stack
	#, dup: Duplicate/copy the value on top of the stack such that the copy is now above the original on the stack
	_, clr: Clear the stack (wipe out all values and start over)
	., drp: Drop the top value on the stack (remove it and throw it away)

	?, help: Prints this help message
`
	fmt.Fprintf(os.Stderr, "%s\033\n", txt)
}

func processWord(w string, s *stack) {
	num, err := strconv.ParseFloat(w, 64)
	if err == nil {
		s.Push(num)
		return
	}
	w = strings.ToLower(w)
	switch w {
	case "+", "add":
		s.Add()
	case "-", "sub":
		s.Sub()
	case "*", "mul":
		s.Mul()
	case "/", "div":
		s.Div()
	case "^", "pow":
		s.Pow()
	case "@", "rnd":
		s.Round()
	case ">", "cil":
		s.Ceil()
	case "<", "flr":
		s.Floor()
	case "|", "abs":
		s.Abs()
	case "!", "inv":
		s.Inv()
	case "V", "sqt":
		s.Sqt()
	case "#", "dup":
		s.Dup()
	case "x", "swp":
		s.Swp()
	case "_", "clr":
		s.Clear()
	case ".", "drp":
		s.Drp()
	case "?", "help":
		PrintOperatorHelp()
		s.Clear()
	default:
		PrintError(fmt.Sprintf("Unknown operator: %q", w))
		s.Clear()
	}
}

func main() {
	pipe := flag.Bool("p", false, "Take input from a pipe")
	flag.Parse()
	s := stack{-1, [100]float64{}}

	if !*pipe {
		termios.SetInitialTermios()
		defer termios.Restore()
		termios.SetCharMode()

		for {
			var cols, _ = termios.GetWindowSize()
			ln := qline.GetInput("", "", cols)
			f := strings.Fields(ln)
			if len(f) == 0 {
				break
			}
			fmt.Print("\n")
			for _, val := range f {
				processWord(val, &s)
			}
			fmt.Fprintf(os.Stderr, "%v\n", s.data[:s.ptr+1])
		}
	} else {
		b, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			fmt.Fprint(os.Stderr, "Unable to read from pipe")
		}
		f := strings.Fields(string(b))
		for _, val := range f {
			processWord(val, &s)
		}
		if s.ptr >= 0 {
			fmt.Fprintf(os.Stderr, "%v\n", s.data[s.ptr])
		} else {
			os.Exit(1)
		}
	}

}
