package templating

import (
	"sort"
	"strings"
)

// node is an item in a sorted k-ary tree of filter parts. Each child is sorted by its part value.
// The special value of "*", is always sorted last.
type node struct {
	separator string
	value     string
	children  nodes
	template  *Template
}

// insert inserts the given string template into the tree.  The filter string is separated
// on the template separator and each part is used as the path in the tree.
func (n *node) insert(filter string, template *Template) {
	n.separator = template.separator
	n.recursiveInsert(strings.Split(filter, n.separator), template)
}

// recursiveInsert does the actual recursive insertion
func (n *node) recursiveInsert(values []string, template *Template) {
	// Add the end, set the template
	if len(values) == 0 {
		n.template = template
		return
	}

	// See if the the current element already exists in the tree. If so, insert the
	// into that sub-tree
	for _, v := range n.children {
		if v.value == values[0] {
			v.recursiveInsert(values[1:], template)
			return
		}
	}

	// New element, add it to the tree and sort the children
	newNode := &node{value: values[0]}
	n.children = append(n.children, newNode)
	sort.Sort(&n.children)

	// Now insert the rest of the tree into the new element
	newNode.recursiveInsert(values[1:], template)
}

// search searches for a template matching the input string
func (n *node) search(line string) *Template {
	separator := n.separator
	return n.recursiveSearch(strings.Split(line, separator))
}

// recursiveSearch performs the actual recursive search
func (n *node) recursiveSearch(lineParts []string) *Template {
	// nothing to search
	if len(lineParts) == 0 || len(n.children) == 0 {
		return n.template
	}

	var (
		hasWildcard bool
		length      = len(n.children)
	)

	// exclude last child from search if it is a wildcard. sort.Search expects
	// a lexicographically sorted set of children and we have artificially sorted
	// wildcards to the end of the child set
	// wildcards will be searched seperately if no exact match is found
	if hasWildcard = n.children[length-1].value == "*"; hasWildcard {
		length--
	}

	i := sort.Search(length, func(i int) bool {
		return n.children[i].value >= lineParts[0]
	})

	// given an exact match is found within children set
	if i < length && n.children[i].value == lineParts[0] {
		// decend into the matching node
		if tmpl := n.children[i].recursiveSearch(lineParts[1:]); tmpl != nil {
			// given a template is found return it
			return tmpl
		}
	}

	// given no template is found and the last child is a wildcard
	if hasWildcard {
		// also search the wildcard child node
		return n.children[length].recursiveSearch(lineParts[1:])
	}

	// fallback to returning template at this node
	return n.template
}

// nodes is simply an array of nodes implementing the sorting interface.
type nodes []*node

// Less returns a boolean indicating whether the filter at position j
// is less than the filter at position k. Filters are order by string
// comparison of each component parts.  A wildcard value "*" is never
// less than a non-wildcard value.
//
// For example, the filters:
//             "*.*"
//             "servers.*"
//             "servers.localhost"
//             "*.localhost"
//
// Would be sorted as:
//             "servers.localhost"
//             "servers.*"
//             "*.localhost"
//             "*.*"
func (n *nodes) Less(j, k int) bool {
	if (*n)[j].value == "*" && (*n)[k].value != "*" {
		return false
	}

	if (*n)[j].value != "*" && (*n)[k].value == "*" {
		return true
	}

	return (*n)[j].value < (*n)[k].value
}

// Swap swaps two elements of the array
func (n *nodes) Swap(i, j int) { (*n)[i], (*n)[j] = (*n)[j], (*n)[i] }

// Len returns the length of the array
func (n *nodes) Len() int { return len(*n) }
