package com.googlecode.totallylazy.collections;

import com.googlecode.totallylazy.Option;
import com.googlecode.totallylazy.Segment;
import com.googlecode.totallylazy.Value;
import com.googlecode.totallylazy.annotations.tailrec;

import static com.googlecode.totallylazy.Option.none;
import static com.googlecode.totallylazy.Option.option;

public class Trie<K, V> implements Value<V> {
    private final Option<V> value;
    private final PersistentMap<K, Trie<K, V>> children;

    private Trie(Option<V> value, PersistentMap<K, Trie<K, V>> children) {
        this.value = value;
        this.children = children;
    }

    public static <K, V> Trie<K, V> trie() {
        return trie(Option.<V>none());
    }

    public static <K, V> Trie<K, V> trie(Option<V> value) {
        return trie(value, ListMap.<K, Trie<K, V>>emptyListMap());
    }

    public static <K, V> Trie<K, V> trie(Option<V> value, PersistentMap<K, Trie<K, V>> children) {
        return new Trie<K, V>(value, children);
    }

    public boolean contains(Segment<K> key){
        if(key.isEmpty()) return !value.isEmpty();
        Option<Trie<K, V>> child = childFor(key);
        return !child.isEmpty() && child.get().contains(key.tail());
    }

    @tailrec
    public Option<V> get(Segment<K> key) {
        if(key.isEmpty()) return value;
        Option<Trie<K, V>> child = childFor(key);
        if(child.isEmpty()) return none();
        return child.get().get(key.tail());
    }

    public Trie<K, V> put(Segment<K> key, V value) {
        if(key.isEmpty()) return trie(option(value), children);
        return trie(this.value, children.insert(key.head(), childFor(key).getOrElse(Trie.<K, V>trie()).put(key.tail(), value)));
    }

    public Trie<K, V> remove(Segment<K> key) {
        return put(key, null);
    }

    public V value() {
        return value.get();
    }

    public boolean isEmpty(){
        return value.isEmpty() && children.isEmpty();
    }

    private Option<Trie<K, V>> childFor(Segment<K> key) {return children.lookup(key.head());}
}