package validator

import (
	"errors"
	"fmt"
	"strconv"

	"fyne.io/fyne/v2"
)

type floatValidator func(float64) error

var (
	FloatEntryValidator = numericEntryValidator(
		nonNegativeValidator,
		minOrZeroValueValidator(1.),
	)
	IntEntryValidator = numericEntryValidator(
		intValidator,
		nonNegativeValidator,
		minOrZeroValueValidator(1.),
	)
)

func NewStackValidator(first fyne.StringValidator, rest ...fyne.StringValidator) fyne.StringValidator {
	if first == nil {
		panic("first validator cannot be nil")
	}
	return func(s string) error {
		if err := first(s); err != nil {
			return err
		}
		for i := range rest {
			if err := rest[i](s); err != nil {
				return err
			}
		}
		return nil
	}
}

func NewMutualValidator(other func() float64, valid func(float64) bool) fyne.StringValidator {
	if other == nil {
		panic("other value getter cannot be nil")
	}
	return func(s string) error {
		myValue, err := ParseFloat(s)
		if err != nil {
			return err
		}
		if !valid(myValue) {
			return errors.New("invalid value")
		}
		if !valid(other()) {
			return errors.New("invalid other value")
		}
		return nil
	}
}

func numericEntryValidator(other ...floatValidator) fyne.StringValidator {
	return func(s string) error {
		v, err := ParseFloat(s)
		if err != nil {
			return errors.New("not a float value")
		}
		for i := range other {
			if err := other[i](v); err != nil {
				return err
			}
		}
		return nil
	}
}

func nonNegativeValidator(v float64) error {
	if v < 0 {
		return errors.New("value must be greater of equal to zero")
	}
	return nil
}

func intValidator(v float64) error {
	if float64(int(v)) != v {
		return errors.New("value must be an integer")
	}
	return nil
}

func minOrZeroValueValidator(min float64) floatValidator {
	return func(f float64) error {
		if f > 0 && f < min {
			return fmt.Errorf("value must be zero or >= %f", min)
		}
		return nil
	}
}

func FloatValueValidator(s string) error {
	if _, err := ParseFloat(s); err != nil {
		return errors.New("not a float value")
	}
	return nil
}

func IntValueValidator(s string) error {
	if _, err := ParseInt(s); err != nil {
		return errors.New("not an integer value")
	}
	return nil
}

func ParseFloat(s string) (float64, error) {
	return strconv.ParseFloat(s, 64)
}

func ParseInt(s string) (int, error) {
	if v, err := strconv.ParseInt(s, 10, 64); err != nil {
		return 0, err
	} else {
		return int(v), nil
	}
}
