package aalg.algebra;


/**
 * One dimensional vector of field elements.
 * @author Ing. Petr Ležák
 *
 * @param <F> type of field element
 */
public abstract class VectorImpl<F extends IElement> implements IVector<F> {
	
	private final IField<F> field;
	
	/**
	 * Creates vector.
	 * @param field underlying field
	 */
	public VectorImpl(final IField<F> field) {
		this.field = field;
	}

	/**
	 * Returns underlying field.
	 * @return field
	 */
	public IField<F> getField() {
		return field;
	}
	
	@Override
	public IVector<F> add (final IVector<F> x) {
		final BinaryFunction<F, F, F> func = new BinaryFunction<F, F, F>() {
			@Override
			public F apply(final F u, final F v) {
				return field.add(u, v);
			}
		};
		
		return applyBinaryFunction(func, field, this, x, true);
	}
	
	@Override
	public IVector<F> subtract (final IVector<F> x) {
		final BinaryFunction<F, F, F> func = new BinaryFunction<F, F, F>() {
			@Override
			public F apply(final F u, final F v) {
				return field.subtract(u, v);
			}
		};
		
		return applyBinaryFunction(func, field, this, x, true);
	}
	
	@Override
	public IVector<F> multiply (final F c) {
		final UnaryFunction<F, F> func = new UnaryFunction<F, F>() {
			@Override
			public F apply(final F u) {
				return field.multiply (u, c);
			}
		};
		
		return applyUnaryFunction(func, field, this, true);
	}
	
	/**
	 * Applies binary function to corresponding elements of the vectors.
	 * @param func applied function
	 * @param field field of the result
	 * @param vectorU input vector
	 * @param zeroStable true of func is zero stable: func(0) = 0
	 * @return output vector
	 */
	public static <T extends IElement, U extends IElement, V extends IElement> IVector<T> applyUnaryFunction (final UnaryFunction<T, U> func, final IField<T> field, final IVector<U> vectorU, final boolean zeroStable) {
		if (zeroStable && vectorU instanceof SparseVector)
			return SparseVector.applyUnaryFunctionSparse (func, field, vectorU);
		else
			return DenseVector.applyUnaryFunctionDense (func, field, vectorU);
	}
	
	/**
	 * Applies binary function to corresponding elements of the vectors.
	 * @param func applied function
	 * @param field field of the result
	 * @param vectorU vectorU input vector
	 * @param vectorV vectorV input vector
	 * @param zeroStable true of func is zero stable: func(0, 0) = 0
	 * @return output vector
	 */
	public static <T extends IElement, U extends IElement, V extends IElement> IVector<T> applyBinaryFunction (final BinaryFunction<T, U, V> func, final IField<T> field, final IVector<U> vectorU, final IVector<V> vectorV, final boolean zeroStable) {
		if (zeroStable && (vectorU instanceof SparseVector || vectorV instanceof SparseVector))
			return SparseVector.applyBinaryFunctionSparse (func, field, vectorU, vectorV);
		else
			return DenseVector.applyBinaryFunctionDense (func, field, vectorU, vectorV);
	}

	@Override
	public boolean equals (final Object obj) {
		if (!(obj instanceof VectorImpl<?>))
			return false;
		
		final IVector<?> cmp = (IVector<?>) obj;
		final int size = getSize();
		
		if (cmp.getSize() != size)
			return false;
		
		IElementIterator<?> it1 = this.getFullIterator();
		IElementIterator<?> it2 = cmp.getFullIterator();
		
		while (it1.isValid()) {
			final IElement val1 = it1.getValue();
			final IElement val2 = it2.getValue();
			
			if (!val1.equals(val2))
				return false;
			
			it1.next();
			it2.next();
		}
		
		return true;
	}
	
	@Override
	public int hashCode() {
		int code = 0;
		
		for (IElementIterator<?> it = this.getFullIterator(); it.isValid(); it.next())
			code = Integer.rotateLeft(code, 1) ^ it.getValue().hashCode();
		
		return code;
	}
	
	@Override
	public String toString() {
		final StringBuilder builder = new StringBuilder();
		builder.append ("[");
		
		for (int i = 0; i < getSize(); i++) {
			if (i > 0)
				builder.append(", ");
			
			builder.append(get(i));
		}
		
		builder.append ("]");
		
		return builder.toString();
	}
	
	@Override
	public void swapElements (final int index1, final int index2) {
		if (index1 != index2) {
			final F element1 = this.get(index1);
			final F element2 = this.get(index2);
			
			this.set(index1, element2);
			this.set(index2, element1);
		}
	}
	
	@Override
	public <T extends IElement> IVector<T> transform (final IFieldTransformation<F, T> transformation) throws TransformationException {
		final int size = getSize();
		final VectorImpl<T> result = new DenseVector<T> (transformation.getOutputField(), size);
		
		for (int i = 0; i < size; i++) {
			result.set (i, transformation.transformElement(this.get(i)));
		}
		
		return result;
	}
}
