// Copyright (c) 2016 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package pathmap

import (
	"bytes"
	"fmt"
	"sort"
)

// PathMap associates Paths to a values. It allows wildcards. The
// primary use of PathMap is to be able to register handlers to paths
// that can be efficiently looked up every time a path is updated.
//
// For example:
//
// m.Set({"interfaces", "*", "adminStatus"}, AdminStatusHandler)
// m.Set({"interface", "Management1", "adminStatus"}, Management1AdminStatusHandler)
//
// m.Visit({"interfaces", "Ethernet3/32/1", "adminStatus"}, HandlerExecutor)
// >> AdminStatusHandler gets passed to HandlerExecutor
// m.Visit({"interfaces", "Management1", "adminStatus"}, HandlerExecutor)
// >> AdminStatusHandler and Management1AdminStatusHandler gets passed to HandlerExecutor
//
// Note, Visit performance is typically linearly with the length of
// the path. But, it can be as bad as O(2^len(Path)) when TreeMap
// nodes have children and a wildcard associated with it. For example,
// if these paths were registered:
//
// m.Set([]string{"foo", "bar", "baz"}, 1)
// m.Set([]string{"*", "bar", "baz"}, 2)
// m.Set([]string{"*", "*", "baz"}, 3)
// m.Set([]string{"*", "*", "*"}, 4)
// m.Set([]string{"foo", "*", "*"}, 5)
// m.Set([]string{"foo", "bar", "*"}, 6)
// m.Set([]string{"foo", "*", "baz"}, 7)
// m.Set([]string{"*", "bar", "*"}, 8)
//
// m.Visit([]{"foo","bar","baz"}, Foo) // 2^3 nodes traversed
//
// This shouldn't be a concern with our paths because it is likely
// that a TreeMap node will either have a wildcard or children, not
// both. A TreeMap node that corresponds to a collection will often be a
// wildcard, otherwise it will have specific children.
type PathMap interface {
	// Visit calls f for every registration in the PathMap that
	// matches path. For example,
	//
	// m.Set({"foo", "bar"}, 1)
	// m.Set({"*", "bar"}, 2)
	//
	// m.Visit({"foo", "bar"}, Printer)
	// >> Calls Printer(1) and Printer(2)
	Visit(path []string, f VisitorFunc) error

	// VisitPrefix calls f for every registration in the PathMap that
	// is a prefix of path. For example,
	//
	// m.Set({}, 0)
	// m.Set({"foo"}, 1)
	// m.Set({"foo", "bar"}, 2)
	// m.Set({"foo", "quux"}, 3)
	// m.Set({"*", "bar"}, 4)
	//
	// m.VisitPrefix({"foo", "bar", "baz"}, Printer)
	// >> Calls Printer on values 0, 1, 2, and 4
	VisitPrefix(path []string, f VisitorFunc) error

	// Get returns the mapping for path. This returns the exact
	// mapping for path. For example, if you register two paths
	//
	// m.Set({"foo", "bar"}, 1)
	// m.Set({"*", "bar"}, 2)
	//
	// m.Get({"foo", "bar"}) => 1
	// m.Get({"*", "bar"}) => 2
	Get(path []string) interface{}

	// Set a mapping of path to value. Path may contain wildcards. Set
	// replaces what was there before.
	Set(path []string, v interface{})

	// Delete removes the mapping for path
	Delete(path []string) bool
}

// Wildcard is a special string representing any possible path
const Wildcard string = "*"

type node struct {
	val      interface{}
	wildcard *node
	children map[string]*node
}

// New creates a new PathMap
func New() PathMap {
	return &node{}
}

// VisitorFunc is the func type passed to Visit
type VisitorFunc func(v interface{}) error

// Visit calls f for every matching registration in the PathMap
func (n *node) Visit(path []string, f VisitorFunc) error {
	for i, element := range path {
		if n.wildcard != nil {
			if err := n.wildcard.Visit(path[i+1:], f); err != nil {
				return err
			}
		}
		next, ok := n.children[element]
		if !ok {
			return nil
		}
		n = next
	}
	if n.val == nil {
		return nil
	}
	return f(n.val)
}

// VisitPrefix calls f for every registered path that is a prefix of
// the path
func (n *node) VisitPrefix(path []string, f VisitorFunc) error {
	for i, element := range path {
		// Call f on each node we visit
		if n.val != nil {
			if err := f(n.val); err != nil {
				return err
			}
		}
		if n.wildcard != nil {
			if err := n.wildcard.VisitPrefix(path[i+1:], f); err != nil {
				return err
			}
		}
		next, ok := n.children[element]
		if !ok {
			return nil
		}
		n = next
	}
	if n.val == nil {
		return nil
	}
	// Call f on the final node
	return f(n.val)
}

// Get returns the mapping for path
func (n *node) Get(path []string) interface{} {
	for _, element := range path {
		if element == Wildcard {
			if n.wildcard == nil {
				return nil
			}
			n = n.wildcard
			continue
		}
		next, ok := n.children[element]
		if !ok {
			return nil
		}
		n = next
	}
	return n.val
}

// Set a mapping of path to value. Path may contain wildcards. Set
// replaces what was there before.
func (n *node) Set(path []string, v interface{}) {
	for _, element := range path {
		if element == Wildcard {
			if n.wildcard == nil {
				n.wildcard = &node{}
			}
			n = n.wildcard
			continue
		}
		if n.children == nil {
			n.children = map[string]*node{}
		}
		next, ok := n.children[element]
		if !ok {
			next = &node{}
			n.children[element] = next
		}
		n = next
	}
	n.val = v
}

// Delete removes the mapping for path
func (n *node) Delete(path []string) bool {
	nodes := make([]*node, len(path)+1)
	for i, element := range path {
		nodes[i] = n
		if element == Wildcard {
			if n.wildcard == nil {
				return false
			}
			n = n.wildcard
			continue
		}
		next, ok := n.children[element]
		if !ok {
			return false
		}
		n = next
	}
	n.val = nil
	nodes[len(path)] = n

	// See if we can delete any node objects
	for i := len(path); i > 0; i-- {
		n = nodes[i]
		if n.val != nil || n.wildcard != nil || len(n.children) > 0 {
			break
		}
		parent := nodes[i-1]
		element := path[i-1]
		if element == Wildcard {
			parent.wildcard = nil
		} else {
			delete(parent.children, element)
		}

	}
	return true
}

func (n *node) String() string {
	var b bytes.Buffer
	n.write(&b, "")
	return b.String()
}

func (n *node) write(b *bytes.Buffer, indent string) {
	if n.val != nil {
		b.WriteString(indent)
		fmt.Fprintf(b, "Val: %v", n.val)
		b.WriteString("\n")
	}
	if n.wildcard != nil {
		b.WriteString(indent)
		fmt.Fprintf(b, "Child %q:\n", Wildcard)
		n.wildcard.write(b, indent+"  ")
	}
	children := make([]string, 0, len(n.children))
	for name := range n.children {
		children = append(children, name)
	}
	sort.Strings(children)

	for _, name := range children {
		child := n.children[name]
		b.WriteString(indent)
		fmt.Fprintf(b, "Child %q:\n", name)
		child.write(b, indent+"  ")
	}
}
