package net.ranides.assira.observable;

import net.ranides.assira.collection.maps.AMap;
import net.ranides.assira.events.EventListener;
import net.ranides.assira.events.EventRouter;

import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class ObservableMap<K,V> extends AMap<K,V> {

    private final Map<K,V> map;

    private final EventRouter router;

    public ObservableMap(EventRouter router, Map<K, V> map) {
        this.map = map;
        this.router = router;
    }

    public EventRouter router() {
        return router;
    }

    public <E extends MapEvent<K,V>> void addEventListener(Class<E> event, EventListener<? super E> listener) {
        router.addEventListener(event, listener);
    }

    protected void signalPut(K key, V prev, V value) {
        router.signalEvent(new MapEvent.Put<>(this, key, prev, value));
    }

    protected void signalRemove(K key, V prev) {
        router.signalEvent(new MapEvent.Remove<>(this, key, prev));
    }

    protected void signalPutAll(Map<? extends K, ? extends V> m) {
        router.signalEvent(new MapEvent.PutAll<>(this, m));
    }

    protected void signalClear() {
        router.signalEvent(new MapEvent.Clear<>(this));
    }

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

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

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

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

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

    @Override
    public V put(K key, V value) {
        V prev = map.put(key, value);
        signalPut(key, prev, value);
        return prev;
    }

    @Override
    public V remove(Object key) {
        V prev = map.remove(key);
        if(prev != null) {
            signalRemove((K) key, prev);
        }
        return prev;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        map.putAll(m);
        signalPutAll(m);
    }

    @Override
    public void clear() {
        map.clear();
        signalClear();
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        return new AbstractSet<Entry<K, V>>() {
            @Override
            public Iterator<Entry<K, V>> iterator() {
                Set<Entry<K, V>> entries = map.entrySet();
                return new CIterator(entries.iterator());
            }

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

            @Override
            public boolean remove(Object object) {
                if (object instanceof Map.Entry) {
                    Map.Entry<?, ?> entry = (Map.Entry<?,?>) object;
                    return ObservableMap.this.remove(entry.getKey(), entry.getValue());
                }
                return false;
            }

            @Override
            public void clear() {
                ObservableMap.this.clear();
            }
        };
    }

    private class CIterator extends ObservableIterator<Entry<K, V>> {
        public CIterator(Iterator<Entry<K, V>> itr) {
            super(itr);
        }

        @Override
        protected void signalRemove(Entry<K, V> entry) {
            router.signalEvent(new MapEvent.Remove<K,V>(ObservableMap.this, entry.getKey(), entry.getValue()));
        }
    }
}
