package build

import (
	"errors"
	"strings"

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

func predicate(q query.Query) func(xpath.NodeNavigator) bool {
	type Predicater interface {
		Test(xpath.NodeNavigator) bool
	}
	if p, ok := q.(Predicater); ok {
		return p.Test
	}
	return func(xpath.NodeNavigator) bool { return true }
}

// positionFunc is a XPath Node Set functions postion().
var positionFunc = func(q query.Query, t query.Iterator) interface{} {
	var (
		count = 1
		node  = t.Current()
	)
	test := predicate(q)
	for node.MoveToPrevious() {
		if test(node) {
			count++
		}
	}
	return float64(count)
}

// lastFunc is a XPath Node Set functions last().
var lastFunc = func(q query.Query, t query.Iterator) interface{} {
	var (
		count = 0
		node  = t.Current()
	)
	node.MoveToFirst()
	test := predicate(q)
	for {
		if test(node) {
			count++
		}
		if !node.MoveToNext() {
			break
		}
	}
	return float64(count)
}

// countFunc is a XPath Node Set functions count(node-set).
var countFunc = func(q query.Query, t query.Iterator) interface{} {
	var (
		count = 0
		node  = t.Current()
	)
	node.MoveToFirst()
	test := predicate(q)
	for {
		if test(node) {
			count++
		}
		if !node.MoveToNext() {
			break
		}
	}
	return float64(count)
}

// nameFunc is a XPath functions name([node-set]).
var nameFunc = func(q query.Query, t query.Iterator) interface{} {
	return t.Current().LocalName()
}

// startwithFunc is a XPath functions starts-with(string, string).
type startwithFunc struct {
	arg1, arg2 query.Query
}

func (s *startwithFunc) do(q query.Query, t query.Iterator) interface{} {
	var (
		m, n string
		ok   bool
	)
	switch typ := s.arg1.Evaluate(t).(type) {
	case string:
		m = typ
	case query.Query:
		node := typ.Select(t)
		if node == nil {
			return false
		}
		m = node.Value()
	default:
		panic(errors.New("starts-with() function argument type must be string"))
	}
	n, ok = s.arg2.Evaluate(t).(string)
	if !ok {
		panic(errors.New("starts-with() function argument type must be string"))
	}
	return strings.HasPrefix(m, n)
}

// normalizespaceFunc is XPath functions normalize-space(string?)
var normalizespaceFunc = func(q query.Query, t query.Iterator) interface{} {
	var m string
	switch typ := q.Evaluate(t).(type) {
	case string:
		m = typ
	case query.Query:
		node := typ.Select(t)
		if node == nil {
			return false
		}
		m = node.Value()
	}
	return strings.TrimSpace(m)
}

// substringFunc is XPath functions substring function returns a part of a given string.
type substringFunc struct {
	arg1, arg2, arg3 query.Query
}

func (f *substringFunc) do(q query.Query, t query.Iterator) interface{} {
	var m string
	switch typ := f.arg1.Evaluate(t).(type) {
	case string:
		m = typ
	case query.Query:
		node := typ.Select(t)
		if node == nil {
			return false
		}
		m = node.Value()
	}

	var start, length float64
	var ok bool

	if start, ok = f.arg2.Evaluate(t).(float64); !ok {
		panic(errors.New("substring() function first argument type must be int"))
	}
	if f.arg3 != nil {
		if length, ok = f.arg3.Evaluate(t).(float64); !ok {
			panic(errors.New("substring() function second argument type must be int"))
		}
	}
	if (len(m) - int(start)) < int(length) {
		panic(errors.New("substring() function start and length argument out of range"))
	}
	if length > 0 {
		return m[int(start):int(length+start)]
	}
	return m[int(start):]
}
