/*
 *  @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.io.Serializable;
import java.util.*;
import net.ranides.assira.generic.Function;
import net.ranides.assira.generic.ValueUtils;
import net.ranides.assira.math.HashHelper;

/**
 * Klasa abstrakcyjna ułatwiająca wygodne tworzenie "wirtualnych" list. Poniższa
 * implementacja stara się minimalizować ilość wywołań metody {@link #get},
 * zakładając, że operacje odczytu są kosztowne - jednak nie realizuje żadnego
 * rodzaju buforowania.
 * <p>
 * Domyślnie lista nie pozwala na modyfikacje. Aby stworzyć listę pozwalającą na
 * modyfikacje należy przeładować metody:
 * </p>
 * <ul>
 *      <li> {@link #set(int index, Object value) set(int index, Object value)} - modyfikacja elementów </li>
 *      <li> {@link #add(int index, Object value) add(int index, Object value)} - dodawanie elementów </li>
 *      <li> {@link #remove(int) remove(int index)} - usuwanie elementów </li>
 * </ul>
 * @param <T>
 * @author ranides
 */
public abstract class VirtualList<T> implements List<T>, RandomAccess, Serializable {
    
    private static final long serialVersionUID = 1L;

    private static final String MSG_IMMUTABLE_LIST = "Not supported by immutable virtual lists.";

    @Override
    public abstract int size();

    @Override
    public abstract T get(int index);

    @Override
    public void add(int index, T element) {
        throw new UnsupportedOperationException(MSG_IMMUTABLE_LIST);
    }

    @Override
    public T set(int index, T element) {
        throw new UnsupportedOperationException(MSG_IMMUTABLE_LIST);
    }

    @Override
    public T remove(int index) {
        throw new UnsupportedOperationException(MSG_IMMUTABLE_LIST);
    }

    // <editor-fold defaultstate="collapsed" desc=" common boilerplate implementation ">

    @Override
    public boolean isEmpty() {
        return 0 == size();
    }

    @Override
    public boolean contains(Object value) {
        int index = indexOf(value);
        return (index >= 0) && (index <= size());
    }

    @Override
    public Iterator<T> iterator() {
        return listIterator();
    }

    @Override
    public Object[] toArray() {
        throw new UnsupportedOperationException("Not supported by virtual lists.");
    }

    @Override
    public <T> T[] toArray(T[] target) {
        throw new UnsupportedOperationException("Not supported by virtual lists.");
    }

    @Override
    public boolean add(T value) {
        add(size(), value);
        return true;
    }

    @Override
    public boolean containsAll(Collection<?> values) {
        Set<?> set = new HashSet<>(values);
        ListIterator<? extends T> iterator = listIterator();
        while(iterator.hasNext()) {
            set.remove( iterator.next() );
            if( set.isEmpty() ) { return true; }
        }
        return false;
    }

    @Override
    public boolean addAll(Collection<? extends T> values) {
        return addAll(size(), values);
    }

    @Override
    public boolean addAll(int index, Collection<? extends T> values) {
        boolean modified = false;
        Iterator<? extends T> iterator = values.iterator();
        for(int i=index; iterator.hasNext(); i++) {
            add(i++, iterator.next());
            modified = true;
        }
        return modified;
    }

    @Override
    public boolean remove(Object value) {
        Iterator<T> iterator = iterator();
        if (value == null) {
            while (iterator.hasNext()) {
                if (iterator.next() == null) {
                    iterator.remove();
                    return true;
                }
            }
        } else {
            while (iterator.hasNext()) {
                if (value.equals(iterator.next())) {
                    iterator.remove();
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> values) {
        boolean modified = false;
        Set<?> set = new HashSet<>(values);
        ListIterator<? extends T> iterator = listIterator();
        while(iterator.hasNext()) {
            T value = iterator.next();
            if(set.contains(value)) {
                iterator.remove();
                modified = true;
            }
        }
        return modified;
    }

    @Override
    public boolean retainAll(Collection<?> values) {
        boolean modified = false;
        Set<?> set = new HashSet<>(values);
        ListIterator<? extends T> iterator = listIterator();
        while(iterator.hasNext()) {
            T value = iterator.next();
            if(!set.contains(value)) {
                iterator.remove();
                modified = true;
            }
        }
        return modified;
    }

    @Override
    public void clear() {
        for(Iterator<T> iterator = listIterator(); iterator.hasNext(); iterator.next() ) {
            iterator.remove();
        }
    }


    @Override
    public int indexOf(Object value) {
        ListIterator<T> iterator = listIterator();
        if (value == null) {
            while (iterator.hasNext()) {
                if (iterator.next() == null) {
                    return iterator.previousIndex();
                }
            }
        } else {
            while (iterator.hasNext()) {
                if (value.equals(iterator.next())) {
                    return iterator.previousIndex();
                }
            }
        }
        return -1;
    }

    @Override
    public int lastIndexOf(Object value) {
        ListIterator<T> iterator = listIterator(size());
        if (value == null) {
            while (iterator.hasPrevious()) {
                if (iterator.previous() == null) {
                    return iterator.nextIndex();
                }
            }
        } else {
            while (iterator.hasPrevious()) {
                if (value.equals(iterator.previous())) {
                    return iterator.nextIndex();
                }
            }
        }
        return -1;
    }

    @Override
    public ListIterator<T> listIterator() {
        return new IIterator(0);
    }

    @Override
    public ListIterator<T> listIterator(int index) {
        return new IIterator(index);
    }

    @Override
    public List<T> subList(int fromIndex, int toIndex) {
        return subList(this, fromIndex, toIndex);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append('[');
        boolean first = true;
        for(Object value : this) {
            if(!first) { builder.append(','); }
            builder.append(null != value ? value.toString() : "null"); 
            first = false;
        }
        builder.append(']');
        return builder.toString();
    }

    @Override
    public boolean equals(Object object) {
        if(object == this) {
            return true;
        }
        if(!(object instanceof List<?>)) {
            return false;
        }
        int n = size();
        List<?> value = (List<?>)object;
        if( value.size() != n ) {
            return false;
        }
        Iterator<?> i1 = this.iterator();
        Iterator<?> i2 = value.iterator();
        for(int i=0; i<n; i++) {
            if( !ValueUtils.equals(i1.next(), i2.next()) ) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        // hashcode compatible with AbstractList
        return HashHelper.hashIterable(1, 31, this);
    }
    
    

    // </editor-fold>

    private class IIterator extends AbstractListIterator<T> {

        public IIterator(int index) {
            super(index);
        }

        @Override
        protected void remove(int index) {
            VirtualList.this.remove(index);
        }

        @Override
        protected void add(int index, T value) {
            VirtualList.this.add(index, value);
        }

        @Override
        protected void set(int index, T value) {
            VirtualList.this.set(index, value);
        }

        @Override
        protected T get(int index) {
            return VirtualList.this.get(index);
        }

        @Override
        protected int size() {
            return VirtualList.this.size();
        }

    }

    private static class SubList<Q> extends VirtualList<Q> {

        private final List<Q> list;
        private final int from;
        private final int to;

        public SubList(List<Q> list, int from, int to) {
            this.list = list;
            this.from = from;
            this.to = to;
        }

        @Override
        public int size() {
            return Math.max(0, Math.min(to, list.size() ) - from );
        }

        @Override
        public Q get(int index) {
            return list.get(from+index);
        }

        @Override
        public void add(int index, Q element) {
            list.add(from+index,element);
        }

        @Override
        public Q set(int index, Q element) {
            return list.set(from+index,element);
        }

        @Override
        public Q remove(int index) {
            return list.remove(from + index);
        }

    }

    private static class RandomSubList<Q> extends SubList<Q> implements RandomAccess {
        public RandomSubList(List<Q> list, int from, int to) {
            super(list, from, to);
        }
    }

    /**
     * Statyczna metoda, która z podanej listy wskazany zakres. Użyteczna dla
     * twórców własnych implementacji list, którzy chcą uniknąć pisania własnej
     * klasy sub-listującej, której jedynym zadaniem jest przeliczanie wszystkich
     * indeksów.
     * <p>
     * Metoda prawidłowo rozpoznaje, czy źródło implementuje {@link RandomAccess} i
     * na tej podstawie zwraca listę, która również ten interfejs implementuje lub nie.
     * </p>
     * @param <Q>
     * @param list lista źródłowa
     * @param from indeks początku listy (włącznie)
     * @param to indeks końca listy (wyłącznie)
     * @return
     */
    public static <Q> List<Q> subList(List<Q> list, int from, int to) {
        return (list instanceof RandomAccess) ? new RandomSubList<>(list, from, to) : new SubList<>(list, from, to);
    }
    
    /**
     * Tworzy nową wirtualną listę o podanym rozmiarze. Elementy są generowane
     * na żądanie.
     * @param <Q>
     * @param size
     * @param function
     * @return 
     */
    public static <Q> List<Q> produce(final int size, final Function<Q, Integer> function) {
        return new VirtualList<Q>() {
            @Override
            public int size() {
                return size;
            }
            @Override
            public Q get(int index) {
                return function.apply(index);
            }
        };
    }
    
}
