/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.lists;

import net.ranides.assira.annotations.Meta;
import net.ranides.assira.collection.iterators.RandomAccessIterator;
import net.ranides.assira.collection.iterators.RandomAccessSpliterator;

import java.io.Serializable;
import java.util.AbstractList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.RandomAccess;
import java.util.Spliterator;
import java.util.function.IntFunction;
import java.util.function.IntSupplier;

/**
 * Abstract class for easy creation "virtual lists". Algorithms in below implementation
 * memoize calls to {@link #get} method, because of assumption that read operations
 * have high cost.
 *
 * This class is thread-safe, internally synchronized.
 *
 * This list is visible as immutable.
 *
 * @param <T> T
 */
@Meta.ThreadSafe
public abstract class LazyList<T> extends AbstractList<T> implements RandomAccess, Serializable {
    
    private static final long serialVersionUID = 1L;

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

    private T[] computed;

    private LazyList() {
        this.computed = null;
    }
    /**
     * Returns new virtual list of specified size, which contains elements calculated by "function" at every access.
     *
     * Virtual list does not support modifications.
     *
     * @param size size
     * @param function function
     * @param <T> T
     * @return virtual
     */
    public static <T> List<T> of(int size, IntFunction<? extends T> function) {
        return of((IntSupplier & Serializable)() -> size, function);
    }

    /**
     * Returns new virtual list of size calculated by "size",
     * which contains elements calculated by "function" at every access.
     *
     * Virtual list does not support modifications.
     *
     * @param size size
     * @param function function
     * @param <T> T
     * @return virtual
     */
    public static <T> List<T> of(IntSupplier size, IntFunction<? extends T> function) {
        return new LazyList<T>() {
            @Override
            public int size() {
                return size.getAsInt();
            }

            @Override
            public T computeAt(int index) {
                return function.apply(index);
            }
        };
    }

    @Override
    public abstract int size();

    protected abstract T computeAt(int index);

    @SuppressWarnings("unchecked")
    @Override
    public final T get(int index) {
        synchronized (this) {
            if(computed == null) {
                computed = (T[])new Object[size()];
            }
            T prev = computed[index];
            if (prev != null) {
                return prev;
            } else {
                return computed[index] = computeAt(index);
            }
        }
    }

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

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

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

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

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

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

    @Override
    public Spliterator<T> spliterator() {
        return RandomAccessSpliterator.of(0, size(), this::get);
    }

    private class LazyIterator extends RandomAccessIterator<T> {

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

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

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

    }

}
