package reads

import (
	"math"
	"regexp"

	"github.com/influxdata/influxql"
)

// evalExpr evaluates expr against a map.
func evalExpr(expr influxql.Expr, m Valuer) interface{} {
	if expr == nil {
		return nil
	}

	switch expr := expr.(type) {
	case *influxql.BinaryExpr:
		return evalBinaryExpr(expr, m)
	case *influxql.BooleanLiteral:
		return expr.Val
	case *influxql.IntegerLiteral:
		return expr.Val
	case *influxql.UnsignedLiteral:
		return expr.Val
	case *influxql.NumberLiteral:
		return expr.Val
	case *influxql.ParenExpr:
		return evalExpr(expr.Expr, m)
	case *influxql.RegexLiteral:
		return expr.Val
	case *influxql.StringLiteral:
		return expr.Val
	case *influxql.VarRef:
		v, _ := m.Value(expr.Val)
		return v
	default:
		return nil
	}
}

func evalBinaryExpr(expr *influxql.BinaryExpr, m Valuer) interface{} {
	lhs := evalExpr(expr.LHS, m)
	rhs := evalExpr(expr.RHS, m)
	if lhs == nil && rhs != nil {
		// When the LHS is nil and the RHS is a boolean, implicitly cast the
		// nil to false.
		if _, ok := rhs.(bool); ok {
			lhs = false
		}
	} else if lhs != nil && rhs == nil {
		// Implicit cast of the RHS nil to false when the LHS is a boolean.
		if _, ok := lhs.(bool); ok {
			rhs = false
		}
	}

	// Evaluate if both sides are simple types.
	switch lhs := lhs.(type) {
	case bool:
		rhs, ok := rhs.(bool)
		switch expr.Op {
		case influxql.AND:
			return ok && (lhs && rhs)
		case influxql.OR:
			return ok && (lhs || rhs)
		case influxql.BITWISE_AND:
			return ok && (lhs && rhs)
		case influxql.BITWISE_OR:
			return ok && (lhs || rhs)
		case influxql.BITWISE_XOR:
			return ok && (lhs != rhs)
		case influxql.EQ:
			return ok && (lhs == rhs)
		case influxql.NEQ:
			return ok && (lhs != rhs)
		}
	case float64:
		// Try the rhs as a float64 or int64
		rhsf, ok := rhs.(float64)
		if !ok {
			var rhsi int64
			if rhsi, ok = rhs.(int64); ok {
				rhsf = float64(rhsi)
			}
		}

		rhs := rhsf
		switch expr.Op {
		case influxql.EQ:
			return ok && (lhs == rhs)
		case influxql.NEQ:
			return ok && (lhs != rhs)
		case influxql.LT:
			return ok && (lhs < rhs)
		case influxql.LTE:
			return ok && (lhs <= rhs)
		case influxql.GT:
			return ok && (lhs > rhs)
		case influxql.GTE:
			return ok && (lhs >= rhs)
		case influxql.ADD:
			if !ok {
				return nil
			}
			return lhs + rhs
		case influxql.SUB:
			if !ok {
				return nil
			}
			return lhs - rhs
		case influxql.MUL:
			if !ok {
				return nil
			}
			return lhs * rhs
		case influxql.DIV:
			if !ok {
				return nil
			} else if rhs == 0 {
				return float64(0)
			}
			return lhs / rhs
		case influxql.MOD:
			if !ok {
				return nil
			}
			return math.Mod(lhs, rhs)
		}
	case int64:
		// Try as a float64 to see if a float cast is required.
		rhsf, ok := rhs.(float64)
		if ok {
			lhs := float64(lhs)
			rhs := rhsf
			switch expr.Op {
			case influxql.EQ:
				return lhs == rhs
			case influxql.NEQ:
				return lhs != rhs
			case influxql.LT:
				return lhs < rhs
			case influxql.LTE:
				return lhs <= rhs
			case influxql.GT:
				return lhs > rhs
			case influxql.GTE:
				return lhs >= rhs
			case influxql.ADD:
				return lhs + rhs
			case influxql.SUB:
				return lhs - rhs
			case influxql.MUL:
				return lhs * rhs
			case influxql.DIV:
				if rhs == 0 {
					return float64(0)
				}
				return lhs / rhs
			case influxql.MOD:
				return math.Mod(lhs, rhs)
			}
		} else {
			rhs, ok := rhs.(int64)
			switch expr.Op {
			case influxql.EQ:
				return ok && (lhs == rhs)
			case influxql.NEQ:
				return ok && (lhs != rhs)
			case influxql.LT:
				return ok && (lhs < rhs)
			case influxql.LTE:
				return ok && (lhs <= rhs)
			case influxql.GT:
				return ok && (lhs > rhs)
			case influxql.GTE:
				return ok && (lhs >= rhs)
			case influxql.ADD:
				if !ok {
					return nil
				}
				return lhs + rhs
			case influxql.SUB:
				if !ok {
					return nil
				}
				return lhs - rhs
			case influxql.MUL:
				if !ok {
					return nil
				}
				return lhs * rhs
			case influxql.DIV:
				if !ok {
					return nil
				} else if rhs == 0 {
					return float64(0)
				}
				return lhs / rhs
			case influxql.MOD:
				if !ok {
					return nil
				} else if rhs == 0 {
					return int64(0)
				}
				return lhs % rhs
			case influxql.BITWISE_AND:
				if !ok {
					return nil
				}
				return lhs & rhs
			case influxql.BITWISE_OR:
				if !ok {
					return nil
				}
				return lhs | rhs
			case influxql.BITWISE_XOR:
				if !ok {
					return nil
				}
				return lhs ^ rhs
			}
		}
	case string:
		switch expr.Op {
		case influxql.EQ:
			rhs, ok := rhs.(string)
			if !ok {
				return nil
			}
			return lhs == rhs
		case influxql.NEQ:
			rhs, ok := rhs.(string)
			if !ok {
				return nil
			}
			return lhs != rhs
		case influxql.EQREGEX:
			rhs, ok := rhs.(*regexp.Regexp)
			if !ok {
				return nil
			}
			return rhs.MatchString(lhs)
		case influxql.NEQREGEX:
			rhs, ok := rhs.(*regexp.Regexp)
			if !ok {
				return nil
			}
			return !rhs.MatchString(lhs)
		}
	case []byte:
		switch expr.Op {
		case influxql.EQ:
			rhs, ok := rhs.(string)
			if !ok {
				return nil
			}
			return string(lhs) == rhs
		case influxql.NEQ:
			rhs, ok := rhs.(string)
			if !ok {
				return nil
			}
			return string(lhs) != rhs
		case influxql.EQREGEX:
			rhs, ok := rhs.(*regexp.Regexp)
			if !ok {
				return nil
			}
			return rhs.Match(lhs)
		case influxql.NEQREGEX:
			rhs, ok := rhs.(*regexp.Regexp)
			if !ok {
				return nil
			}
			return !rhs.Match(lhs)
		}
	}
	return nil
}

func EvalExprBool(expr influxql.Expr, m Valuer) bool {
	v, _ := evalExpr(expr, m).(bool)
	return v
}
