// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package preprocessor

import (
	"strings"

	"github.com/asteris-llc/converge/parse"

	"github.com/asteris-llc/converge/graph"
	"github.com/asteris-llc/converge/resource/module"
)

// VertexSplit takes a graph with a set of vertexes and a string, and returns
// the longest vertex id from the graph and the remainder of the string.  If no
// matching vertex is found 'false' is returned.
func VertexSplit(g *graph.Graph, s string) (string, string, bool) {
	prefix, found := Find(Prefixes(s), g.Contains)
	if !found {
		return "", s, false
	}
	if prefix == s {
		return prefix, "", true
	}
	return prefix, s[len(prefix)+1:], true
}

// VertexSplitTraverse will act like vertex split, looking for a prefix matching
// the current set of graph nodes, however unlike `VertexSplit`, if a node is
// not found at the current level it will look at the parent level to the
// provided starting node, unless stop(parent) returns true.
func VertexSplitTraverse(g *graph.Graph, toFind string, startingNode string, stop func(*graph.Graph, string) bool, history map[string]struct{}) (string, string, bool) {
	history[startingNode] = struct{}{}

	for _, child := range g.Children(startingNode) {
		if _, ok := history[child]; ok {
			continue
		}
		if stop(g, child) {
			continue
		}
		vertex, middle, found := VertexSplitTraverse(g, toFind, child, stop, history)
		if found {
			return vertex, middle, found
		}
	}
	if stop(g, startingNode) {
		return "", toFind, false
	}

	fqgn := graph.SiblingID(startingNode, toFind)
	vertex, middle, found := VertexSplit(g, fqgn)
	if found {
		return vertex, middle, found
	}
	parentID := graph.ParentID(startingNode)
	return VertexSplitTraverse(g, toFind, parentID, stop, history)
}

// TraverseUntilModule is a function intended to be used with
// VertexSplitTraverse and will cause vertex splitting to propogate upwards
// until it encounters a module
func TraverseUntilModule(g *graph.Graph, id string) bool {
	if graph.IsRoot(id) {
		return true
	}
	elemMeta, ok := g.Get(id)
	if !ok {
		return true
	}
	elem := elemMeta.Value()
	if _, ok := elem.(*module.Module); ok {
		return true
	}
	if _, ok := elem.(*module.Preparer); ok {
		return true
	}
	if node, ok := elem.(*parse.Node); ok {
		return node.Kind() == "module"
	}
	return false
}

// Find returns the first element of the string slice for which f returns true
func Find(slice []string, f func(string) bool) (string, bool) {
	for _, elem := range slice {
		if f(elem) {
			return elem, true
		}
	}
	return "", false
}

// SplitTerms takes a string and splits it on '.'
func SplitTerms(in string) []string {
	return strings.Split(in, ".")
}

// JoinTerms takes a list of terms and joins them with '.'
func JoinTerms(s []string) string {
	return strings.Join(s, ".")
}

// Inits returns a list of heads of the string,
// e.g. [1,2,3] -> [[1,2,3],[1,2],[1]]
func Inits(in []string) [][]string {
	var results [][]string
	for i := 0; i < len(in); i++ {
		results = append([][]string{in[0 : i+1]}, results...)
	}
	return results
}

// Prefixes returns a set of prefixes for a string, e.g. "a.b.c.d" will yield
// []string{"a.b.c.d","a.b.c","a.b.","a"}
func Prefixes(in string) (out []string) {
	for _, termSet := range Inits(SplitTerms(in)) {
		out = append(out, JoinTerms(termSet))
	}
	return out
}
