// 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 contains methods for dealing with key.Paths.
package path

import (
	"strings"

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

// New constructs a path from a variable number of elements.
// Each element may either be a key.Key or a value that can
// be wrapped by a key.Key.
func New(elements ...interface{}) key.Path {
	return appendElements(nil, elements...)
}

// Append appends a variable number of elements to a path.
// Each element may either be a key.Key or a value that can
// be wrapped by a key.Key. Note that calling Append on a
// single path returns that same path, whereas in all other
// cases a new path is returned.
func Append(path key.Path, elements ...interface{}) key.Path {
	return appendElements(path, elements...)
}

// Join joins a variable number of paths together. Each path
// in the joining is treated as a subpath of its predecessor.
// Calling Join with no or only empty paths returns nil.
func Join(paths ...key.Path) key.Path {
	n := 0
	for _, path := range paths {
		n += len(path)
	}
	if n == 0 {
		return nil
	}
	result, i := make(key.Path, n), 0
	for _, path := range paths {
		i += copy(result[i:], path)
	}
	return result
}

// Parent returns all but the last element of the path. If
// the path is empty, Parent returns nil.
func Parent(path key.Path) key.Path {
	if len(path) > 0 {
		return path[:len(path)-1]
	}
	return nil
}

// Base returns the last element of the path. If the path is
// empty, Base returns nil.
func Base(path key.Path) key.Key {
	if len(path) > 0 {
		return path[len(path)-1]
	}
	return nil
}

// Clone returns a new path with the same elements as in the
// provided path.
func Clone(path key.Path) key.Path {
	result := make(key.Path, len(path))
	copy(result, path)
	return result
}

// Equal returns whether path a and path b are the same
// length and whether each element in b corresponds to the
// same element in a.
func Equal(a, b key.Path) bool {
	return len(a) == len(b) && hasPrefix(a, b)
}

// HasElement returns whether element b exists in path a.
func HasElement(a key.Path, b key.Key) bool {
	for _, element := range a {
		if element.Equal(b) {
			return true
		}
	}
	return false
}

// HasPrefix returns whether path b is a prefix of path a.
// It checks that b is at most the length of path a and
// whether each element in b corresponds to the same element
// in a from the first element.
func HasPrefix(a, b key.Path) bool {
	return len(a) >= len(b) && hasPrefix(a, b)
}

// Match returns whether path a and path b are the same
// length and whether each element in b corresponds to the
// same element or a wildcard in a.
func Match(a, b key.Path) bool {
	return len(a) == len(b) && matchPrefix(a, b)
}

// MatchPrefix returns whether path b is a prefix of path a
// where path a may contain wildcards.
// It checks that b is at most the length of path a and
// whether each element in b corresponds to the same element
// or a wildcard in a from the first element.
func MatchPrefix(a, b key.Path) bool {
	return len(a) >= len(b) && matchPrefix(a, b)
}

// FromString constructs a path from the elements resulting
// from a split of the input string by "/". Strings that do
// not lead with a '/' are accepted but not reconstructable
// with key.Path.String. Both "" and "/" are treated as a
// key.Path{}.
func FromString(str string) key.Path {
	if str == "" || str == "/" {
		return key.Path{}
	} else if str[0] == '/' {
		str = str[1:]
	}
	elements := strings.Split(str, "/")
	result := make(key.Path, len(elements))
	for i, element := range elements {
		result[i] = key.New(element)
	}
	return result
}

// appendElements makes a copy of dest when elements is non-empty and
// then appends elements to the copy and returns it.
func appendElements(dest key.Path, elements ...interface{}) key.Path {
	if len(elements) == 0 {
		return dest
	}
	clone := make(key.Path, len(dest), len(dest)+len(elements))
	copy(clone, dest)
	dest = clone
	for _, element := range elements {
		switch val := element.(type) {
		case key.Key:
			dest = append(dest, val)
		case []key.Key:
			dest = append(dest, val...)
		case key.Path:
			dest = append(dest, val...)
		case []string:
			for i := range val {
				dest = append(dest, key.New(val[i]))
			}
		case []key.Path:
			for i := range val {
				dest = append(dest, val[i]...)
			}
		default:
			dest = append(dest, key.New(val))
		}
	}
	return dest
}

func hasPrefix(a, b key.Path) bool {
	for i := range b {
		if !b[i].Equal(a[i]) {
			return false
		}
	}
	return true
}

func matchPrefix(a, b key.Path) bool {
	for i := range b {
		if !a[i].Equal(Wildcard) && !b[i].Equal(a[i]) {
			return false
		}
	}
	return true
}
