package exp

import (
	"reflect"
	"regexp"
)

type boolean struct {
	lhs Expression
	rhs interface{}
	op  BooleanOperation
}

func NewBooleanExpression(op BooleanOperation, lhs Expression, rhs interface{}) BooleanExpression {
	return boolean{op: op, lhs: lhs, rhs: rhs}
}

func (b boolean) Clone() Expression {
	return NewBooleanExpression(b.op, b.lhs.Clone(), b.rhs)
}

func (b boolean) Expression() Expression {
	return b
}

func (b boolean) RHS() interface{} {
	return b.rhs
}

func (b boolean) LHS() Expression {
	return b.lhs
}

func (b boolean) Op() BooleanOperation {
	return b.op
}

// used internally to create an equality BooleanExpression
func eq(lhs Expression, rhs interface{}) BooleanExpression {
	return checkBoolExpType(EqOp, lhs, rhs, false)
}

// used internally to create an in-equality BooleanExpression
func neq(lhs Expression, rhs interface{}) BooleanExpression {
	return checkBoolExpType(EqOp, lhs, rhs, true)
}

// used internally to create an gt comparison BooleanExpression
func gt(lhs Expression, rhs interface{}) BooleanExpression {
	return NewBooleanExpression(GtOp, lhs, rhs)
}

// used internally to create an gte comparison BooleanExpression
func gte(lhs Expression, rhs interface{}) BooleanExpression {
	return NewBooleanExpression(GteOp, lhs, rhs)
}

// used internally to create an lt comparison BooleanExpression
func lt(lhs Expression, rhs interface{}) BooleanExpression {
	return NewBooleanExpression(LtOp, lhs, rhs)
}

// used internally to create an lte comparison BooleanExpression
func lte(lhs Expression, rhs interface{}) BooleanExpression {
	return NewBooleanExpression(LteOp, lhs, rhs)
}

// used internally to create an IN BooleanExpression
func in(lhs Expression, vals ...interface{}) BooleanExpression {
	if len(vals) == 1 && reflect.Indirect(reflect.ValueOf(vals[0])).Kind() == reflect.Slice {
		return NewBooleanExpression(InOp, lhs, vals[0])
	}
	return NewBooleanExpression(InOp, lhs, vals)
}

// used internally to create a NOT IN BooleanExpression
func notIn(lhs Expression, vals ...interface{}) BooleanExpression {
	if len(vals) == 1 && reflect.Indirect(reflect.ValueOf(vals[0])).Kind() == reflect.Slice {
		return NewBooleanExpression(NotInOp, lhs, vals[0])
	}
	return NewBooleanExpression(NotInOp, lhs, vals)
}

// used internally to create an IS BooleanExpression
func is(lhs Expression, val interface{}) BooleanExpression {
	return checkBoolExpType(IsOp, lhs, val, false)
}

// used internally to create an IS NOT BooleanExpression
func isNot(lhs Expression, val interface{}) BooleanExpression {
	return checkBoolExpType(IsOp, lhs, val, true)
}

// used internally to create a LIKE BooleanExpression
func like(lhs Expression, val interface{}) BooleanExpression {
	return checkLikeExp(LikeOp, lhs, val, false)
}

// used internally to create an ILIKE BooleanExpression
func iLike(lhs Expression, val interface{}) BooleanExpression {
	return checkLikeExp(ILikeOp, lhs, val, false)
}

// used internally to create a NOT LIKE BooleanExpression
func notLike(lhs Expression, val interface{}) BooleanExpression {
	return checkLikeExp(LikeOp, lhs, val, true)
}

// used internally to create a NOT ILIKE BooleanExpression
func notILike(lhs Expression, val interface{}) BooleanExpression {
	return checkLikeExp(ILikeOp, lhs, val, true)
}

// checks an like rhs to create the proper like expression for strings or regexps
func checkLikeExp(op BooleanOperation, lhs Expression, val interface{}, invert bool) BooleanExpression {
	rhs := val

	if t, ok := val.(*regexp.Regexp); ok {
		if op == LikeOp {
			op = RegexpLikeOp
		} else if op == ILikeOp {
			op = RegexpILikeOp
		}
		rhs = t.String()
	}
	if invert {
		op = operatorInversions[op]
	}
	return NewBooleanExpression(op, lhs, rhs)
}

// checks a boolean operation normalizing the operation based on the RHS (e.g. "a" = true vs "a" IS TRUE
func checkBoolExpType(op BooleanOperation, lhs Expression, rhs interface{}, invert bool) BooleanExpression {
	if rhs == nil {
		op = IsOp
	} else {
		switch reflect.Indirect(reflect.ValueOf(rhs)).Kind() {
		case reflect.Bool:
			op = IsOp
		case reflect.Slice:
			// if its a slice of bytes dont treat as an IN
			if _, ok := rhs.([]byte); !ok {
				op = InOp
			}
		case reflect.Struct:
			switch rhs.(type) {
			case SQLExpression:
				op = InOp
			case AppendableExpression:
				op = InOp
			case *regexp.Regexp:
				return checkLikeExp(LikeOp, lhs, rhs, invert)
			}
		}
	}
	if invert {
		op = operatorInversions[op]
	}
	return NewBooleanExpression(op, lhs, rhs)
}
