package xpath

import (
	"bytes"
	"fmt"
	"hash/fnv"
	"reflect"
)

// The return type of the XPath expression.
type resultType int

var xpathResultType = struct {
	Boolean resultType
	// A numeric value
	Number resultType
	String resultType
	// A node collection.
	NodeSet resultType
	// Any of the XPath node types.
	Any resultType
}{
	Boolean: 0,
	Number:  1,
	String:  2,
	NodeSet: 3,
	Any:     4,
}

type queryProp int

var queryProps = struct {
	None     queryProp
	Position queryProp
	Count    queryProp
	Cached   queryProp
	Reverse  queryProp
	Merge    queryProp
}{
	None:     0,
	Position: 1,
	Count:    2,
	Cached:   4,
	Reverse:  8,
	Merge:    16,
}

type iterator interface {
	Current() NodeNavigator
}

// An XPath query interface.
type query interface {
	// Select traversing iterator returns a query matched node NodeNavigator.
	Select(iterator) NodeNavigator

	// Evaluate evaluates query and returns values of the current query.
	Evaluate(iterator) interface{}

	Clone() query

	// ValueType returns the value type of the current query.
	ValueType() resultType

	Properties() queryProp
}

// nopQuery is an empty query that always return nil for any query.
type nopQuery struct{}

func (nopQuery) Select(iterator) NodeNavigator { return nil }

func (nopQuery) Evaluate(iterator) interface{} { return nil }

func (nopQuery) Clone() query { return nopQuery{} }

func (nopQuery) ValueType() resultType { return xpathResultType.NodeSet }

func (nopQuery) Properties() queryProp {
	return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
}

// contextQuery is returns current node on the iterator object query.
type contextQuery struct {
	count int
}

func (c *contextQuery) Select(t iterator) NodeNavigator {
	if c.count > 0 {
		return nil
	}
	c.count++
	return t.Current().Copy()
}

func (c *contextQuery) Evaluate(iterator) interface{} {
	c.count = 0
	return c
}

func (c *contextQuery) Clone() query {
	return &contextQuery{}
}

func (c *contextQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (c *contextQuery) Properties() queryProp {
	return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
}

type absoluteQuery struct {
	count int
}

func (a *absoluteQuery) Select(t iterator) (n NodeNavigator) {
	if a.count > 0 {
		return
	}
	a.count++
	n = t.Current().Copy()
	n.MoveToRoot()
	return
}

func (a *absoluteQuery) Evaluate(t iterator) interface{} {
	a.count = 0
	return a
}

func (a *absoluteQuery) Clone() query {
	return &absoluteQuery{}
}

func (a *absoluteQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (a *absoluteQuery) Properties() queryProp {
	return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
}

// ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*)
type ancestorQuery struct {
	name     string
	iterator func() NodeNavigator
	table    map[uint64]bool

	Self      bool
	Input     query
	Predicate func(NodeNavigator) bool
}

func (a *ancestorQuery) Select(t iterator) NodeNavigator {
	if a.table == nil {
		a.table = make(map[uint64]bool)
	}

	for {
		if a.iterator == nil {
			node := a.Input.Select(t)
			if node == nil {
				return nil
			}
			first := true
			node = node.Copy()
			a.iterator = func() NodeNavigator {
				if first {
					first = false
					if a.Self && a.Predicate(node) {
						return node
					}
				}
				for node.MoveToParent() {
					if a.Predicate(node) {
						return node
					}
				}
				return nil
			}
		}

		for node := a.iterator(); node != nil; node = a.iterator() {
			node_id := getHashCode(node.Copy())
			if _, ok := a.table[node_id]; !ok {
				a.table[node_id] = true
				return node
			}
		}
		a.iterator = nil
	}
}

func (a *ancestorQuery) Evaluate(t iterator) interface{} {
	a.Input.Evaluate(t)
	a.iterator = nil
	return a
}

func (a *ancestorQuery) Test(n NodeNavigator) bool {
	return a.Predicate(n)
}

func (a *ancestorQuery) Clone() query {
	return &ancestorQuery{name: a.name, Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate}
}

func (a *ancestorQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (a *ancestorQuery) Properties() queryProp {
	return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge | queryProps.Reverse
}

// attributeQuery is an XPath attribute node query.(@*)
type attributeQuery struct {
	name     string
	iterator func() NodeNavigator

	Input     query
	Predicate func(NodeNavigator) bool
}

func (a *attributeQuery) Select(t iterator) NodeNavigator {
	for {
		if a.iterator == nil {
			node := a.Input.Select(t)
			if node == nil {
				return nil
			}
			node = node.Copy()
			a.iterator = func() NodeNavigator {
				for {
					onAttr := node.MoveToNextAttribute()
					if !onAttr {
						return nil
					}
					if a.Predicate(node) {
						return node
					}
				}
			}
		}

		if node := a.iterator(); node != nil {
			return node
		}
		a.iterator = nil
	}
}

func (a *attributeQuery) Evaluate(t iterator) interface{} {
	a.Input.Evaluate(t)
	a.iterator = nil
	return a
}

func (a *attributeQuery) Test(n NodeNavigator) bool {
	return a.Predicate(n)
}

func (a *attributeQuery) Clone() query {
	return &attributeQuery{name: a.name, Input: a.Input.Clone(), Predicate: a.Predicate}
}

func (a *attributeQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (a *attributeQuery) Properties() queryProp {
	return queryProps.Merge
}

// childQuery is an XPath child node query.(child::*)
type childQuery struct {
	name     string
	posit    int
	iterator func() NodeNavigator

	Input     query
	Predicate func(NodeNavigator) bool
}

func (c *childQuery) Select(t iterator) NodeNavigator {
	for {
		if c.iterator == nil {
			c.posit = 0
			node := c.Input.Select(t)
			if node == nil {
				return nil
			}
			node = node.Copy()
			first := true
			c.iterator = func() NodeNavigator {
				for {
					if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) {
						return nil
					}
					first = false
					if c.Predicate(node) {
						return node
					}
				}
			}
		}

		if node := c.iterator(); node != nil {
			c.posit++
			return node
		}
		c.iterator = nil
	}
}

func (c *childQuery) Evaluate(t iterator) interface{} {
	c.Input.Evaluate(t)
	c.iterator = nil
	return c
}

func (c *childQuery) Test(n NodeNavigator) bool {
	return c.Predicate(n)
}

func (c *childQuery) Clone() query {
	return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
}

func (c *childQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (c *childQuery) Properties() queryProp {
	return queryProps.Merge
}

// position returns a position of current NodeNavigator.
func (c *childQuery) position() int {
	return c.posit
}

type cachedChildQuery struct {
	name     string
	posit    int
	iterator func() NodeNavigator

	Input     query
	Predicate func(NodeNavigator) bool
}

func (c *cachedChildQuery) Select(t iterator) NodeNavigator {
	for {
		if c.iterator == nil {
			c.posit = 0
			node := c.Input.Select(t)
			if node == nil {
				return nil
			}
			node = node.Copy()
			first := true
			c.iterator = func() NodeNavigator {
				for {
					if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) {
						return nil
					}
					first = false
					if c.Predicate(node) {
						return node
					}
				}
			}
		}

		if node := c.iterator(); node != nil {
			c.posit++
			return node
		}
		c.iterator = nil
	}
}

func (c *cachedChildQuery) Evaluate(t iterator) interface{} {
	c.Input.Evaluate(t)
	c.iterator = nil
	return c
}

func (c *cachedChildQuery) position() int {
	return c.posit
}

func (c *cachedChildQuery) Test(n NodeNavigator) bool {
	return c.Predicate(n)
}

func (c *cachedChildQuery) Clone() query {
	return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
}

func (c *cachedChildQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (c *cachedChildQuery) Properties() queryProp {
	return queryProps.Merge
}

// descendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*)
type descendantQuery struct {
	name     string
	iterator func() NodeNavigator
	posit    int
	level    int

	Self      bool
	Input     query
	Predicate func(NodeNavigator) bool
}

func (d *descendantQuery) Select(t iterator) NodeNavigator {
	for {
		if d.iterator == nil {
			d.posit = 0
			node := d.Input.Select(t)
			if node == nil {
				return nil
			}
			node = node.Copy()
			d.level = 0
			first := true
			d.iterator = func() NodeNavigator {
				if first {
					first = false
					if d.Self && d.Predicate(node) {
						return node
					}
				}

				for {
					if node.MoveToChild() {
						d.level = d.level + 1
					} else {
						for {
							if d.level == 0 {
								return nil
							}
							if node.MoveToNext() {
								break
							}
							node.MoveToParent()
							d.level = d.level - 1
						}
					}
					if d.Predicate(node) {
						return node
					}
				}
			}
		}

		if node := d.iterator(); node != nil {
			d.posit++
			return node
		}
		d.iterator = nil
	}
}

func (d *descendantQuery) Evaluate(t iterator) interface{} {
	d.Input.Evaluate(t)
	d.iterator = nil
	return d
}

func (d *descendantQuery) Test(n NodeNavigator) bool {
	return d.Predicate(n)
}

// position returns a position of current NodeNavigator.
func (d *descendantQuery) position() int {
	return d.posit
}

func (d *descendantQuery) depth() int {
	return d.level
}

func (d *descendantQuery) Clone() query {
	return &descendantQuery{name: d.name, Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate}
}

func (d *descendantQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (d *descendantQuery) Properties() queryProp {
	return queryProps.Merge
}

// followingQuery is an XPath following node query.(following::*|following-sibling::*)
type followingQuery struct {
	posit    int
	iterator func() NodeNavigator

	Input     query
	Sibling   bool // The matching sibling node of current node.
	Predicate func(NodeNavigator) bool
}

func (f *followingQuery) Select(t iterator) NodeNavigator {
	for {
		if f.iterator == nil {
			f.posit = 0
			node := f.Input.Select(t)
			if node == nil {
				return nil
			}
			node = node.Copy()
			if f.Sibling {
				f.iterator = func() NodeNavigator {
					for {
						if !node.MoveToNext() {
							return nil
						}
						if f.Predicate(node) {
							f.posit++
							return node
						}
					}
				}
			} else {
				var q *descendantQuery // descendant query
				f.iterator = func() NodeNavigator {
					for {
						if q == nil {
							for !node.MoveToNext() {
								if !node.MoveToParent() {
									return nil
								}
							}
							q = &descendantQuery{
								Self:      true,
								Input:     &contextQuery{},
								Predicate: f.Predicate,
							}
							t.Current().MoveTo(node)
						}
						if node := q.Select(t); node != nil {
							f.posit = q.posit
							return node
						}
						q = nil
					}
				}
			}
		}

		if node := f.iterator(); node != nil {
			return node
		}
		f.iterator = nil
	}
}

func (f *followingQuery) Evaluate(t iterator) interface{} {
	f.Input.Evaluate(t)
	return f
}

func (f *followingQuery) Test(n NodeNavigator) bool {
	return f.Predicate(n)
}

func (f *followingQuery) Clone() query {
	return &followingQuery{Input: f.Input.Clone(), Sibling: f.Sibling, Predicate: f.Predicate}
}

func (f *followingQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (f *followingQuery) Properties() queryProp {
	return queryProps.Merge
}

func (f *followingQuery) position() int {
	return f.posit
}

// precedingQuery is an XPath preceding node query.(preceding::*)
type precedingQuery struct {
	iterator  func() NodeNavigator
	posit     int
	Input     query
	Sibling   bool // The matching sibling node of current node.
	Predicate func(NodeNavigator) bool
}

func (p *precedingQuery) Select(t iterator) NodeNavigator {
	for {
		if p.iterator == nil {
			p.posit = 0
			node := p.Input.Select(t)
			if node == nil {
				return nil
			}
			node = node.Copy()
			if p.Sibling {
				p.iterator = func() NodeNavigator {
					for {
						for !node.MoveToPrevious() {
							return nil
						}
						if p.Predicate(node) {
							p.posit++
							return node
						}
					}
				}
			} else {
				var q query
				p.iterator = func() NodeNavigator {
					for {
						if q == nil {
							for !node.MoveToPrevious() {
								if !node.MoveToParent() {
									return nil
								}
								p.posit = 0
							}
							q = &descendantQuery{
								Self:      true,
								Input:     &contextQuery{},
								Predicate: p.Predicate,
							}
							t.Current().MoveTo(node)
						}
						if node := q.Select(t); node != nil {
							p.posit++
							return node
						}
						q = nil
					}
				}
			}
		}
		if node := p.iterator(); node != nil {
			return node
		}
		p.iterator = nil
	}
}

func (p *precedingQuery) Evaluate(t iterator) interface{} {
	p.Input.Evaluate(t)
	return p
}

func (p *precedingQuery) Test(n NodeNavigator) bool {
	return p.Predicate(n)
}

func (p *precedingQuery) Clone() query {
	return &precedingQuery{Input: p.Input.Clone(), Sibling: p.Sibling, Predicate: p.Predicate}
}

func (p *precedingQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (p *precedingQuery) Properties() queryProp {
	return queryProps.Merge | queryProps.Reverse
}

func (p *precedingQuery) position() int {
	return p.posit
}

// parentQuery is an XPath parent node query.(parent::*)
type parentQuery struct {
	Input     query
	Predicate func(NodeNavigator) bool
}

func (p *parentQuery) Select(t iterator) NodeNavigator {
	for {
		node := p.Input.Select(t)
		if node == nil {
			return nil
		}
		node = node.Copy()
		if node.MoveToParent() && p.Predicate(node) {
			return node
		}
	}
}

func (p *parentQuery) Evaluate(t iterator) interface{} {
	p.Input.Evaluate(t)
	return p
}

func (p *parentQuery) Clone() query {
	return &parentQuery{Input: p.Input.Clone(), Predicate: p.Predicate}
}

func (p *parentQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (p *parentQuery) Properties() queryProp {
	return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
}

func (p *parentQuery) Test(n NodeNavigator) bool {
	return p.Predicate(n)
}

// selfQuery is an Self node query.(self::*)
type selfQuery struct {
	Input     query
	Predicate func(NodeNavigator) bool
}

func (s *selfQuery) Select(t iterator) NodeNavigator {
	for {
		node := s.Input.Select(t)
		if node == nil {
			return nil
		}

		if s.Predicate(node) {
			return node
		}
	}
}

func (s *selfQuery) Evaluate(t iterator) interface{} {
	s.Input.Evaluate(t)
	return s
}

func (s *selfQuery) Test(n NodeNavigator) bool {
	return s.Predicate(n)
}

func (s *selfQuery) Clone() query {
	return &selfQuery{Input: s.Input.Clone(), Predicate: s.Predicate}
}

func (s *selfQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (s *selfQuery) Properties() queryProp {
	return queryProps.Merge
}

// filterQuery is an XPath query for predicate filter.
type filterQuery struct {
	Input      query
	Predicate  query
	NoPosition bool

	posit    int
	positmap map[int]int
}

func (f *filterQuery) do(t iterator) bool {
	val := reflect.ValueOf(f.Predicate.Evaluate(t))
	switch val.Kind() {
	case reflect.Bool:
		return val.Bool()
	case reflect.String:
		return len(val.String()) > 0
	case reflect.Float64:
		pt := getNodePosition(f.Input)
		return int(val.Float()) == pt
	default:
		if f.Predicate != nil {
			return f.Predicate.Select(t) != nil
		}
	}
	return false
}

func (f *filterQuery) position() int {
	return f.posit
}

func (f *filterQuery) Select(t iterator) NodeNavigator {
	if f.positmap == nil {
		f.positmap = make(map[int]int)
	}
	for {

		node := f.Input.Select(t)
		if node == nil {
			return nil
		}
		node = node.Copy()

		t.Current().MoveTo(node)
		if f.do(t) {
			// fix https://github.com/antchfx/htmlquery/issues/26
			// Calculate and keep the each of matching node's position in the same depth.
			level := getNodeDepth(f.Input)
			f.positmap[level]++
			f.posit = f.positmap[level]
			return node
		}
	}
}

func (f *filterQuery) Evaluate(t iterator) interface{} {
	f.Input.Evaluate(t)
	return f
}

func (f *filterQuery) Clone() query {
	return &filterQuery{Input: f.Input.Clone(), Predicate: f.Predicate.Clone()}
}

func (f *filterQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (f *filterQuery) Properties() queryProp {
	return (queryProps.Position | f.Input.Properties()) & (queryProps.Reverse | queryProps.Merge)
}

// functionQuery is an XPath function that returns a computed value for
// the Evaluate call of the current NodeNavigator node. Select call isn't
// applicable for functionQuery.
type functionQuery struct {
	Input query                             // Node Set
	Func  func(query, iterator) interface{} // The xpath function.
}

func (f *functionQuery) Select(t iterator) NodeNavigator {
	return nil
}

// Evaluate call a specified function that will returns the
// following value type: number,string,boolean.
func (f *functionQuery) Evaluate(t iterator) interface{} {
	return f.Func(f.Input, t)
}

func (f *functionQuery) Clone() query {
	if f.Input == nil {
		return &functionQuery{Func: f.Func}
	}
	return &functionQuery{Input: f.Input.Clone(), Func: f.Func}
}

func (f *functionQuery) ValueType() resultType {
	return xpathResultType.Any
}

func (f *functionQuery) Properties() queryProp {
	return queryProps.Merge
}

// transformFunctionQuery diffs from functionQuery where the latter computes a scalar
// value (number,string,boolean) for the current NodeNavigator node while the former
// (transformFunctionQuery) performs a mapping or transform of the current NodeNavigator
// and returns a new NodeNavigator. It is used for non-scalar XPath functions such as
// reverse(), remove(), subsequence(), unordered(), etc.
type transformFunctionQuery struct {
	Input    query
	Func     func(query, iterator) func() NodeNavigator
	iterator func() NodeNavigator
}

func (f *transformFunctionQuery) Select(t iterator) NodeNavigator {
	if f.iterator == nil {
		f.iterator = f.Func(f.Input, t)
	}
	return f.iterator()
}

func (f *transformFunctionQuery) Evaluate(t iterator) interface{} {
	f.Input.Evaluate(t)
	f.iterator = nil
	return f
}

func (f *transformFunctionQuery) Clone() query {
	return &transformFunctionQuery{Input: f.Input.Clone(), Func: f.Func}
}

func (f *transformFunctionQuery) ValueType() resultType {
	return xpathResultType.Any
}

func (f *transformFunctionQuery) Properties() queryProp {
	return queryProps.Merge
}

// constantQuery is an XPath constant operand.
type constantQuery struct {
	Val interface{}
}

func (c *constantQuery) Select(t iterator) NodeNavigator {
	return nil
}

func (c *constantQuery) Evaluate(t iterator) interface{} {
	return c.Val
}

func (c *constantQuery) Clone() query {
	return c
}

func (c *constantQuery) ValueType() resultType {
	return getXPathType(c.Val)
}

func (c *constantQuery) Properties() queryProp {
	return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
}

type groupQuery struct {
	posit int

	Input query
}

func (g *groupQuery) Select(t iterator) NodeNavigator {
	node := g.Input.Select(t)
	if node == nil {
		return nil
	}
	g.posit++
	return node
}

func (g *groupQuery) Evaluate(t iterator) interface{} {
	return g.Input.Evaluate(t)
}

func (g *groupQuery) Clone() query {
	return &groupQuery{Input: g.Input.Clone()}
}

func (g *groupQuery) ValueType() resultType {
	return g.Input.ValueType()
}

func (g *groupQuery) Properties() queryProp {
	return queryProps.Position
}

func (g *groupQuery) position() int {
	return g.posit
}

// logicalQuery is an XPath logical expression.
type logicalQuery struct {
	Left, Right query

	Do func(iterator, interface{}, interface{}) interface{}
}

func (l *logicalQuery) Select(t iterator) NodeNavigator {
	// When a XPath expr is logical expression.
	node := t.Current().Copy()
	val := l.Evaluate(t)
	switch val.(type) {
	case bool:
		if val.(bool) == true {
			return node
		}
	}
	return nil
}

func (l *logicalQuery) Evaluate(t iterator) interface{} {
	m := l.Left.Evaluate(t)
	n := l.Right.Evaluate(t)
	return l.Do(t, m, n)
}

func (l *logicalQuery) Clone() query {
	return &logicalQuery{Left: l.Left.Clone(), Right: l.Right.Clone(), Do: l.Do}
}

func (l *logicalQuery) ValueType() resultType {
	return xpathResultType.Boolean
}

func (l *logicalQuery) Properties() queryProp {
	return queryProps.Merge
}

// numericQuery is an XPath numeric operator expression.
type numericQuery struct {
	Left, Right query

	Do func(iterator, interface{}, interface{}) interface{}
}

func (n *numericQuery) Select(t iterator) NodeNavigator {
	return nil
}

func (n *numericQuery) Evaluate(t iterator) interface{} {
	m := n.Left.Evaluate(t)
	k := n.Right.Evaluate(t)
	return n.Do(t, m, k)
}

func (n *numericQuery) Clone() query {
	return &numericQuery{Left: n.Left.Clone(), Right: n.Right.Clone(), Do: n.Do}
}

func (n *numericQuery) ValueType() resultType {
	return xpathResultType.Number
}

func (n *numericQuery) Properties() queryProp {
	return queryProps.Merge
}

type booleanQuery struct {
	IsOr        bool
	Left, Right query
	iterator    func() NodeNavigator
}

func (b *booleanQuery) Select(t iterator) NodeNavigator {
	if b.iterator == nil {
		var list []NodeNavigator
		i := 0
		root := t.Current().Copy()
		if b.IsOr {
			for {
				node := b.Left.Select(t)
				if node == nil {
					break
				}
				node = node.Copy()
				list = append(list, node)
			}
			t.Current().MoveTo(root)
			for {
				node := b.Right.Select(t)
				if node == nil {
					break
				}
				node = node.Copy()
				list = append(list, node)
			}
		} else {
			var m []NodeNavigator
			var n []NodeNavigator
			for {
				node := b.Left.Select(t)
				if node == nil {
					break
				}
				node = node.Copy()
				list = append(m, node)
			}
			t.Current().MoveTo(root)
			for {
				node := b.Right.Select(t)
				if node == nil {
					break
				}
				node = node.Copy()
				list = append(n, node)
			}
			for _, k := range m {
				for _, j := range n {
					if k == j {
						list = append(list, k)
					}
				}
			}
		}

		b.iterator = func() NodeNavigator {
			if i >= len(list) {
				return nil
			}
			node := list[i]
			i++
			return node
		}
	}
	return b.iterator()
}

func (b *booleanQuery) Evaluate(t iterator) interface{} {
	n := t.Current().Copy()

	m := b.Left.Evaluate(t)
	left := asBool(t, m)
	if b.IsOr && left {
		return true
	} else if !b.IsOr && !left {
		return false
	}

	t.Current().MoveTo(n)
	m = b.Right.Evaluate(t)
	return asBool(t, m)
}

func (b *booleanQuery) Clone() query {
	return &booleanQuery{IsOr: b.IsOr, Left: b.Left.Clone(), Right: b.Right.Clone()}
}

func (b *booleanQuery) ValueType() resultType {
	return xpathResultType.Boolean
}

func (b *booleanQuery) Properties() queryProp {
	return queryProps.Merge
}

type unionQuery struct {
	Left, Right query
	iterator    func() NodeNavigator
}

func (u *unionQuery) Select(t iterator) NodeNavigator {
	if u.iterator == nil {
		var list []NodeNavigator
		var m = make(map[uint64]bool)
		root := t.Current().Copy()
		for {
			node := u.Left.Select(t)
			if node == nil {
				break
			}
			code := getHashCode(node.Copy())
			if _, ok := m[code]; !ok {
				m[code] = true
				list = append(list, node.Copy())
			}
		}
		t.Current().MoveTo(root)
		for {
			node := u.Right.Select(t)
			if node == nil {
				break
			}
			code := getHashCode(node.Copy())
			if _, ok := m[code]; !ok {
				m[code] = true
				list = append(list, node.Copy())
			}
		}
		var i int
		u.iterator = func() NodeNavigator {
			if i >= len(list) {
				return nil
			}
			node := list[i]
			i++
			return node
		}
	}
	return u.iterator()
}

func (u *unionQuery) Evaluate(t iterator) interface{} {
	u.iterator = nil
	u.Left.Evaluate(t)
	u.Right.Evaluate(t)
	return u
}

func (u *unionQuery) Clone() query {
	return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()}
}

func (u *unionQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (u *unionQuery) Properties() queryProp {
	return queryProps.Merge
}

type lastFuncQuery struct {
	buffer  []NodeNavigator
	counted bool

	Input query
}

func (q *lastFuncQuery) Select(t iterator) NodeNavigator {
	return nil
}

func (q *lastFuncQuery) Evaluate(t iterator) interface{} {
	if !q.counted {
		for {
			node := q.Input.Select(t)
			if node == nil {
				break
			}
			q.buffer = append(q.buffer, node.Copy())
		}
		q.counted = true
	}
	return float64(len(q.buffer))
}

func (q *lastFuncQuery) Clone() query {
	return &lastFuncQuery{Input: q.Input.Clone()}
}

func (q *lastFuncQuery) ValueType() resultType {
	return xpathResultType.Number
}

func (q *lastFuncQuery) Properties() queryProp {
	return queryProps.Merge
}

type descendantOverDescendantQuery struct {
	name        string
	level       int
	posit       int
	currentNode NodeNavigator

	Input     query
	MatchSelf bool
	Predicate func(NodeNavigator) bool
}

func (d *descendantOverDescendantQuery) moveToFirstChild() bool {
	if d.currentNode.MoveToChild() {
		d.level++
		return true
	}
	return false
}

func (d *descendantOverDescendantQuery) moveUpUntilNext() bool {
	for !d.currentNode.MoveToNext() {
		d.level--
		if d.level == 0 {
			return false
		}
		d.currentNode.MoveToParent()
	}
	return true
}

func (d *descendantOverDescendantQuery) Select(t iterator) NodeNavigator {
	for {
		if d.level == 0 {
			node := d.Input.Select(t)
			if node == nil {
				return nil
			}
			d.currentNode = node.Copy()
			d.posit = 0
			if d.MatchSelf && d.Predicate(d.currentNode) {
				d.posit = 1
				return d.currentNode
			}
			d.moveToFirstChild()
		} else if !d.moveUpUntilNext() {
			continue
		}
		for ok := true; ok; ok = d.moveToFirstChild() {
			if d.Predicate(d.currentNode) {
				d.posit++
				return d.currentNode
			}
		}
	}
}

func (d *descendantOverDescendantQuery) Evaluate(t iterator) interface{} {
	d.Input.Evaluate(t)
	return d
}

func (d *descendantOverDescendantQuery) Clone() query {
	return &descendantOverDescendantQuery{Input: d.Input.Clone(), Predicate: d.Predicate, MatchSelf: d.MatchSelf}
}

func (d *descendantOverDescendantQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (d *descendantOverDescendantQuery) Properties() queryProp {
	return queryProps.Merge
}

func (d *descendantOverDescendantQuery) position() int {
	return d.posit
}

type mergeQuery struct {
	Input query
	Child query

	iterator func() NodeNavigator
}

func (m *mergeQuery) Select(t iterator) NodeNavigator {
	for {
		if m.iterator == nil {
			root := m.Input.Select(t)
			if root == nil {
				return nil
			}
			m.Child.Evaluate(t)
			root = root.Copy()
			t.Current().MoveTo(root)
			var list []NodeNavigator
			for node := m.Child.Select(t); node != nil; node = m.Child.Select(t) {
				list = append(list, node.Copy())
			}
			i := 0
			m.iterator = func() NodeNavigator {
				if i >= len(list) {
					return nil
				}
				result := list[i]
				i++
				return result
			}
		}

		if node := m.iterator(); node != nil {
			return node
		}
		m.iterator = nil
	}
}

func (m *mergeQuery) Evaluate(t iterator) interface{} {
	m.Input.Evaluate(t)
	return m
}

func (m *mergeQuery) Clone() query {
	return &mergeQuery{Input: m.Input.Clone(), Child: m.Child.Clone()}
}

func (m *mergeQuery) ValueType() resultType {
	return xpathResultType.NodeSet
}

func (m *mergeQuery) Properties() queryProp {
	return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
}

func getHashCode(n NodeNavigator) uint64 {
	var sb bytes.Buffer
	switch n.NodeType() {
	case AttributeNode, TextNode, CommentNode:
		sb.WriteString(fmt.Sprintf("%s=%s", n.LocalName(), n.Value()))
		// https://github.com/antchfx/htmlquery/issues/25
		d := 1
		for n.MoveToPrevious() {
			d++
		}
		sb.WriteString(fmt.Sprintf("-%d", d))
		for n.MoveToParent() {
			d = 1
			for n.MoveToPrevious() {
				d++
			}
			sb.WriteString(fmt.Sprintf("-%d", d))
		}
	case ElementNode:
		sb.WriteString(n.Prefix() + n.LocalName())
		d := 1
		for n.MoveToPrevious() {
			d++
		}
		sb.WriteString(fmt.Sprintf("-%d", d))

		for n.MoveToParent() {
			d = 1
			for n.MoveToPrevious() {
				d++
			}
			sb.WriteString(fmt.Sprintf("-%d", d))
		}
	}
	h := fnv.New64a()
	h.Write(sb.Bytes())
	return h.Sum64()
}

func getNodePosition(q query) int {
	type Position interface {
		position() int
	}
	if count, ok := q.(Position); ok {
		return count.position()
	}
	return 1
}

func getNodeDepth(q query) int {
	type Depth interface {
		depth() int
	}
	if count, ok := q.(Depth); ok {
		return count.depth()
	}
	return 0
}

func getXPathType(i interface{}) resultType {
	v := reflect.ValueOf(i)
	switch v.Kind() {
	case reflect.Float64:
		return xpathResultType.Number
	case reflect.String:
		return xpathResultType.String
	case reflect.Bool:
		return xpathResultType.Boolean
	default:
		if _, ok := i.(query); ok {
			return xpathResultType.NodeSet
		}
	}
	panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
}
