// Copyright (c) 2017 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 path

import (
	"bytes"
	"fmt"
	"sort"

	"github.com/aristanetworks/goarista/key"
)

// Map associates paths to values. It allows wildcards. A Map
// is primarily used to register handlers with paths that can
// be easily looked up each time a path is updated.
type Map struct {
	val      interface{}
	ok       bool
	wildcard *Map
	children map[key.Key]*Map
}

// VisitorFunc is a function that handles the value associated
// with a path in a Map. Note that only the value is passed in
// as an argument since the path can be stored inside the value
// if needed.
type VisitorFunc func(v interface{}) error

// Visit calls a function fn for every value in the Map
// that is registered with a match of a path p. In the
// general case, time complexity is linear with respect
// to the length of p but it can be as bad as O(2^len(p))
// if there are a lot of paths with wildcards registered.
//
// Example:
//
// a := path.New("foo", "bar", "baz")
// b := path.New("foo", path.Wildcard, "baz")
// c := path.New(path.Wildcard, "bar", "baz")
// d := path.New("foo", "bar", path.Wildcard)
// e := path.New(path.Wildcard, path.Wildcard, "baz")
// f := path.New(path.Wildcard, "bar", path.Wildcard)
// g := path.New("foo", path.Wildcard, path.Wildcard)
// h := path.New(path.Wildcard, path.Wildcard, path.Wildcard)
//
// m.Set(a, 1)
// m.Set(b, 2)
// m.Set(c, 3)
// m.Set(d, 4)
// m.Set(e, 5)
// m.Set(f, 6)
// m.Set(g, 7)
// m.Set(h, 8)
//
// p := path.New("foo", "bar", "baz")
//
// m.Visit(p, fn)
//
// Result: fn(1), fn(2), fn(3), fn(4), fn(5), fn(6), fn(7) and fn(8)
func (m *Map) Visit(p key.Path, fn VisitorFunc) error {
	for i, element := range p {
		if m.wildcard != nil {
			if err := m.wildcard.Visit(p[i+1:], fn); err != nil {
				return err
			}
		}
		next, ok := m.children[element]
		if !ok {
			return nil
		}
		m = next
	}
	if !m.ok {
		return nil
	}
	return fn(m.val)
}

// VisitPrefixes calls a function fn for every value in the
// Map that is registered with a prefix of a path p.
//
// Example:
//
// a := path.New()
// b := path.New("foo")
// c := path.New("foo", "bar")
// d := path.New("foo", "baz")
// e := path.New(path.Wildcard, "bar")
//
// m.Set(a, 1)
// m.Set(b, 2)
// m.Set(c, 3)
// m.Set(d, 4)
// m.Set(e, 5)
//
// p := path.New("foo", "bar", "baz")
//
// m.VisitPrefixes(p, fn)
//
// Result: fn(1), fn(2), fn(3), fn(5)
func (m *Map) VisitPrefixes(p key.Path, fn VisitorFunc) error {
	for i, element := range p {
		if m.ok {
			if err := fn(m.val); err != nil {
				return err
			}
		}
		if m.wildcard != nil {
			if err := m.wildcard.VisitPrefixes(p[i+1:], fn); err != nil {
				return err
			}
		}
		next, ok := m.children[element]
		if !ok {
			return nil
		}
		m = next
	}
	if !m.ok {
		return nil
	}
	return fn(m.val)
}

// VisitPrefixed calls fn for every value in the map that is
// registerd with a path that is prefixed by p. This method
// can be used to visit every registered path if p is the
// empty path (or root path) which prefixes all paths.
//
// Example:
//
// a := path.New("foo")
// b := path.New("foo", "bar")
// c := path.New("foo", "bar", "baz")
// d := path.New("foo", path.Wildcard)
//
// m.Set(a, 1)
// m.Set(b, 2)
// m.Set(c, 3)
// m.Set(d, 4)
//
// p := path.New("foo", "bar")
//
// m.VisitPrefixed(p, fn)
//
// Result: fn(2), fn(3), fn(4)
func (m *Map) VisitPrefixed(p key.Path, fn VisitorFunc) error {
	for i, element := range p {
		if m.wildcard != nil {
			if err := m.wildcard.VisitPrefixed(p[i+1:], fn); err != nil {
				return err
			}
		}
		next, ok := m.children[element]
		if !ok {
			return nil
		}
		m = next
	}
	return m.visitSubtree(fn)
}

func (m *Map) visitSubtree(fn VisitorFunc) error {
	if m.ok {
		if err := fn(m.val); err != nil {
			return err
		}
	}
	if m.wildcard != nil {
		if err := m.wildcard.visitSubtree(fn); err != nil {
			return err
		}
	}
	for _, next := range m.children {
		if err := next.visitSubtree(fn); err != nil {
			return err
		}
	}
	return nil
}

// Get returns the value registered with an exact match of a
// path p. If there is no exact match for p, Get returns nil
// and false. If p has an exact match and it is set to true,
// Get returns nil and true.
//
// Example:
//
// m.Set(path.New("foo", "bar"), 1)
// m.Set(path.New("baz", "qux"), nil)
//
// a := m.Get(path.New("foo", "bar"))
// b := m.Get(path.New("foo", path.Wildcard))
// c, ok := m.Get(path.New("baz", "qux"))
//
// Result: a == 1, b == nil, c == nil and ok == true
func (m *Map) Get(p key.Path) (interface{}, bool) {
	for _, element := range p {
		if element.Equal(Wildcard) {
			if m.wildcard == nil {
				return nil, false
			}
			m = m.wildcard
			continue
		}
		next, ok := m.children[element]
		if !ok {
			return nil, false
		}
		m = next
	}
	return m.val, m.ok
}

// Set registers a path p with a value. If the path was already
// registered with a value it returns true and false otherwise.
//
// Example:
//
// p := path.New("foo", "bar")
//
// a := m.Set(p, 0)
// b := m.Set(p, 1)
//
// v := m.Get(p)
//
// Result: a == false, b == true and v == 1
func (m *Map) Set(p key.Path, v interface{}) bool {
	for _, element := range p {
		if element.Equal(Wildcard) {
			if m.wildcard == nil {
				m.wildcard = &Map{}
			}
			m = m.wildcard
			continue
		}
		if m.children == nil {
			m.children = map[key.Key]*Map{}
		}
		next, ok := m.children[element]
		if !ok {
			next = &Map{}
			m.children[element] = next
		}
		m = next
	}
	set := !m.ok
	m.val, m.ok = v, true
	return set
}

// Delete unregisters the value registered with a path. It
// returns true if a value was deleted and false otherwise.
//
// Example:
//
// p := path.New("foo", "bar")
//
// m.Set(p, 0)
//
// a := m.Delete(p)
// b := m.Delete(p)
//
// Result: a == true and b == false
func (m *Map) Delete(p key.Path) bool {
	maps := make([]*Map, len(p)+1)
	for i, element := range p {
		maps[i] = m
		if element.Equal(Wildcard) {
			if m.wildcard == nil {
				return false
			}
			m = m.wildcard
			continue
		}
		next, ok := m.children[element]
		if !ok {
			return false
		}
		m = next
	}
	deleted := m.ok
	m.val, m.ok = nil, false
	maps[len(p)] = m

	// Remove any empty maps.
	for i := len(p); i > 0; i-- {
		m = maps[i]
		if m.ok || m.wildcard != nil || len(m.children) > 0 {
			break
		}
		parent := maps[i-1]
		element := p[i-1]
		if element.Equal(Wildcard) {
			parent.wildcard = nil
		} else {
			delete(parent.children, element)
		}
	}
	return deleted
}

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

func (m *Map) write(b *bytes.Buffer, indent string) {
	if m.ok {
		b.WriteString(indent)
		fmt.Fprintf(b, "Val: %v", m.val)
		b.WriteString("\n")
	}
	if m.wildcard != nil {
		b.WriteString(indent)
		fmt.Fprintf(b, "Child %q:\n", Wildcard)
		m.wildcard.write(b, indent+"  ")
	}
	children := make([]key.Key, 0, len(m.children))
	for key := range m.children {
		children = append(children, key)
	}
	sort.Slice(children, func(i, j int) bool {
		return children[i].String() < children[j].String()
	})

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