package aalg.algebra;

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

/**
 * Implementation of vector that stores only nonzero elements.
 * @author Ing. Petr Ležák
 *
 * @param <F> field element
 */
public class SparseVector<F extends IElement> extends VectorImpl<F> {
	
	/**
	 * Iterator that iterates through nonzero elements.
	 * @author Ing. Petr Ležák
	 */
	private class NonzeroIterator implements IElementIterator<F> {
		
		private int recordIndex;
		
		@Override
		public boolean isValid() {
			return recordIndex < records.size();
		}

		@Override
		public void next() {
			recordIndex++;
		}

		@Override
		public int getIndex() {
			return records.get(recordIndex).index;
		}

		@Override
		public F getValue() {
			return records.get(recordIndex).value;
		}
	}
	
	/**
	 * Iterator that iterates through all elements.
	 * @author Ing. Petr Ležák
	 */
	private class FullIterator implements IElementIterator<F> {
		
		// Index of the current element
		private int elementIndex;
		
		// Index of the first record with records.get(recordIndex).index >= elementIndex
		private int recordIndex;
		
		@Override
		public boolean isValid() {
			return elementIndex < size;
		}

		@Override
		public void next() {
			elementIndex++;
			
			while (recordIndex < records.size() && records.get(recordIndex).index < elementIndex)
				recordIndex++;
		}

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

		@Override
		public F getValue() {
			if (recordIndex < records.size()) {
				final Record<F> record = records.get(recordIndex);
				
				if (record.index == elementIndex)
					return record.value;
			}
			
			return zero;
		}
	}
	
	/**
	 * Record of nonzero element.
	 * @author Ing. Petr Ležák
	 *
	 * @param <T>
	 */
	private static class Record<T extends IElement> {
		public int index;   // Index of the element
		public T value;   // Value of the element
	}
	
	private final List<Record<F>> records;
	private final int size;
	private final F zero;
	
	/**
	 * Creates sparse vector.
	 * @param field underlying field
	 * @param size size of the vector
	 */
	public SparseVector (final IField<F> field, final int size) {
		super (field);
		
		this.records = new ArrayList<Record<F>>();
		this.size = size;
		this.zero = field.getAdditiveIdentity();
	}

	@Override
	public int getSize() {
		return size;
	}
	
	/**
	 * Finds first recordIndex with records.get(recordIndex).index >= elementIndex.
	 * If all records have index < elementIndex returns records.size().
	 * In other words returns index of record with given elementIndex if it exists or
	 * index where it should be inserted if it does not exists.
	 * Algorithm uses binary search.
	 * @param elementIndex index of the element
	 * @return index of the record
	 */
	private int getRecordIndex(final int elementIndex) {
		int low = 0;
		int high = records.size();
		
		if (high == 0)
			return 0;   // Singular case - empty list of records
		
		// Interval <low; high) contains required record.
		while (high - low > 1) {
			final int mid = (low + high) / 2;
			
			if (elementIndex >= records.get(mid).index)
				low = mid;
			else
				high = mid;
		}
		
		// Now low points to the record with elementIndex or (if there is no such record)
		// to record before position where it should be inserted.
		if (elementIndex <= records.get(low).index)
			return low;   // Record found
		else
			return low+1;   // Record not found -> return next position where record should be inserted
	}

	@Override
	public F get(final int index) {
		final int recordIndex = getRecordIndex(index);
		
		if (recordIndex < records.size()) {
			final Record<F> record = records.get(recordIndex);
			
			if (record.index == index)
				return record.value;
		}
		
		return zero;
	}

	@Override
	public void set(final int index, final F element) {
		final int recordIndex = getRecordIndex(index);
		
		if (recordIndex < records.size()) {
			final Record<F> record = records.get(recordIndex);
			
			if (record.index == index) {
				if (element.equals(zero))
					records.remove(recordIndex);
				else
					record.value = element;
				
				return;
			}
		}
		
		final Record<F> record = new Record<F>();
		record.index = index;
		record.value = element;
		
		records.add(recordIndex, record);
	}

	@Override
	public IElementIterator<F> getNonzeroIterator() {
		return new NonzeroIterator();
	}

	@Override
	public IElementIterator<F> getFullIterator() {
		return new FullIterator();
	}

	@Override
	public void fill(final F element) {
		records.clear();
		
		if (!element.equals(zero)) {
			for (int i = 0; i < size; i++) {
				final Record<F> record = new Record<F>();
				record.index = i;
				record.value = element;
				
				records.add(record);
			}
		}
	}

	@Override
	public void assign(final IVector<F> orig) {
		records.clear();

		for (IElementIterator<F> it = orig.getNonzeroIterator(); it.isValid(); it.next()) {
			final Record<F> record = new Record<F>();
			record.index = it.getIndex();
			record.value = it.getValue();
			
			records.add(record);
		}
	}

	@Override
	public IVector<F> copy() {
		final SparseVector<F> result = new SparseVector<F>(getField(), size);
		result.assign(this);
		
		return result;
	}

	/**
	 * Adds element to the end.
	 * @param index index of the element
	 * @param element value of the element
	 */
	private void addLastElement (final int index, final F element) {
		if (!element.equals(zero)) {
			final Record<F> record = new Record<F>();
			record.index = index;
			record.value = element;
		
			records.add(record);
		}
	}
	
	/**
	 * Applies unary function to each element of the vector.
	 * Function is optimized for sparse vectors. It is expected that func is
	 * zero stable: func(0) = 0.
	 * @param func applied function
	 * @param field field of the result
	 * @param vectorU input vector
	 * @return output vector
	 */
	public static <T extends IElement, U extends IElement, V extends IElement> IVector<T> applyUnaryFunctionSparse (final UnaryFunction<T, U> func, final IField<T> field, final IVector<U> vectorU) {
		final int size = vectorU.getSize();
		final SparseVector<T> result = new SparseVector<T>(field, size);
		
		for (IElementIterator<U> it = vectorU.getNonzeroIterator(); it.isValid(); it.next()) {
			result.addLastElement (it.getIndex(), func.apply(it.getValue()));
		}
		
		return result;
	}

	/**
	 * Applies binary function to corresponding elements of the vectors.
	 * Function is optimized for sparse vectors. It is expected that func is
	 * zero stable: func(0, 0) = 0.
	 * @param func applied function
	 * @param field field of the result
	 * @param vectorU input vector
	 * @param vectorV input vector
	 * @return output vector
	 */
	public static <T extends IElement, U extends IElement, V extends IElement> SparseVector<T> applyBinaryFunctionSparse (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 SparseVector<T> result = new SparseVector<T>(field, size);
		
		final IElementIterator<U> itU = vectorU.getNonzeroIterator();
		final IElementIterator<V> itV = vectorV.getNonzeroIterator();
		
		final U zeroU = vectorU.getField().getAdditiveIdentity();
		final V zeroV = vectorV.getField().getAdditiveIdentity();

		while (itU.isValid() && itV.isValid()) {
			final int indexU = itU.getIndex();
			final int indexV = itV.getIndex();
			
			final int minIndex = Math.min(indexU, indexV);
			
			final U valueU;

			if (indexU == minIndex) {
				valueU = itU.getValue();
				itU.next();
			}
			else
				valueU = zeroU;

			final V valueV;

			if (indexV == minIndex) {
				valueV = itV.getValue();
				itV.next();
			}
			else
				valueV = zeroV;
			
			result.addLastElement (minIndex, func.apply(valueU, valueV));
		}

		while (itU.isValid()) {
			result.addLastElement (itU.getIndex(), func.apply(itU.getValue(), zeroV));
			itU.next();
		}

		while (itV.isValid()) {
			result.addLastElement (itV.getIndex(), func.apply(zeroU, itV.getValue()));
			itV.next();
		}
		
		return result;
	}

}
