// Copyright 2011 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oglematchers

import (
	"errors"
	"fmt"
	"math"
	"reflect"
)

// LessThan returns a matcher that matches integer, floating point, or strings
// values v such that v < x. Comparison is not defined between numeric and
// string types, but is defined between all integer and floating point types.
//
// x must itself be an integer, floating point, or string type; otherwise,
// LessThan will panic.
func LessThan(x any) Matcher {
	v := reflect.ValueOf(x)
	kind := v.Kind()

	switch {
	case isInteger(v):
	case isFloat(v):
	case kind == reflect.String:

	default:
		panic(fmt.Sprintf("LessThan: unexpected kind %v", kind))
	}

	return &lessThanMatcher{v}
}

type lessThanMatcher struct {
	limit reflect.Value
}

func (m *lessThanMatcher) Description() string {
	// Special case: make it clear that strings are strings.
	if m.limit.Kind() == reflect.String {
		return fmt.Sprintf("less than \"%s\"", m.limit.String())
	}

	return fmt.Sprintf("less than %v", m.limit.Interface())
}

func compareIntegers(v1, v2 reflect.Value) (err error) {
	err = errors.New("")

	switch {
	case isSignedInteger(v1) && isSignedInteger(v2):
		if v1.Int() < v2.Int() {
			err = nil
		}
		return

	case isSignedInteger(v1) && isUnsignedInteger(v2):
		if v1.Int() < 0 || uint64(v1.Int()) < v2.Uint() {
			err = nil
		}
		return

	case isUnsignedInteger(v1) && isSignedInteger(v2):
		if v1.Uint() <= math.MaxInt64 && int64(v1.Uint()) < v2.Int() {
			err = nil
		}
		return

	case isUnsignedInteger(v1) && isUnsignedInteger(v2):
		if v1.Uint() < v2.Uint() {
			err = nil
		}
		return
	}

	panic(fmt.Sprintf("compareIntegers: %v %v", v1, v2))
}

func getFloat(v reflect.Value) float64 {
	switch {
	case isSignedInteger(v):
		return float64(v.Int())

	case isUnsignedInteger(v):
		return float64(v.Uint())

	case isFloat(v):
		return v.Float()
	}

	panic(fmt.Sprintf("getFloat: %v", v))
}

func (m *lessThanMatcher) Matches(c any) (err error) {
	v1 := reflect.ValueOf(c)
	v2 := m.limit

	err = errors.New("")

	// Handle strings as a special case.
	if v1.Kind() == reflect.String && v2.Kind() == reflect.String {
		if v1.String() < v2.String() {
			err = nil
		}
		return
	}

	// If we get here, we require that we are dealing with integers or floats.
	v1Legal := isInteger(v1) || isFloat(v1)
	v2Legal := isInteger(v2) || isFloat(v2)
	if !v1Legal || !v2Legal {
		err = NewFatalError("which is not comparable")
		return
	}

	// Handle the various comparison cases.
	switch {
	// Both integers
	case isInteger(v1) && isInteger(v2):
		return compareIntegers(v1, v2)

	// At least one float32
	case v1.Kind() == reflect.Float32 || v2.Kind() == reflect.Float32:
		if float32(getFloat(v1)) < float32(getFloat(v2)) {
			err = nil
		}
		return

	// At least one float64
	case v1.Kind() == reflect.Float64 || v2.Kind() == reflect.Float64:
		if getFloat(v1) < getFloat(v2) {
			err = nil
		}
		return
	}

	// We shouldn't get here.
	panic(fmt.Sprintf("lessThanMatcher.Matches: Shouldn't get here: %v %v", v1, v2))
}
