package cbreaker

import (
	"fmt"

	"github.com/vulcand/oxy/v2/internal/holsterv4/clock"
	"github.com/vulcand/predicate"
)

type hpredicate func(*CircuitBreaker) bool

// parseExpression parses expression in the go language into predicates.
func parseExpression(in string) (hpredicate, error) {
	p, err := predicate.NewParser(predicate.Def{
		Operators: predicate.Operators{
			AND: and,
			OR:  or,
			EQ:  eq,
			NEQ: neq,
			LT:  lt,
			LE:  le,
			GT:  gt,
			GE:  ge,
		},
		Functions: map[string]interface{}{
			"LatencyAtQuantileMS": latencyAtQuantile,
			"NetworkErrorRatio":   networkErrorRatio,
			"ResponseCodeRatio":   responseCodeRatio,
		},
	})
	if err != nil {
		return nil, err
	}
	out, err := p.Parse(in)
	if err != nil {
		return nil, err
	}
	pr, ok := out.(hpredicate)
	if !ok {
		return nil, fmt.Errorf("expected predicate, got %T", out)
	}
	return pr, nil
}

type toInt func(c *CircuitBreaker) int

type toFloat64 func(c *CircuitBreaker) float64

func latencyAtQuantile(quantile float64) toInt {
	return func(c *CircuitBreaker) int {
		h, err := c.metrics.LatencyHistogram()
		if err != nil {
			c.log.Error("Failed to get latency histogram, for %v error: %v", c, err)
			return 0
		}
		return int(h.LatencyAtQuantile(quantile) / clock.Millisecond)
	}
}

func networkErrorRatio() toFloat64 {
	return func(c *CircuitBreaker) float64 {
		return c.metrics.NetworkErrorRatio()
	}
}

func responseCodeRatio(startA, endA, startB, endB int) toFloat64 {
	return func(c *CircuitBreaker) float64 {
		return c.metrics.ResponseCodeRatio(startA, endA, startB, endB)
	}
}

// or returns predicate by joining the passed predicates with logical 'or'.
func or(fns ...hpredicate) hpredicate {
	return func(c *CircuitBreaker) bool {
		for _, fn := range fns {
			if fn(c) {
				return true
			}
		}
		return false
	}
}

// and returns predicate by joining the passed predicates with logical 'and'.
func and(fns ...hpredicate) hpredicate {
	return func(c *CircuitBreaker) bool {
		for _, fn := range fns {
			if !fn(c) {
				return false
			}
		}
		return true
	}
}

// not creates negation of the passed predicate.
func not(p hpredicate) hpredicate {
	return func(c *CircuitBreaker) bool {
		return !p(c)
	}
}

// eq returns predicate that tests for equality of the value of the mapper and the constant.
func eq(m interface{}, value interface{}) (hpredicate, error) {
	switch mapper := m.(type) {
	case toInt:
		return intEQ(mapper, value)
	case toFloat64:
		return float64EQ(mapper, value)
	}
	return nil, fmt.Errorf("eq: unsupported argument: %T", m)
}

// neq returns predicate that tests for inequality of the value of the mapper and the constant.
func neq(m interface{}, value interface{}) (hpredicate, error) {
	p, err := eq(m, value)
	if err != nil {
		return nil, err
	}
	return not(p), nil
}

// lt returns predicate that tests that value of the mapper function is less than the constant.
func lt(m interface{}, value interface{}) (hpredicate, error) {
	switch mapper := m.(type) {
	case toInt:
		return intLT(mapper, value)
	case toFloat64:
		return float64LT(mapper, value)
	}
	return nil, fmt.Errorf("lt: unsupported argument: %T", m)
}

// le returns predicate that tests that value of the mapper function is less or equal than the constant.
func le(m interface{}, value interface{}) (hpredicate, error) {
	l, err := lt(m, value)
	if err != nil {
		return nil, err
	}
	e, err := eq(m, value)
	if err != nil {
		return nil, err
	}
	return func(c *CircuitBreaker) bool {
		return l(c) || e(c)
	}, nil
}

// gt returns predicate that tests that value of the mapper function is greater than the constant.
func gt(m interface{}, value interface{}) (hpredicate, error) {
	switch mapper := m.(type) {
	case toInt:
		return intGT(mapper, value)
	case toFloat64:
		return float64GT(mapper, value)
	}
	return nil, fmt.Errorf("gt: unsupported argument: %T", m)
}

// ge returns predicate that tests that value of the mapper function is less or equal than the constant.
func ge(m interface{}, value interface{}) (hpredicate, error) {
	g, err := gt(m, value)
	if err != nil {
		return nil, err
	}
	e, err := eq(m, value)
	if err != nil {
		return nil, err
	}
	return func(c *CircuitBreaker) bool {
		return g(c) || e(c)
	}, nil
}

func intEQ(m toInt, val interface{}) (hpredicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(c *CircuitBreaker) bool {
		return m(c) == value
	}, nil
}

func float64EQ(m toFloat64, val interface{}) (hpredicate, error) {
	value, ok := val.(float64)
	if !ok {
		return nil, fmt.Errorf("expected float64, got %T", val)
	}
	return func(c *CircuitBreaker) bool {
		return m(c) == value
	}, nil
}

func intLT(m toInt, val interface{}) (hpredicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(c *CircuitBreaker) bool {
		return m(c) < value
	}, nil
}

func intGT(m toInt, val interface{}) (hpredicate, error) {
	value, ok := val.(int)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(c *CircuitBreaker) bool {
		return m(c) > value
	}, nil
}

func float64LT(m toFloat64, val interface{}) (hpredicate, error) {
	value, ok := val.(float64)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(c *CircuitBreaker) bool {
		return m(c) < value
	}, nil
}

func float64GT(m toFloat64, val interface{}) (hpredicate, error) {
	value, ok := val.(float64)
	if !ok {
		return nil, fmt.Errorf("expected int, got %T", val)
	}
	return func(c *CircuitBreaker) bool {
		return m(c) > value
	}, nil
}
