package tree

import (
	"path/filepath"
	"sort"
	"strings"
)

type Tree interface {
	Add(string) Tree
	AddPath(string) Tree
	FlatView() chan string
	TreeView() chan string
}

func New() Tree {
	return &node{
		Name: ".",
	}
}

type node struct {
	Name  string
	Nodes map[string]*node
}

func (t *node) Add(name string) Tree {
	if t.Nodes == nil {
		t.Nodes = map[string]*node{}
	}
	n, ok := t.Nodes[name]
	if !ok {
		n = &node{Name: name}
		t.Nodes[name] = n
	}
	return n
}
func (t *node) AddPath(path string) Tree {
	n := Tree(t)
	for _, name := range strings.Split(path, "/") {
		n = n.Add(name)
	}
	return n
}

func (t *node) Children() []*node {
	childs := make([]*node, len(t.Nodes))
	i := 0
	for _, n := range t.Nodes {
		childs[i] = n
		i++
	}
	sort.Slice(childs, func(i, j int) bool {
		return childs[i].Name < childs[j].Name
	})
	return childs
}

func (t *node) HasChildren() bool {
	return t.Nodes != nil
}

func (t *node) FlatView() (out chan string) {
	out = make(chan string)
	go func() {
		defer close(out)
		var flatten func(string, *node)

		flatten = func(path string, t *node) {
			switch t.HasChildren() {
			case false:
				out <- path
			case true:
				for _, child := range t.Children() {
					flatten(filepath.Join(path, child.Name), child)
				}
			}
		}

		flatten("", t)
	}()
	return out
}

func (t *node) TreeView() (out chan string) {
	out = make(chan string)
	treeLinkChar := "│   "
	treeMidChar := "├── "
	treeEndChar := "└── "
	treeAfterEndChar := "    "

	go func() {
		defer close(out)

		var tree func(string, *node)

		tree = func(prefix string, t *node) {
			children := t.Children()
			for i, st := range children {
				switch i {
				case len(children) - 1:
					out <- prefix + treeEndChar + st.Name
					tree(prefix+treeAfterEndChar, st)
				case 0:
					out <- prefix + treeMidChar + st.Name
					tree(prefix+treeLinkChar, st)
				default:
					out <- prefix + treeMidChar + st.Name
					tree(prefix+treeLinkChar, st)
				}
			}
		}

		out <- t.Name
		tree("", t)
	}()
	return out
}