/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.collection.map;

import java.util.Map.Entry;
import java.util.*;
import net.ranides.assira.generic.Function;

/**
 * Klasa abstrakcyjna umożliwiająca leniwą konwersję mapy elementów na inny format
 * (a więc tworzenie map tylko do odczytu). Konwertowane są tylko wartości umieszczone
 * w mapie - klucze nie mogą być transformowane.
 * <p>
 * Ogólny wariant adaptera zakłada, że konwersja może zwracać jako wynik obiekty
 * innej klasy, niż źródłowa. Specjalizacja {@link MapAdapterTT} służy do przeprowadzania
 * konwersji, które mają wpływ jedynie na wartości, ale nie na klasy elementów.
 * </p><p>
 * Oszczędza pamięć i czas procesora: nie musimy foreach'em
 * konwertować wszystkiego ze starej mapy i kopiować do nowej. Konwersja odbywa
 * się w locie. Jest to jeszcze bardziej istotne, niż w przypadku adapterów list,
 * ponieważ konstrukcja samej mapy jest bardzo kosztowna. Dzięki adapterowi nie
 * trzeba przebudowywać całego drzewa kluczy lub przeliczać wszystkich kodów
 * haszujących. Również oszczędność pamięciowa jest nieporównywalnie większa.
 * </p><p>
 * Mapa pozwala na modyfikację zawartości na tyle, na ile to możliwe - tzn np na usuwanie
 * kluczy. Zwrócone iteratory i widoki (entryList, values) pozwalają na wszystkie 
 * operacje, delegując ich realizację do klasy. Aby więc stworzyć 
 * w pełni funkcjonalny adapter działający w dwie strony, wystarczy zaimplementować
 * metodę {@link #put(java.lang.Object, java.lang.Object) put}.
 * </p><p>
 * W metodzie {@link #apply(java.lang.Object) apply} można nie tylko konwertować 
 * wartości, ale również logować operacje odczytu. Z tego powodu adapter może być
 * użyty z powodzeniem jako wydajna realizacja obserwatora.
 * </p>
 * 
 * @see MapAdapterTT
 * @see MapBiAdapter
 * 
 * @param <K> klucz
 * @param <VT> format publiczny, udostępniany konsumentowi
 * @param <VS> wewnętrzny format danych
 * @author ranides
 */
public abstract class MapAdapter<K, VS, VT> implements Map<K, VT>, Function<VT, Entry<K,VS>> {

    final Map<K, VS> content;
    
    /**
     * Tworzy nowy adapter odwzorowujący podaną mapę.
     * @param content
     */
    public MapAdapter(Map<K, VS> content) {
        this.content = content;
    }

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

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

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

    @Override
    @SuppressWarnings("element-type-mismatch")
    public boolean containsValue(Object value) {
        return this.values().contains(value);
    }

    @Override
    public VT get(Object key) {
        return apply(new EntryConst<K,VS>(key, this.content.get(key) ));
    }

    @Override
    public VT put(K key, VT value) {
        throw new UnsupportedOperationException("immutable map");
    }

    @Override
    public VT remove(Object key) {
        return apply(new EntryConst<K,VS>(key, this.content.remove(key) ));
    }

    @Override
    public void putAll(Map<? extends K, ? extends VT> map) {
        for(Entry<? extends K, ? extends VT> entry : map.entrySet() ) {
            put(entry.getKey(), entry.getValue());
        }
    }

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

    @Override
    public Set<K> keySet() {
        return this.content.keySet();
    }

    @Override
    public Collection<VT> values() {
        return new AbstractCollection<VT>() {
            @Override
            public Iterator<VT> iterator() { return new ValueIterator(); }
            @Override
            public int size() { return content.size(); }
        };
    }

    @Override
    public Set<Entry<K, VT>> entrySet() {
        return new EntrySet();
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "[ " + content.toString() + " ]";
    }

/* ************************************************************************** */

    private class ValueIterator implements Iterator<VT> {

        private final EntrySource<K,VS> shentry = new EntrySource<>();
        private final Iterator<Entry<K,VS>> iterator;

        public ValueIterator() {
            iterator = content.entrySet().iterator();
        }

        @Override
        public boolean hasNext() {
            return iterator.hasNext();
        }

        @Override
        public VT next() {
            Entry<K,VS> entry = iterator.next();
            return apply(shentry.set(entry.getKey(), entry.getValue() ));
        }

        @Override
        public void remove() {
            iterator.remove();
        }

    }

/* ************************************************************************** */

    private class EntryIterator implements Iterator<Entry<K,VT>> {

        private final Iterator<Entry<K,VS>> iterator;

        public EntryIterator() {
            iterator = content.entrySet().iterator();
        }

        @Override
        public boolean hasNext() {
            return iterator.hasNext();
        }

        @Override
        public Entry<K,VT> next() {
            Entry<K,VS> entry = iterator.next();
            return new EntryConst<>(entry.getKey(), apply(entry) );
        }

        @Override
        public void remove() {
            iterator.remove();
        }

    }

/* ************************************************************************** */

    private class EntrySet extends AbstractSet<Entry<K,VT>> {

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


        @Override
        @SuppressWarnings("element-type-mismatch")
        public boolean contains(Object object) {
            if(object instanceof Entry) { return false; }
            return MapAdapter.this.containsKey( ((Entry)object).getKey() );
        }

        @Override
        public Iterator<Entry<K, VT>> iterator() {
            return new EntryIterator();
        }

        @Override
        public boolean add(Entry<K, VT> entry) {
            return !entry.equals( MapAdapter.this.put(entry.getKey(), entry.getValue()) );
        }

        @Override
        //@SuppressWarnings("element-type-mismatch")
        public boolean remove(Object entry) {
            if(entry instanceof Entry) { return false; }
            return null != MapAdapter.this.remove( ((Entry)entry).getKey() );
        }

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

    }

/* ************************************************************************** */

    final static class EntryConst<KK,VV> implements Entry<KK,VV> {
        private final KK key;
        private final VV val;
        @Override
        public KK getKey() {
            return key;
        }
        @Override
        public VV getValue() {
            return val;
        }
        @Override
        public VV setValue(VV value) {
            throw new UnsupportedOperationException("Immutable entry.");
        }
        @SuppressWarnings("unchecked")
        public EntryConst(Object key, VV val) {
            this.key = (KK)key;
            this.val = val;
        }
        @Override
        public String toString() {
            return key + "=" + val;
        }
    }

    final static class EntrySource<KK,VV> implements Entry<KK,VV> {
        private KK key;
        private VV val;
        @Override
        public KK getKey() {
            return key;
        }
        @Override
        public VV getValue() {
            return val;
        }
        @Override
        public VV setValue(VV value) {
            throw new UnsupportedOperationException("You can't change entry passed for transformation only.");
        }
        @SuppressWarnings("unchecked")
        public Entry<KK,VV> set(Object key, VV val) {
            this.key = (KK)key;
            this.val = val;
            return this;
        }
        @Override
        public String toString() {
            return key + "=" + val;
        }
    }


}
