package aalg.algebra;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Implementation of vector that stores all elements.
 * It is good to use it when zero elements are rare.
 * @author Ing. Petr Ležák
 *
 * @param <F> field element
 */
public class DenseVector<F extends IElement> extends VectorImpl<F> {
	
	private final List<F> elements;
	
	
	private class FullIterator implements IElementIterator<F> {
		
		private int index = 0;
		
		@Override
		public boolean isValid() {
			return index < getSize();
		}
		
		@Override
		public void next() {
			index++;
		}

		@Override
		public int getIndex() {
			return index;
		}

		@Override
		public F getValue() {
			return elements.get(index);
		}
		
	}
	
	private class NonzeroIterator implements IElementIterator<F> {
		
		private int index;
		
		public NonzeroIterator() {
			index = 0;
			skipZeros();
		}

		private void skipZeros() {
			final F zero = getField().getAdditiveIdentity();
			
			while (index < getSize() && elements.get(index).equals(zero))
				index++;
		}
		
		@Override
		public boolean isValid() {
			return index < getSize();
		}
		
		@Override
		public void next() {
			index++;
			skipZeros();
		}

		@Override
		public int getIndex() {
			return index;
		}

		@Override
		public F getValue() {
			return elements.get(index);
		}
		
	}


	public DenseVector (final IField<F> field, final int size) {
		super (field);
		
		this.elements = new ArrayList<F>(size);
		
		final F zero = field.getAdditiveIdentity();
		
		for (int i = 0; i < size; i++)
			this.elements.add(zero);		
	}

	@Override
	public void fill (final F element) {
		Collections.fill(elements, element);
	}
	
	/**
	 * Returns number of elements.
	 * @return number of elements
	 */
	public int getSize() {
		return elements.size();
	}
	
	/**
	 * Returns field on given index.
	 * @param index index of element
	 * @return field on given index
	 */
	public F get (final int index) {
		return elements.get(index);
	}
	
	/**
	 * Sets field on given index.
	 * @param index field on given index
	 * @param element new element value
	 */
	public void set (final int index, final F element) {
		elements.set(index, element);
	}
	
	@Override
	public void assign (final IVector<F> orig) {
		final int size = getSize();
		
		if (size != orig.getSize())
			throw new RuntimeException("Different sizes of vectors");
		
		for (IElementIterator<F> it = orig.getFullIterator(); it.isValid(); it.next())
			this.set(it.getIndex(), it.getValue());
	}
	
	@Override
	public DenseVector<F> copy() {
		final IField<F> field = getField();
		final int size = getSize();

		final DenseVector<F> cp = new DenseVector<F>(field, size);
		cp.assign(this);
		
		return cp;
	}

	/**
	 * Returns iterator that iterates through nonzero elements.
	 * @return iterator
	 */
	@Override
	public IElementIterator<F> getNonzeroIterator() {
		return new NonzeroIterator();
	}

	/**
	 * Returns iterator that iterates through nonzero elements.
	 * @return iterator
	 */
	@Override
	public IElementIterator<F> getFullIterator() {
		return new FullIterator();
	}
	
	public static <T extends IElement, U extends IElement, V extends IElement> IVector<T> applyUnaryFunctionDense (final UnaryFunction<T, U> func, final IField<T> field, final IVector<U> vectorU) {
		final int size = vectorU.getSize();
		final DenseVector<T> result = new DenseVector<T>(field, size);
		
		for (int i = 0; i < size; i++)
			result.set(i, func.apply(vectorU.get(i)));
		
		return result;
	}

	public static <T extends IElement, U extends IElement, V extends IElement> DenseVector<T> applyBinaryFunctionDense (final BinaryFunction<T, U, V> func, final IField<T> field, final IVector<U> vectorU, final IVector<V> vectorV) {
		final int size = vectorU.getSize();
		
		if (vectorV.getSize() != size)
			throw new RuntimeException("Different sizes of vectors");
		
		final DenseVector<T> result = new DenseVector<T>(field, size);
		
		for (int i = 0; i < size; i++)
			result.set(i, func.apply(vectorU.get(i), vectorV.get(i)));
		
		return result;
	}
	
}
