package build

import (
	"fmt"
	"reflect"
	"strconv"

	"goutil/xmlUtil/gxpath/internal/query"
)

// valueType is a return value type.
type valueType int

const (
	booleanType valueType = iota
	numberType
	stringType
	nodeSetType
)

func getValueType(i interface{}) valueType {
	v := reflect.ValueOf(i)
	switch v.Kind() {
	case reflect.Float64:
		return numberType
	case reflect.String:
		return stringType
	case reflect.Bool:
		return booleanType
	default:
		if _, ok := i.(query.Query); ok {
			return nodeSetType
		}
	}
	panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
}

type logical func(query.Iterator, string, interface{}, interface{}) bool

var logicalFuncs = [][]logical{
	[]logical{cmpBoolean_Boolean, nil, nil, nil},
	[]logical{nil, cmpNumeric_Numeric, cmpNumeric_String, cmpNumeric_NodeSet},
	[]logical{nil, cmpString_Numeric, cmpString_String, cmpString_NodeSet},
	[]logical{nil, cmpNodeSet_Numeric, cmpNodeSet_String, cmpNodeSet_NodeSet},
}

// number vs number
func cmpNumberNumberF(op string, a, b float64) bool {
	switch op {
	case "=":
		return a == b
	case ">":
		return a > b
	case "<":
		return a < b
	case ">=":
		return a >= b
	case "<=":
		return a <= b
	case "!=":
		return a != b
	}
	return false
}

// string vs string
func cmpStringStringF(op string, a, b string) bool {
	switch op {
	case "=":
		return a == b
	case ">":
		return a > b
	case "<":
		return a < b
	case ">=":
		return a >= b
	case "<=":
		return a <= b
	case "!=":
		return a != b
	}
	return false
}

func cmpBooleanBooleanF(op string, a, b bool) bool {
	switch op {
	case "or":
		return a || b
	case "and":
		return a && b
	}
	return false
}

func cmpNumeric_Numeric(t query.Iterator, op string, m, n interface{}) bool {
	a := m.(float64)
	b := n.(float64)
	return cmpNumberNumberF(op, a, b)
}

func cmpNumeric_String(t query.Iterator, op string, m, n interface{}) bool {
	a := m.(float64)
	b := n.(string)
	num, err := strconv.ParseFloat(b, 64)
	if err != nil {
		panic(err)
	}
	return cmpNumberNumberF(op, a, num)
}

func cmpNumeric_NodeSet(t query.Iterator, op string, m, n interface{}) bool {
	a := m.(float64)
	b := n.(query.Query)

	for {
		node := b.Select(t)
		if node == nil {
			break
		}
		num, err := strconv.ParseFloat(node.Value(), 64)
		if err != nil {
			panic(err)
		}
		if cmpNumberNumberF(op, a, num) {
			return true
		}
	}
	return false
}

func cmpNodeSet_Numeric(t query.Iterator, op string, m, n interface{}) bool {
	a := m.(query.Query)
	b := n.(float64)
	for {
		node := a.Select(t)
		if node == nil {
			break
		}
		num, err := strconv.ParseFloat(node.Value(), 64)
		if err != nil {
			panic(err)
		}
		if cmpNumberNumberF(op, num, b) {
			return true
		}
	}
	return false
}

func cmpNodeSet_String(t query.Iterator, op string, m, n interface{}) bool {
	a := m.(query.Query)
	b := n.(string)
	for {
		node := a.Select(t)
		if node == nil {
			break
		}
		if cmpStringStringF(op, b, node.Value()) {
			return true
		}
	}
	return false
}

func cmpNodeSet_NodeSet(t query.Iterator, op string, m, n interface{}) bool {
	return false
}

func cmpString_Numeric(t query.Iterator, op string, m, n interface{}) bool {
	a := m.(string)
	b := n.(float64)
	num, err := strconv.ParseFloat(a, 64)
	if err != nil {
		panic(err)
	}
	return cmpNumberNumberF(op, b, num)
}

func cmpString_String(t query.Iterator, op string, m, n interface{}) bool {
	a := m.(string)
	b := n.(string)
	return cmpStringStringF(op, a, b)
}

func cmpString_NodeSet(t query.Iterator, op string, m, n interface{}) bool {
	a := m.(string)
	b := n.(query.Query)
	for {
		node := b.Select(t)
		if node == nil {
			break
		}
		if cmpStringStringF(op, a, node.Value()) {
			return true
		}
	}
	return false
}

func cmpBoolean_Boolean(t query.Iterator, op string, m, n interface{}) bool {
	a := m.(bool)
	b := n.(bool)
	return cmpBooleanBooleanF(op, a, b)
}

// eqFunc is an `=` operator.
func eqFunc(t query.Iterator, m, n interface{}) interface{} {
	t1 := getValueType(m)
	t2 := getValueType(n)
	return logicalFuncs[t1][t2](t, "=", m, n)
}

// gtFunc is an `>` operator.
func gtFunc(t query.Iterator, m, n interface{}) interface{} {
	t1 := getValueType(m)
	t2 := getValueType(n)
	return logicalFuncs[t1][t2](t, ">", m, n)
}

// geFunc is an `>=` operator.
func geFunc(t query.Iterator, m, n interface{}) interface{} {
	t1 := getValueType(m)
	t2 := getValueType(n)
	return logicalFuncs[t1][t2](t, ">=", m, n)
}

// ltFunc is an `<` operator.
func ltFunc(t query.Iterator, m, n interface{}) interface{} {
	t1 := getValueType(m)
	t2 := getValueType(n)
	return logicalFuncs[t1][t2](t, "<", m, n)
}

// leFunc is an `<=` operator.
func leFunc(t query.Iterator, m, n interface{}) interface{} {
	t1 := getValueType(m)
	t2 := getValueType(n)
	return logicalFuncs[t1][t2](t, "<=", m, n)
}

// neFunc is an `!=` operator.
func neFunc(t query.Iterator, m, n interface{}) interface{} {
	t1 := getValueType(m)
	t2 := getValueType(n)
	return logicalFuncs[t1][t2](t, "!=", m, n)
}

// orFunc is an `or` operator.
var orFunc = func(t query.Iterator, m, n interface{}) interface{} {
	t1 := getValueType(m)
	t2 := getValueType(n)
	return logicalFuncs[t1][t2](t, "or", m, n)
}

func numericExpr(m, n interface{}, cb func(float64, float64) float64) float64 {
	typ := reflect.TypeOf(float64(0))
	a := reflect.ValueOf(m).Convert(typ)
	b := reflect.ValueOf(n).Convert(typ)
	return cb(a.Float(), b.Float())
}

// plusFunc is an `+` operator.
var plusFunc = func(m, n interface{}) interface{} {
	return numericExpr(m, n, func(a, b float64) float64 {
		return a + b
	})
}

// minusFunc is an `-` operator.
var minusFunc = func(m, n interface{}) interface{} {
	return numericExpr(m, n, func(a, b float64) float64 {
		return a - b
	})
}

// mulFunc is an `*` operator.
var mulFunc = func(m, n interface{}) interface{} {
	return numericExpr(m, n, func(a, b float64) float64 {
		return a * b
	})
}

// divFunc is an `DIV` operator.
var divFunc = func(m, n interface{}) interface{} {
	return numericExpr(m, n, func(a, b float64) float64 {
		return a / b
	})
}

// modFunc is an 'MOD' operator.
var modFunc = func(m, n interface{}) interface{} {
	return numericExpr(m, n, func(a, b float64) float64 {
		return float64(int(a) % int(b))
	})
}
