package com.googlecode.totallylazy.collections;

import com.googlecode.totallylazy.functions.Function1;
import com.googlecode.totallylazy.Filterable;
import com.googlecode.totallylazy.Foldable;
import com.googlecode.totallylazy.Functor;
import com.googlecode.totallylazy.Maps;
import com.googlecode.totallylazy.Option;
import com.googlecode.totallylazy.Pair;
import com.googlecode.totallylazy.predicates.Predicate;
import com.googlecode.totallylazy.Segment;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.totallylazy.Unchecked;

import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentMap;

import static com.googlecode.totallylazy.Pair.pair;
import static com.googlecode.totallylazy.Sequences.sequence;

public interface PersistentMap<K, V> extends Map<K, V>, Iterable<Pair<K, V>>, Segment<Pair<K, V>>, PersistentContainer<K>, Foldable<Pair<K, V>> {
    @Override
    boolean isEmpty();

    Option<V> lookup(K key);

    PersistentMap<K, V> insert(K key, V value);

    PersistentMap<K, V> delete(K key);

    @Override
    PersistentMap<K, V> empty();

    @Override
    PersistentMap<K, V> cons(Pair<K, V> head);

    @Override
    PersistentMap<K, V> tail() throws NoSuchElementException;

    PersistentList<Pair<K, V>> toPersistentList();

    Sequence<Pair<K, V>> toSequence();

    Sequence<K> keys();

    @Override
    Sequence<V> values();

    Map<K, V> toMutableMap();

    ConcurrentMap<K, V> toConcurrentMap();

    /**
     * @deprecated Not type safe: Replaced by {@link PersistentMap#delete(K)}
     */
    @Override
    @Deprecated
    V remove(Object o);

    /**
     * @deprecated Not type safe: Replaced by {@link PersistentMap#insert(K, V)}
     */
    @Override
    @Deprecated
    V put(K key, V value);

    /**
     * @deprecated Not type safe: Replaced by {@link PersistentMap#insert(K, V)}
     */
    @Override
    @Deprecated
    void putAll(Map<? extends K, ? extends V> m);

    /**
     * @deprecated Not type safe: Replaced by {@link PersistentMap#empty()}
     */
    @Override
    @Deprecated
    void clear();

    class constructors {
        public static <K, V> PersistentMap<K, V> map() {
            return HashTreeMap.hashTreeMap();
        }

        public static <K, V> PersistentMap<K, V> emptyMap() {
            return map();
        }

        public static <K, V> PersistentMap<K, V> emptyMap(Class<K> kClass, Class<V> vClass) {
            return map();
        }

        public static <K, V> PersistentMap<K, V> map(K key, V value) {
            return map(sequence(pair(key, value)));
        }

        public static <K, V> PersistentMap<K, V> map(K key1, V value1, K key2, V value2) {
            return map(sequence(pair(key1, value1), pair(key2, value2)));
        }

        public static <K, V> PersistentMap<K, V> map(K key1, V value1, K key2, V value2, K key3, V value3) {
            return map(sequence(pair(key1, value1), pair(key2, value2), pair(key3, value3)));
        }

        public static <K, V> PersistentMap<K, V> map(K key1, V value1, K key2, V value2, K key3, V value3, K key4, V value4) {
            return map(sequence(pair(key1, value1), pair(key2, value2), pair(key3, value3), pair(key4, value4)));
        }

        public static <K, V> PersistentMap<K, V> map(K key1, V value1, K key2, V value2, K key3, V value3, K key4, V value4, K key5, V value5) {
            return map(sequence(pair(key1, value1), pair(key2, value2), pair(key3, value3), pair(key4, value4), pair(key5, value5)));
        }

        @SafeVarargs
        public static <K, V> PersistentMap<K, V> map(final Pair<K, V> head, final Pair<K, V>... tail) {
            return map(sequence(tail).cons(head));
        }

        public static <K, V> PersistentMap<K, V> map(final Iterable<? extends Pair<K, V>> values) {
            return HashTreeMap.hashTreeMap(values);
        }

        public static <K, V> PersistentMap<K, V> map(final Map<K, V> values) {
            return map(Maps.pairs(values));
        }
    }

    class functions {
        public static <K, V> Function1<PersistentMap<K, V>, Option<V>> get(final K key) {
            return map -> map.lookup(key);
        }

        public static <K, V> Function1<PersistentMap<K, V>, PersistentMap<K, V>> remove(final K key) {
            return map -> map.delete(key);
        }

        public static <K, V> Function1<PersistentMap<K, V>, Boolean> contains(final Object other) {
            return map -> map.contains(other);
        }
    }

    class methods {
        public static <K, V> Map<K, V> toMap(PersistentMap<K, V> source) {
            return Maps.map(source);
        }

        public static <K, V, M extends PersistentMap<K, V>> Pair<M, Option<V>> put(M map, K key, V newValue) {
            return Pair.pair(Unchecked.<M>cast(map.insert(key, newValue)), map.lookup(key));
        }

        public static <K, V, M extends PersistentMap<K, V>> Pair<M, Option<V>> remove(M map, K key) {
            return Pair.pair(Unchecked.<M>cast(map.delete(key)), map.lookup(key));
        }
    }
}
