package staros

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
)

func Calc(math string) (float64, error) {
	math = strings.Replace(math, " ", "", -1)
	math = strings.ToLower(math)
	if err := check(math); err != nil {
		return 0, err
	}
	result,err:=calc(math)
	if err!=nil {
		return 0,err
	}
	return floatRound(result,15),nil
}

func floatRound(f float64, n int) float64 {
	format := "%." + strconv.Itoa(n) + "f"
	res, _ := strconv.ParseFloat(fmt.Sprintf(format, f), 64)
	return res
}

func check(math string) error {
	math = strings.Replace(math, " ", "", -1)
	math = strings.ToLower(math)
	var bracketSum int
	var signReady bool
	for k, v := range math {
		if string([]rune{v}) == "(" {
			bracketSum++
		}
		if string([]rune{v}) == ")" {
			bracketSum--
		}
		if bracketSum < 0 {
			return fmt.Errorf("err at position %d.Reason is right bracket position not correct,except (", k)
		}
		if containSign(string([]rune{v})) {
			if signReady {
				if string([]rune{v}) != "+" && string([]rune{v}) != "-" {
					return fmt.Errorf("err at position %d.Reason is sign %s not correct", k, string([]rune{v}))
				}
			} else {
				signReady = true
				continue
			}
		}
		signReady = false
	}
	if bracketSum != 0 {
		return fmt.Errorf("Error:right bracket is not equal as left bracket")
	}
	return nil
}

func calc(math string) (float64, error) {
	var bracketLeft int
	var bracketRight int
	var DupStart int = -1
	for pos, str := range math {
		if string(str) == "(" {
			bracketLeft = pos
		}
		if string(str) == ")" {
			bracketRight = pos
			break
		}
	}
	if bracketRight == 0 && bracketLeft != 0 || (bracketLeft > bracketRight) {
		return 0, fmt.Errorf("Error:bracket not correct at %d ,except )", bracketLeft)
	}
	if bracketRight == 0 && bracketLeft == 0 {
		return calcLong(math)
	}
	line := math[bracketLeft+1 : bracketRight]
	num, err := calcLong(line)
	if err != nil {
		return 0, err
	}
	for i := bracketLeft - 1; i >= 0; i-- {
		if !containSign(math[i : i+1]) {
			DupStart = i
			continue
		}
		break
	}
	if DupStart != -1 {
		sign := math[DupStart:bracketLeft]
		num, err := calcDuaFloat(sign, num)
		if err != nil {
			return 0, err
		}
		math = math[:DupStart] + fmt.Sprintf("%.15f", num) + math[bracketRight+1:]
		DupStart = -1
	} else {
		math = math[:bracketLeft] + fmt.Sprintf("%.15f", num) + math[bracketRight+1:]
	}
	return calc(math)
}

func calcLong(str string) (float64, error) {
	var sigReady bool = false
	var sigApply bool = false
	var numPool []float64
	var operPool []string
	var numStr string
	var oper string
	if str[0:1] == "+" || str[0:1] == "-" {
		sigReady = true
	}
	for _, stp := range str {
		if sigReady && containSign(string(stp)) {
			sigReady = false
			sigApply = true
			oper = string(stp)
			continue
		}
		if !containSign(string(stp)) {
			sigReady = false
			numStr = string(append([]rune(numStr), stp))
			continue
		}
		if !sigReady {
			sigReady = true
		}
		if sigApply {
			num, err := calcDua(oper, numStr)
			if err != nil {
				return 0, err
			}
			sigApply = false
			numPool = append(numPool, num)
		} else {
			num, err := parseNumbic(numStr)
			if err != nil {
				return 0, err
			}
			numPool = append(numPool, num)
		}
		numStr = ""
		operPool = append(operPool, string(stp))
	}
	if sigApply {
		num, err := calcDua(oper, numStr)
		if err != nil {
			return 0, err
		}
		numPool = append(numPool, num)
	} else {
		num, err := parseNumbic(numStr)
		if err != nil {
			return 0, err
		}
		numPool = append(numPool, num)
	}
	return calcPool(numPool, operPool)
}

func calcPool(numPool []float64, operPool []string) (float64, error) {
	if len(numPool) == 1 && len(operPool) == 0 {
		return numPool[0], nil
	}
	if len(numPool) < len(operPool) {
		return 0, errors.New(("Operate Signal Is too much"))
	}
	calcFunc := func(k int, v string) (float64, error) {
		num, err := calcSigFloat(numPool[k], v, numPool[k+1])
		if err != nil {
			return 0, err
		}
		tmp := append(numPool[:k], num)
		numPool = append(tmp, numPool[k+2:]...)
		operPool = append(operPool[:k], operPool[k+1:]...)
		return calcPool(numPool, operPool)
	}
	for k, v := range operPool {
		if v == "^" {
			return calcFunc(k, v)
		}
	}
	for k, v := range operPool {
		if v == "*" || v == "/" {
			return calcFunc(k, v)
		}
	}
	for k, v := range operPool {
		return calcFunc(k, v)
	}
	return 0, nil
}

func calcSigFloat(floatA float64, b string, floatC float64) (float64, error) {
	switch b {
	case "+":
		return floatRound(floatA + floatC,15), nil
	case "-":
		return floatRound(floatA - floatC,15), nil
	case "*":
		return floatRound(floatA * floatC,15), nil
	case "/":
		if floatC == 0 {
			return 0, errors.New("Divisor cannot be 0")
		}
		return floatRound(floatA / floatC,15), nil
	case "^":
		return math.Pow(floatA, floatC), nil
	}
	return 0, fmt.Errorf("unexpect method:%s", b)
}

func calcSig(a, b, c string) (float64, error) {
	floatA, err := parseNumbic(a)
	if err != nil {
		return 0, err
	}
	floatC, err := parseNumbic(c)
	if err != nil {
		return 0, err
	}
	return calcSigFloat(floatA, b, floatC)
}

func calcDuaFloat(a string, floatB float64) (float64, error) {
	switch a {
	case "sin":
		return math.Sin(floatB), nil
	case "cos":
		return math.Cos(floatB), nil
	case "tan":
		return math.Tan(floatB), nil
	case "abs":
		return math.Abs(floatB), nil
	case "arcsin":
		return math.Asin(floatB), nil
	case "arccos":
		return math.Acos(floatB), nil
	case "arctan":
		return math.Atan(floatB), nil
	case "sqrt":
		return math.Sqrt(floatB), nil
	case "loge":
		return math.Log(floatB), nil
	case "log10":
		return math.Log10(floatB), nil
	case "log2":
		return math.Log2(floatB), nil
	case "floor":
		return math.Floor(floatB), nil
	case "ceil":
		return math.Ceil(floatB), nil
	case "round":
		return math.Round(floatB), nil
	case "trunc":
		return math.Trunc(floatB), nil
	case "+":
		return 0 + floatB, nil
	case "-":
		return 0 - floatB, nil
	}
	return 0, fmt.Errorf("unexpect method:%s", a)
}
func calcDua(a, b string) (float64, error) {
	floatB, err := parseNumbic(b)
	if err != nil {
		return 0, err
	}
	return calcDuaFloat(a, floatB)
}

func parseNumbic(str string) (float64, error) {
	switch str {
	case "pi":
		return float64(math.Pi), nil
	case "e":
		return float64(math.E), nil
	default:
		return strconv.ParseFloat(str, 64)
	}
}

func containSign(str string) bool {
	var sign []string = []string{"+", "-", "*", "/", "^"}
	for _, v := range sign {
		if str == v {
			return true
		}
	}
	return false
}

func contain(pool []string, str string) bool {
	for _, v := range pool {
		if v == str {
			return true
		}
	}
	return false
}
