// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package db

import (
	"reflect"
	"time"
)

// Comparison defines methods for representing comparison operators in a
// portable way across databases.
type Comparison interface {
	Operator() ComparisonOperator

	Value() interface{}
}

// ComparisonOperator is a type we use to label comparison operators.
type ComparisonOperator uint8

// Comparison operators
const (
	ComparisonOperatorNone ComparisonOperator = iota

	ComparisonOperatorEqual
	ComparisonOperatorNotEqual

	ComparisonOperatorLessThan
	ComparisonOperatorGreaterThan

	ComparisonOperatorLessThanOrEqualTo
	ComparisonOperatorGreaterThanOrEqualTo

	ComparisonOperatorBetween
	ComparisonOperatorNotBetween

	ComparisonOperatorIn
	ComparisonOperatorNotIn

	ComparisonOperatorIs
	ComparisonOperatorIsNot

	ComparisonOperatorLike
	ComparisonOperatorNotLike

	ComparisonOperatorRegExp
	ComparisonOperatorNotRegExp

	ComparisonOperatorAfter
	ComparisonOperatorBefore

	ComparisonOperatorOnOrAfter
	ComparisonOperatorOnOrBefore
)

type dbComparisonOperator struct {
	t  ComparisonOperator
	op string
	v  interface{}
}

func (c *dbComparisonOperator) CustomOperator() string {
	return c.op
}

func (c *dbComparisonOperator) Operator() ComparisonOperator {
	return c.t
}

func (c *dbComparisonOperator) Value() interface{} {
	return c.v
}

// Gte indicates whether the reference is greater than or equal to the given
// argument.
func Gte(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorGreaterThanOrEqualTo,
		v: v,
	}
}

// Lte indicates whether the reference is less than or equal to the given
// argument.
func Lte(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorLessThanOrEqualTo,
		v: v,
	}
}

// Eq indicates whether the constraint is equal to the given argument.
func Eq(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorEqual,
		v: v,
	}
}

// NotEq indicates whether the constraint is not equal to the given argument.
func NotEq(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorNotEqual,
		v: v,
	}
}

// Gt indicates whether the constraint is greater than the given argument.
func Gt(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorGreaterThan,
		v: v,
	}
}

// Lt indicates whether the constraint is less than the given argument.
func Lt(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorLessThan,
		v: v,
	}
}

// In indicates whether the argument is part of the reference.
func In(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorIn,
		v: toInterfaceArray(v),
	}
}

// NotIn indicates whether the argument is not part of the reference.
func NotIn(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorNotIn,
		v: toInterfaceArray(v),
	}
}

// After indicates whether the reference is after the given time.
func After(t time.Time) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorGreaterThan,
		v: t,
	}
}

// Before indicates whether the reference is before the given time.
func Before(t time.Time) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorLessThan,
		v: t,
	}
}

// OnOrAfter indicater whether the reference is after or equal to the given
// time value.
func OnOrAfter(t time.Time) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorGreaterThanOrEqualTo,
		v: t,
	}
}

// OnOrBefore indicates whether the reference is before or equal to the given
// time value.
func OnOrBefore(t time.Time) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorLessThanOrEqualTo,
		v: t,
	}
}

// Between indicates whether the reference is contained between the two given
// values.
func Between(a interface{}, b interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorBetween,
		v: []interface{}{a, b},
	}
}

// NotBetween indicates whether the reference is not contained between the two
// given values.
func NotBetween(a interface{}, b interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorNotBetween,
		v: []interface{}{a, b},
	}
}

// Is indicates whether the reference is nil, true or false.
func Is(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorIs,
		v: v,
	}
}

// IsNot indicates whether the reference is not nil, true nor false.
func IsNot(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorIsNot,
		v: v,
	}
}

// IsNull indicates whether the reference is a NULL value.
func IsNull() Comparison {
	return Is(nil)
}

// IsNotNull indicates whether the reference is a NULL value.
func IsNotNull() Comparison {
	return IsNot(nil)
}

/*
// IsDistinctFrom indicates whether the reference is different from
// the given value, including NULL values.
func IsDistinctFrom(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorIsDistinctFrom,
		v: v,
	}
}

// IsNotDistinctFrom indicates whether the reference is not different from the
// given value, including NULL values.
func IsNotDistinctFrom(v interface{}) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorIsNotDistinctFrom,
		v: v,
	}
}
*/

// Like indicates whether the reference matches the wildcard value.
func Like(v string) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorLike,
		v: v,
	}
}

// NotLike indicates whether the reference does not match the wildcard value.
func NotLike(v string) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorNotLike,
		v: v,
	}
}

/*
// ILike indicates whether the reference matches the wildcard value (case
// insensitive).
func ILike(v string) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorILike,
		v: v,
	}
}

// NotILike indicates whether the reference does not match the wildcard value
// (case insensitive).
func NotILike(v string) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorNotILike,
		v: v,
	}
}
*/

// RegExp indicates whether the reference matches the regexp pattern.
func RegExp(v string) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorRegExp,
		v: v,
	}
}

// NotRegExp indicates whether the reference does not match the regexp pattern.
func NotRegExp(v string) Comparison {
	return &dbComparisonOperator{
		t: ComparisonOperatorNotRegExp,
		v: v,
	}
}

// Op represents a custom comparison operator against the reference.
func Op(customOperator string, v interface{}) Comparison {
	return &dbComparisonOperator{
		op: customOperator,
		t:  ComparisonOperatorNone,
		v:  v,
	}
}

func toInterfaceArray(v interface{}) []interface{} {
	rv := reflect.ValueOf(v)
	switch rv.Type().Kind() {
	case reflect.Ptr:
		return toInterfaceArray(rv.Elem().Interface())
	case reflect.Slice:
		elems := rv.Len()
		args := make([]interface{}, elems)
		for i := 0; i < elems; i++ {
			args[i] = rv.Index(i).Interface()
		}
		return args
	}
	return []interface{}{v}
}

var _ Comparison = &dbComparisonOperator{}
