package classic

import (
	"fmt"

	"github.com/grafana/grafana/pkg/expr/mathexp"
)

type evaluator interface {
	Eval(mathexp.Number) bool
}

type noValueEvaluator struct{}

type thresholdEvaluator struct {
	Type      string
	Threshold float64
}

type rangedEvaluator struct {
	Type  string
	Lower float64
	Upper float64
}

// newAlertEvaluator is a factory function for returning
// an AlertEvaluator depending on evaluation operator.
func newAlertEvaluator(model conditionEvalJSON) (evaluator, error) {
	switch model.Type {
	case "gt", "lt":
		return newThresholdEvaluator(model)
	case "within_range", "outside_range":
		return newRangedEvaluator(model)
	case "no_value":
		return &noValueEvaluator{}, nil
	}

	return nil, fmt.Errorf("evaluator invalid evaluator type: %s", model.Type)
}

func (e *thresholdEvaluator) Eval(reducedValue mathexp.Number) bool {
	fv := reducedValue.GetFloat64Value()
	if fv == nil {
		return false
	}

	switch e.Type {
	case "gt":
		return *fv > e.Threshold
	case "lt":
		return *fv < e.Threshold
	}

	return false
}

func newThresholdEvaluator(model conditionEvalJSON) (*thresholdEvaluator, error) {
	if len(model.Params) == 0 {
		return nil, fmt.Errorf("evaluator '%v' is missing the threshold parameter", model.Type)
	}

	return &thresholdEvaluator{
		Type:      model.Type,
		Threshold: model.Params[0],
	}, nil
}

func (e *noValueEvaluator) Eval(reducedValue mathexp.Number) bool {
	return reducedValue.GetFloat64Value() == nil
}

func newRangedEvaluator(model conditionEvalJSON) (*rangedEvaluator, error) {
	if len(model.Params) != 2 {
		return nil, fmt.Errorf("ranged evaluator requires 2 parameters")
	}

	return &rangedEvaluator{
		Type:  model.Type,
		Lower: model.Params[0],
		Upper: model.Params[1],
	}, nil
}

func (e *rangedEvaluator) Eval(reducedValue mathexp.Number) bool {
	fv := reducedValue.GetFloat64Value()
	if fv == nil {
		return false
	}

	switch e.Type {
	case "within_range":
		return (e.Lower < *fv && e.Upper > *fv) || (e.Upper < *fv && e.Lower > *fv)
	case "outside_range":
		return (e.Upper < *fv && e.Lower < *fv) || (e.Upper > *fv && e.Lower > *fv)
	}

	return false
}
