package calc

import (
	"errors"
	"math"
)

type Calculator struct {
	Text []byte
	Pos  int
}

func iswhitespace(c byte) bool {
	switch c {
	case '\n', '\r', '\t', ' ':
		return true
	}
	return false
}

func isnumber(c byte) bool {
	return c >= '0' && c <= '9'
}

func isexp10(c byte) bool {
	return c == 'e' || c == 'E'
}

func doubleFactorial(x float64) float64 {
	if math.Mod(x, 1) == 0 { //if integer
		num := float64(1)
		if math.Mod(x, 2) != 0 {
			if x > 1 {
				for i := float64(3); i <= x; i += 2 {
					num *= i
				}
			} else {
				xp2 := x + 2
				for i := float64(-1); i >= xp2; i -= 2 {
					num /= i
				}
			}
		} else {
			if x >= 0 {
				for i := float64(2); i <= x; i += 2 {
					num *= i
				}
			} else {
				return math.NaN()
			}
		}
		return num
	}
	const lnPi_2_4 = 0.11289567632236371618154880747372
	return math.Exp(math.Ln2*x/2+lnPi_2_4*(math.Cos(math.Pi*x)-1)) * math.Gamma(1+x/2)
}

func (c *Calculator) calcexp() float64 {
	x := c.readnumber()
	for {
		v := c.Text[c.Pos]
		switch {
		case v == '^':
			c.Pos++
			x = math.Pow(x, c.calcexp())
		case v == '!':
			if c.Text[c.Pos+1] == '!' {
				x = doubleFactorial(x)
				c.Pos += 2
				break
			}
			c.Pos++
			x = math.Gamma(x + 1)
		case iswhitespace(v):
			for {
				c.Pos++
				if !iswhitespace(c.Text[c.Pos]) {
					break
				}
			}
		default:
			return x
		}
	}
}

func (c *Calculator) calcpoint() float64 {
	x := c.calcexp()
	for {
		switch c.Text[c.Pos] {
		case '*':
			c.Pos++
			x *= c.calcexp()
		case '/':
			c.Pos++
			x /= c.calcexp()
		case '%':
			c.Pos++
			x = math.Mod(x, c.calcexp())
		default:
			return x
		}
	}
}

func (c *Calculator) calc() float64 {
	x := c.calcpoint()
	for {
		switch c.Text[c.Pos] {
		case '+':
			c.Pos++
			x += c.calcpoint()
		case '-':
			c.Pos++
			x -= c.calcpoint()
		default:
			return x
		}
	}
}

func (c *Calculator) readexp10(num float64) float64 {
	var x int
	sign := 1
	y := 1.0

	for {
		v := c.Text[c.Pos]
		switch {
		case isnumber(v):
			x = int(v - '0')
			for {
				c.Pos++
				v = c.Text[c.Pos]
				if isnumber(v) {
					x = x*10 + int(v-'0')
					continue
				}
				for i := 0; i < x; i++ {
					y *= 10
				}
				if sign == 1 {
					num *= y
				} else {
					num /= y
				}
				return num
			}
		case v == '-':
			sign *= -1
			fallthrough
		case v == '+':
			c.Pos++
			continue
		default:
			panic("only integer expected")
		}
	}
}

func (c *Calculator) readfloat(num float64, sign int) float64 {
	x := 10.0
	y := 0.0

	for {
		v := c.Text[c.Pos]
		if isnumber(v) {
			y += float64(v-'0') / x
			x *= 10
			c.Pos++
			continue
		}
		num += float64(sign) * y
		if isexp10(c.Text[c.Pos]) {
			c.Pos++
			return c.readexp10(num)
		}
		return num
	}
}

func (c *Calculator) readnumber() float64 {
	sign := 1
	var num float64

	for {
		v := c.Text[c.Pos]
		switch {
		case isnumber(v):
			num = float64(v - '0')
			for {
				c.Pos++
				v = c.Text[c.Pos]
				if isnumber(v) {
					num = num*10 + float64(v-'0')
					continue
				}
				num *= float64(sign)
				switch {
				case isexp10(v):
					c.Pos++
					return c.readexp10(num)
				case v == '.':
					c.Pos++
					return c.readfloat(num, sign)
				default:
					return num
				}
			}
		case v == '.':
			c.Pos++
			return c.readfloat(num, sign)
		case v == '-':
			sign *= -1
			fallthrough
		case v == '+' || iswhitespace(v):
			c.Pos++
			continue
		case v == '(':
			c.Pos++
			num = c.calc()
			if c.Text[c.Pos] != ')' {
				panic("unclosing brackets")
			}
			c.Pos++
			return num * float64(sign)
		default:
			panic("unexpected expression")
		}
	}
}

func (c *Calculator) Calculate() (num float64, e error) {
	defer func() {
		v := recover()
		if v != nil {
			num = 0
			e = errors.New(v.(string))
		}
	}()

	if len(c.Text) == 1 {
		panic("You have nothing typed in")
	}
	result := c.calc()
	if c.Text[c.Pos] != 0 {
		panic("unexpected expression")
	}
	return result, nil
}
