package com.googlecode.totallylazy.collections;

import com.googlecode.totallylazy.Atomic;
import com.googlecode.totallylazy.Maps;
import com.googlecode.totallylazy.Option;
import com.googlecode.totallylazy.Pair;
import com.googlecode.totallylazy.Segment;
import com.googlecode.totallylazy.Unchecked;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;

import static com.googlecode.totallylazy.Atomic.constructors.atomic;
import static com.googlecode.totallylazy.Pair.pair;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.Unchecked.cast;

public class AtomicMap<K, V> implements ConcurrentMap<K, V> {
    private final Atomic<PersistentMap<K, V>> atomic;

    private AtomicMap(Atomic<PersistentMap<K, V>> atomic) {
        this.atomic = atomic;
    }

    public static <K, V> AtomicMap<K, V> atomicMap(PersistentMap<K, V> map) {return new AtomicMap<K, V>(atomic(map));}

    private PersistentMap<K, V> map() {return atomic.value();}

    private K key(Object key) {return cast(key);}

    @Override
    public int size() {
        return map().size();
    }

    @Override
    public boolean isEmpty() {
        return map().isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return map().contains(key(key));
    }

    @Override
    public boolean containsValue(Object value) {
        return values().contains(value);
    }

    @Override
    public V get(Object key) {
        return map().lookup(key(key)).getOrNull();
    }

    @Override
    public V put(final K key, final V value) {
        return atomic.modifyReturn(map -> AtomicMap.this.put(map, key, value));
    }

    private Pair<PersistentMap<K, V>, V> put(PersistentMap<K, V> map, K key, V value) {
        return PersistentMap.methods.put(map, key, value).
                second(Option.functions.<V>getOrNull());
    }

    @Override
    public void putAll(final Map<? extends K, ? extends V> m) {
        atomic.modify(map -> Maps.pairs(m).<Pair<K, V>>unsafeCast().fold(map, Segment.functions.<Pair<K, V>, PersistentMap<K, V>>cons()));
    }

    @Override
    public V remove(final Object key) {
        return atomic.modifyReturn(map -> PersistentMap.methods.remove(map, key(key)).
                second(Option.functions.<V>getOrNull()));
    }

    @Override
    public void clear() {
        atomic.modify(PersistentMap<K, V>::empty);
    }

    @Override
    public Set<K> keySet() {
        return map().keys().toSet();
    }

    @Override
    public Collection<V> values() {
        return map().values().toList();
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        return Maps.entrySet(map());
    }

    @Override
    public V putIfAbsent(final K key, final V value) {
        return atomic.modifyReturn(map -> {
            if (!map.contains(key)) return put(map, key, value);
            return pair(map, map.lookup(key).getOrNull());
        });
    }

    @Override
    public boolean remove(final Object rawKey, final Object value) {
        return atomic.modifyReturn(map -> {
            K key = key(rawKey);
            if (map.lookup(key).contains(Unchecked.<V>cast(value))) return pair(map.delete(key), true);
            return pair(map, false);
        });
    }

    @Override
    public boolean replace(final K rawKey, final V oldValue, final V newValue) {
        return atomic.modifyReturn(map -> {
            K key = key(rawKey);
            if (map.lookup(key).contains(Unchecked.<V>cast(oldValue))) return pair(map.insert(key, newValue), true);
            return pair(map, false);
        });
    }

    @Override
    public V replace(final K key, final V value) {
        return atomic.modifyReturn(map -> {
            if (map.contains(key)) return AtomicMap.this.put(map, key, value);
            return pair(map, null);
        });
    }
}