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

package net.ranides.assira.collection.list;

import java.util.*;
import net.ranides.assira.generic.CoFunction;
import net.ranides.assira.generic.Function;

/**
 * Klasa abstrakcyjna umożliwiająca leniwą konwersję listy elementów na inny format
 * (a więc tworzenie list tylko do odczytu).
 * <p>
 * Ogólny wariant adaptera zakłada, że konwersja może zwracać jako wynik obiekty
 * innej klasy, niż źródłowa. Specjalizacja {@link ListAdapterTT} 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 listy i kopiować do nowej. Konwersja odbywa
 * się w locie.
 * </p><p>
 * Rozwiązuje problem przekazania danych w określonym formacie do widoku,
 * który ma prawo jedynie je odczytywać - a nie modyfikować. Próba
 * modyfikacji powoduje wyjątek zamiast "cichego" nieokreślonego zachowania
 * polegającego na modyfikowaniu listy w oparciu o wiązania "do referencji"
 * </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>
 *
 * @param <T> format publiczny, udostępniany konsumentowi
 * @param <S> wewnętrzny format danych
 *
 * @author ranides
 */
public abstract class ListAdapter<T,S> extends AbstractList<T> implements Function<T,S> {

    /**
     * Lista źródłowa, której elementy będą odwzorowywane.
     */
    protected final List<S> content;

    /**
     * Tworzy nowy adapter odwzorowujący podaną listę.
     * @param content
     */
    public ListAdapter(List<S> content) {
        this.content = (null == content) ? Collections.<S>emptyList() : content;
    }

    /**
     * Tworzy nowy adapter odwzorowujący podaną tablicę.
     * @param content
     */
    public ListAdapter(S[] content) {
        this.content = (null == content) ? Collections.<S>emptyList() : Arrays.asList(content);
    }

    @Override
    public T get(int index) {
        return apply( content.get(index) );
    }

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

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

    /**
     * Wykonuje natychmiast pełną konwersję wszystkich elementów i jako wynik
     * zwraca nową tablicę, która jest w pełni niezależna od adaptera oraz
     * kolekcji źródłowej.
     * <p>
     * Adapter dokonuje konwersji przy każdej operacji odczytu, zwracając najbardziej
     * aktualną i zsynchronizowaną wartość. Jeśli lista źródłowa nie jest często
     * modyfikowana, a adapter jest bardzo intensywnie używany (wielokrotne
     * iterowanie po całej zawartości) optymalniejszym rozwiązaniem może być
     * wywołanie poniższej metody i praca na zwróconej kopii.
     * </p>
     * <p>
     * Użycie metody może oszczędzić nam wielokrotnego dokonywania tych samych
     * konwersji (a więc czas procesora) ale tracimy całą oszczędność pamięciową.
     * </p>
     * @return
     */
    public List<T> apply() {
        List<T> list = new ArrayList<>(content.size());
        for(Iterator<S> item = content.iterator(); item.hasNext(); ) {
            list.add(apply(item.next()));
        }
        return list;
    }

    /**
     * Metoda statyczna pozwalająca transformować podaną listę za pomocą przekazanego
     * operatora konwersji. Jako wynik zwraca nową, zupełnie niezależną listę z
     * przekonwertowanymi obiektami.
     * @param <S>
     * @param <T>
     * @param content transformowana lista
     * @param function
     * @return
     */
    public static <S,T> List<T> apply(Collection<S> content, Function<T,S> function) {
        List<T> list = new ArrayList<>(content.size());
        for(Iterator<S> item = content.iterator(); item.hasNext(); ) {
            list.add(function.apply(item.next()));
        }
        return list;
    }

    /**
     * Metoda statyczna pozwalająca transformować podaną listę za pomocą przekazanego
     * operatora konwersji. Jako wynik zwraca nową, zupełnie niezależną listę z
     * przekonwertowanymi obiektami.
     * @param <S>
     * @param <T>
     * @param content transformowana lista
     * @param function
     * @return
     */
    public static <S,T> List<S> inverse(Collection<T> content, CoFunction<T,S> function) {
        List<S> list = new ArrayList<>(content.size());
        for(Iterator<T> item = content.iterator(); item.hasNext(); ) {
            list.add(function.inverse(item.next()));
        }
        return list;
    }

}
