package bexpr

import (
	"fmt"
	"reflect"
	"regexp"
	"strings"
)

var byteSliceTyp reflect.Type = reflect.TypeOf([]byte{})

var primitiveEqualityFns = map[reflect.Kind]func(first interface{}, second reflect.Value) bool{
	reflect.Bool:    doEqualBool,
	reflect.Int:     doEqualInt,
	reflect.Int8:    doEqualInt8,
	reflect.Int16:   doEqualInt16,
	reflect.Int32:   doEqualInt32,
	reflect.Int64:   doEqualInt64,
	reflect.Uint:    doEqualUint,
	reflect.Uint8:   doEqualUint8,
	reflect.Uint16:  doEqualUint16,
	reflect.Uint32:  doEqualUint32,
	reflect.Uint64:  doEqualUint64,
	reflect.Float32: doEqualFloat32,
	reflect.Float64: doEqualFloat64,
	reflect.String:  doEqualString,
}

func doEqualBool(first interface{}, second reflect.Value) bool {
	return first.(bool) == second.Bool()
}

func doEqualInt(first interface{}, second reflect.Value) bool {
	return first.(int) == int(second.Int())
}

func doEqualInt8(first interface{}, second reflect.Value) bool {
	return first.(int8) == int8(second.Int())
}

func doEqualInt16(first interface{}, second reflect.Value) bool {
	return first.(int16) == int16(second.Int())
}

func doEqualInt32(first interface{}, second reflect.Value) bool {
	return first.(int32) == int32(second.Int())
}

func doEqualInt64(first interface{}, second reflect.Value) bool {
	return first.(int64) == second.Int()
}

func doEqualUint(first interface{}, second reflect.Value) bool {
	return first.(uint) == uint(second.Uint())
}

func doEqualUint8(first interface{}, second reflect.Value) bool {
	return first.(uint8) == uint8(second.Uint())
}

func doEqualUint16(first interface{}, second reflect.Value) bool {
	return first.(uint16) == uint16(second.Uint())
}

func doEqualUint32(first interface{}, second reflect.Value) bool {
	return first.(uint32) == uint32(second.Uint())
}

func doEqualUint64(first interface{}, second reflect.Value) bool {
	return first.(uint64) == second.Uint()
}

func doEqualFloat32(first interface{}, second reflect.Value) bool {
	return first.(float32) == float32(second.Float())
}

func doEqualFloat64(first interface{}, second reflect.Value) bool {
	return first.(float64) == second.Float()
}

func doEqualString(first interface{}, second reflect.Value) bool {
	return first.(string) == second.String()
}

// Get rid of 0 to many levels of pointers to get at the real type
func derefType(rtype reflect.Type) reflect.Type {
	for rtype.Kind() == reflect.Ptr {
		rtype = rtype.Elem()
	}
	return rtype
}

func doMatchMatches(expression *MatchExpression, value reflect.Value) (bool, error) {
	if !value.Type().ConvertibleTo(byteSliceTyp) {
		return false, fmt.Errorf("Value of type %s is not convertible to []byte", value.Type())
	}

	re := expression.Value.Converted.(*regexp.Regexp)

	return re.Match(value.Convert(byteSliceTyp).Interface().([]byte)), nil
}

func doMatchEqual(expression *MatchExpression, value reflect.Value) (bool, error) {
	// NOTE: see preconditions in evaluateMatchExpressionRecurse
	eqFn := primitiveEqualityFns[value.Kind()]
	matchValue := getMatchExprValue(expression)
	return eqFn(matchValue, value), nil
}

func doMatchIn(expression *MatchExpression, value reflect.Value) (bool, error) {
	// NOTE: see preconditions in evaluateMatchExpressionRecurse
	matchValue := getMatchExprValue(expression)

	switch kind := value.Kind(); kind {
	case reflect.Map:
		found := value.MapIndex(reflect.ValueOf(matchValue))
		return found.IsValid(), nil
	case reflect.Slice, reflect.Array:
		itemType := derefType(value.Type().Elem())
		eqFn := primitiveEqualityFns[itemType.Kind()]

		for i := 0; i < value.Len(); i++ {
			item := value.Index(i)

			// the value will be the correct type as we verified the itemType
			if eqFn(matchValue, reflect.Indirect(item)) {
				return true, nil
			}
		}

		return false, nil
	case reflect.String:
		return strings.Contains(value.String(), matchValue.(string)), nil
	default:
		// this shouldn't be possible but we have to have something to return to keep the compiler happy
		return false, fmt.Errorf("Cannot perform in/contains operations on type %s for selector: %q", kind, expression.Selector)
	}
}

func doMatchIsEmpty(matcher *MatchExpression, value reflect.Value) (bool, error) {
	// NOTE: see preconditions in evaluateMatchExpressionRecurse
	return value.Len() == 0, nil
}

func getMatchExprValue(expression *MatchExpression) interface{} {
	// NOTE: see preconditions in evaluateMatchExpressionRecurse
	if expression.Value == nil {
		return nil
	}

	if expression.Value.Converted != nil {
		return expression.Value.Converted
	}

	return expression.Value.Raw
}

func evaluateMatchExpressionRecurse(expression *MatchExpression, depth int, rvalue reflect.Value, fields FieldConfigurations) (bool, error) {
	// NOTE: Some information about preconditions is probably good to have here. Parsing
	//       as well as the extra validation pass that MUST occur before executing the
	//       expression evaluation allow us to make some assumptions here.
	//
	//       1. Selectors MUST be valid. Therefore we don't need to test if they should
	//          be valid. This means that we can index in the FieldConfigurations map
	//          and a configuration MUST be present.
	//       2. If expression.Value could be converted it will already have been. No need to try
	//          and convert again. There is also no need to check that the types match as they MUST
	//          in order to have passed validation.
	//       3. If we are presented with a map and we have more selectors to go through then its key
	//          type MUST be a string
	//       4. We already have validated that the operations can be performed on the target data.
	//          So calls to the doMatch* functions don't need to do any checking to ensure that
	//          calling various fns on them will work and not panic - because they wont.

	if depth >= len(expression.Selector) {
		// we have reached the end of the selector - execute the match operations
		switch expression.Operator {
		case MatchEqual:
			return doMatchEqual(expression, rvalue)
		case MatchNotEqual:
			result, err := doMatchEqual(expression, rvalue)
			if err == nil {
				return !result, nil
			}
			return false, err
		case MatchIn:
			return doMatchIn(expression, rvalue)
		case MatchNotIn:
			result, err := doMatchIn(expression, rvalue)
			if err == nil {
				return !result, nil
			}
			return false, err
		case MatchIsEmpty:
			return doMatchIsEmpty(expression, rvalue)
		case MatchIsNotEmpty:
			result, err := doMatchIsEmpty(expression, rvalue)
			if err == nil {
				return !result, nil
			}
			return false, err
		case MatchMatches:
			return doMatchMatches(expression, rvalue)
		case MatchNotMatches:
			result, err := doMatchMatches(expression, rvalue)
			if err == nil {
				return !result, nil
			}
			return false, err
		default:
			return false, fmt.Errorf("Invalid match operation: %d", expression.Operator)
		}
	}

	switch rvalue.Kind() {
	case reflect.Struct:
		fieldName := expression.Selector[depth]
		fieldConfig := fields[FieldName(fieldName)]

		if fieldConfig.StructFieldName != "" {
			fieldName = fieldConfig.StructFieldName
		}

		value := reflect.Indirect(rvalue.FieldByName(fieldName))

		if matcher, ok := value.Interface().(MatchExpressionEvaluator); ok {
			return matcher.EvaluateMatch(expression.Selector[depth+1:], expression.Operator, getMatchExprValue(expression))
		}

		return evaluateMatchExpressionRecurse(expression, depth+1, value, fieldConfig.SubFields)

	case reflect.Slice, reflect.Array:
		// TODO (mkeeler) - Should we support implementing the MatchExpressionEvaluator interface for slice/array types?
		//                  Punting on that for now.
		for i := 0; i < rvalue.Len(); i++ {
			item := reflect.Indirect(rvalue.Index(i))
			// we use the same depth because right now we are not allowing
			// selection of individual slice/array elements
			result, err := evaluateMatchExpressionRecurse(expression, depth, item, fields)
			if err != nil {
				return false, err
			}

			// operations on slices are implicity ANY operations currently so the first truthy evaluation we find we can stop
			if result {
				return true, nil
			}
		}

		return false, nil
	case reflect.Map:
		// TODO (mkeeler) - Should we support implementing the MatchExpressionEvaluator interface for map types
		//                  such as the FieldConfigurations type? Maybe later
		//
		value := reflect.Indirect(rvalue.MapIndex(reflect.ValueOf(expression.Selector[depth])))

		if !value.IsValid() {
			// when the key doesn't exist in the map
			switch expression.Operator {
			case MatchEqual, MatchIsNotEmpty, MatchIn:
				return false, nil
			default:
				// MatchNotEqual, MatchIsEmpty, MatchNotIn
				// Whatever you were looking for cannot be equal because it doesn't exist
				// Similarly it cannot be in some other container and every other container
				// is always empty.
				return true, nil
			}
		}

		if matcher, ok := value.Interface().(MatchExpressionEvaluator); ok {
			return matcher.EvaluateMatch(expression.Selector[depth+1:], expression.Operator, getMatchExprValue(expression))
		}

		return evaluateMatchExpressionRecurse(expression, depth+1, value, fields[FieldNameAny].SubFields)
	default:
		return false, fmt.Errorf("Value at selector %q with type %s does not support nested field selection", expression.Selector[:depth], rvalue.Kind())
	}
}

func evaluateMatchExpression(expression *MatchExpression, datum interface{}, fields FieldConfigurations) (bool, error) {
	if matcher, ok := datum.(MatchExpressionEvaluator); ok {
		return matcher.EvaluateMatch(expression.Selector, expression.Operator, getMatchExprValue(expression))
	}

	rvalue := reflect.Indirect(reflect.ValueOf(datum))

	return evaluateMatchExpressionRecurse(expression, 0, rvalue, fields)
}

func evaluate(ast Expression, datum interface{}, fields FieldConfigurations) (bool, error) {
	switch node := ast.(type) {
	case *UnaryExpression:
		switch node.Operator {
		case UnaryOpNot:
			result, err := evaluate(node.Operand, datum, fields)
			return !result, err
		}
	case *BinaryExpression:
		switch node.Operator {
		case BinaryOpAnd:
			result, err := evaluate(node.Left, datum, fields)
			if err != nil || result == false {
				return result, err
			}

			return evaluate(node.Right, datum, fields)

		case BinaryOpOr:
			result, err := evaluate(node.Left, datum, fields)
			if err != nil || result == true {
				return result, err
			}

			return evaluate(node.Right, datum, fields)
		}
	case *MatchExpression:
		return evaluateMatchExpression(node, datum, fields)
	}
	return false, fmt.Errorf("Invalid AST node")
}
