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

package net.ranides.assira.collection;

import java.io.Serializable;
import java.util.*;
import net.ranides.assira.generic.ValueUtils;
import net.ranides.assira.math.HashHelper;

/**
 * Wydajna pamięciowo implementacja kolekcji zawierającej tylko 1 element.
 * Kolekcja nie może zmieniać rozmiarów, ani być modyfikowana za
 * pomocą iteratora. Niemniej przechowywana pojedyncza wartość
 * może być modyfikowana bezpośrednio za pomocą funkcji set.
 *
 * <p>Kolekcja implementuje interfejsy {@link Set} oraz {@link List}</p>
 * @param <T>
 * @author ranides
 */
public class SingleCollection<T> implements List<T>, Set<T>, Serializable  {

    private static final long serialVersionUID = 1L;

    private static final String IMMUTABLE_COLLECTION = "Immutable collection";

    private T value;

    /**
     * Tworzy nową kolekcję zawierającą podany obiekt
     * @param value
     */
    public SingleCollection(T value) {
        this.value = value;
    }

    /**
     * Tworzy nową kolekcję zawierającą podany obiekt
     * @param <ST>
     * @param value
     * @return
     */
    public static <ST> SingleCollection<ST> of(ST value) {
        return new SingleCollection<>(value);
    }

    @Override
    public int size() {
        return 1;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public boolean contains(Object object) {
        return ValueUtils.equals(value, object);
    }

    @Override
    public Iterator<T> iterator() {
        return new SingleIterator<>(value);
    }

    @Override
    public Object[] toArray() {
        return new Object[]{value};
    }

    @SuppressWarnings("unchecked")
    @Override
    public <K> K[] toArray(K[] target) {
        K[] result;
        Class<K> clazz = (Class<K>)target.getClass().getComponentType();
        if(target.length>=1) {
            result = target;
            Arrays.fill(target, 1, target.length, null);
        } else {
            result = ArrayUtils.makeT(clazz, 1);
        }
        result[0] = clazz.cast(value);
        return result;
    }

    @Override
    public boolean add(T element) {
        throw new UnsupportedOperationException(IMMUTABLE_COLLECTION);
    }

    @Override
    public boolean remove(Object object) {
        throw new UnsupportedOperationException(IMMUTABLE_COLLECTION);
    }

    @Override
    public boolean containsAll(Collection<?> collection) {
        return collection.size() == 1 && collection.contains(value);
    }

    @Override
    public boolean addAll(Collection<? extends T> collection) {
        throw new UnsupportedOperationException(IMMUTABLE_COLLECTION);
    }

    @Override
    public boolean addAll(int index, Collection<? extends T> collection) {
        throw new UnsupportedOperationException(IMMUTABLE_COLLECTION);
    }

    @Override
    public boolean removeAll(Collection<?> collection) {
        throw new UnsupportedOperationException(IMMUTABLE_COLLECTION);
    }

    @Override
    public boolean retainAll(Collection<?> collection) {
        throw new UnsupportedOperationException(IMMUTABLE_COLLECTION);
    }

    @Override
    public void clear() {
        throw new UnsupportedOperationException(IMMUTABLE_COLLECTION);
    }

    @Override
    public T get(int index) {
        if(index != 0) {
            throw new IndexOutOfBoundsException("Index out of bound: " + index + " size=1");
        } else {
            return value;
        }
    }



    @Override
    public T set(int index, T element) {
        if(index != 0) {
            throw new IndexOutOfBoundsException("Index out of bound: " + index + " size=1");
        } else {
            T prev = value;
            value = element;
            return prev;
        }
    }

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

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

    @Override
    public int indexOf(Object object) {
        return ValueUtils.equals(value, object) ? 0 : -1;
    }

    @Override
    public int lastIndexOf(Object object) {
        return ValueUtils.equals(value, object) ? 0 : -1;
    }

    @Override
    public ListIterator<T> listIterator() {
        return new SingleIterator<>(value);
    }

    @Override
    public ListIterator<T> listIterator(int index) {
        if(index != 0) {
            throw new IndexOutOfBoundsException("Index out of bound: " + index + " size=1");
        }
        return new SingleIterator<>(value);
    }

    @Override
    public List<T> subList(int fromIndex, int toIndex) {
        if(fromIndex>1 || toIndex>1) {
            throw new IndexOutOfBoundsException("subList out of bounds: " + fromIndex + ":" + toIndex + " (size=1)");
        }
        if(fromIndex == toIndex) {
            return Collections.emptyList();
        } else {
            return this;
        }
    }

    /**
     * Wartość przechowywana w kolekcji
     * @return
     */
    protected T value() {
        return value;
    }

    @Override
    public int hashCode() {
        return HashHelper.hashValues(3, 97, value);
    }

    @Override
    public boolean equals(Object obj) {
        if( !(obj instanceof Collection<?>) ) {
            return false;
        }
        Collection<?> other = (Collection<?>) obj;
        return 1==other.size() && ValueUtils.equals(value, other.iterator().next());
    }

}
