/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.math.linear;

import java.io.Serializable;
import java.lang.reflect.Array;

import org.apache.commons.math.Field;
import org.apache.commons.math.FieldElement;
import org.apache.commons.math.MathRuntimeException;
import org.apache.commons.math.util.OpenIntToFieldHashMap;

/**
 * This class implements the {@link FieldVector} interface with a
 * {@link OpenIntToFieldHashMap} backing store.
 * 
 * @param <T>
 *            the type of the field elements
 * @version $Revision: 728186 $ $Date: 2009-04-25 12:39:38 -0700 (Sat, 25 Apr
 *          2009) $
 * @since 2.0
 */
public class SparseFieldVector<T extends FieldElement<T>> implements FieldVector<T>, Serializable {

	/**
	 * Serial version id
	 */
	private static final long serialVersionUID = 7841233292190413362L;
	/** Field to which the elements belong. */
	private final Field<T> field;
	/** Entries of the vector. */
	private final OpenIntToFieldHashMap<T> entries;
	/** Dimension of the vector. */
	private final int virtualSize;

	/**
	 * Build a 0-length vector.
	 * <p>
	 * Zero-length vectors may be used to initialize construction of vectors by
	 * data gathering. We start with zero-length and use either the
	 * {@link #SparseFieldVector(SparseFieldVector, int)} constructor or one of
	 * the <code>append</code> method ({@link #append(FieldElement)},
	 * {@link #append(FieldElement[])}, {@link #append(FieldVector)},
	 * {@link #append(SparseFieldVector)}) to gather data into this vector.
	 * </p>
	 * 
	 * @param field
	 *            field to which the elements belong
	 */
	public SparseFieldVector(Field<T> field) {
		this(field, 0);
	}

	/**
	 * Construct a (dimension)-length vector of zeros.
	 * 
	 * @param field
	 *            field to which the elements belong
	 * @param dimension
	 *            Size of the vector
	 */
	public SparseFieldVector(Field<T> field, int dimension) {
		this.field = field;
		virtualSize = dimension;
		entries = new OpenIntToFieldHashMap<T>(field);
	}

	/**
	 * Build a resized vector, for use with append.
	 * 
	 * @param v
	 *            The original vector
	 * @param resize
	 *            The amount to resize it
	 */
	protected SparseFieldVector(SparseFieldVector<T> v, int resize) {
		field = v.field;
		virtualSize = v.getDimension() + resize;
		entries = new OpenIntToFieldHashMap<T>(v.entries);
	}

	/**
	 * Build a vector with known the sparseness (for advanced use only).
	 * 
	 * @param field
	 *            field to which the elements belong
	 * @param dimension
	 *            The size of the vector
	 * @param expectedSize
	 *            The expected number of non-zero entries
	 */
	public SparseFieldVector(Field<T> field, int dimension, int expectedSize) {
		this.field = field;
		virtualSize = dimension;
		entries = new OpenIntToFieldHashMap<T>(field, expectedSize);
	}

	/**
	 * Create from a Field array. Only non-zero entries will be stored
	 * 
	 * @param field
	 *            field to which the elements belong
	 * @param values
	 *            The set of values to create from
	 */
	public SparseFieldVector(Field<T> field, T[] values) {
		this.field = field;
		virtualSize = values.length;
		entries = new OpenIntToFieldHashMap<T>(field);
		for (int key = 0; key < values.length; key++) {
			T value = values[key];
			entries.put(key, value);
		}
	}

	/**
	 * Copy constructor.
	 * 
	 * @param v
	 *            The instance to copy from
	 */
	public SparseFieldVector(SparseFieldVector<T> v) {
		field = v.field;
		virtualSize = v.getDimension();
		entries = new OpenIntToFieldHashMap<T>(v.getEntries());
	}

	/**
	 * Get the entries of this instance.
	 * 
	 * @return entries of this instance
	 */
	private OpenIntToFieldHashMap<T> getEntries() {
		return entries;
	}

	/**
	 * Optimized method to add sparse vectors.
	 * 
	 * @param v
	 *            vector to add
	 * @return The sum of <code>this</code> and <code>v</code>
	 * @throws IllegalArgumentException
	 *             If the dimensions don't match
	 */
	public FieldVector<T> add(SparseFieldVector<T> v) throws IllegalArgumentException {
		checkVectorDimensions(v.getDimension());
		SparseFieldVector<T> res = (SparseFieldVector<T>) copy();
		OpenIntToFieldHashMap<T>.Iterator iter = v.getEntries().iterator();
		while (iter.hasNext()) {
			iter.advance();
			int key = iter.key();
			T value = iter.value();
			if (entries.containsKey(key)) {
				res.setEntry(key, entries.get(key).add(value));
			} else {
				res.setEntry(key, value);
			}
		}
		return res;

	}

	/** {@inheritDoc} */
	public FieldVector<T> add(T[] v) throws IllegalArgumentException {
		checkVectorDimensions(v.length);
		SparseFieldVector<T> res = new SparseFieldVector<T>(field, getDimension());
		for (int i = 0; i < v.length; i++) {
			res.setEntry(i, v[i].add(getEntry(i)));
		}
		return res;
	}

	/**
	 * Construct a vector by appending a vector to this vector.
	 * 
	 * @param v
	 *            vector to append to this one.
	 * @return a new vector
	 */
	public FieldVector<T> append(SparseFieldVector<T> v) {
		SparseFieldVector<T> res = new SparseFieldVector<T>(this, v.getDimension());
		OpenIntToFieldHashMap<T>.Iterator iter = v.entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			res.setEntry(iter.key() + virtualSize, iter.value());
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldVector<T> append(FieldVector<T> v) {
		if (v instanceof SparseFieldVector<?>) {
			return append((SparseFieldVector<T>) v);
		} else {
			return append(v.toArray());
		}
	}

	/** {@inheritDoc} */
	public FieldVector<T> append(T d) {
		FieldVector<T> res = new SparseFieldVector<T>(this, 1);
		res.setEntry(virtualSize, d);
		return res;
	}

	/** {@inheritDoc} */
	public FieldVector<T> append(T[] a) {
		FieldVector<T> res = new SparseFieldVector<T>(this, a.length);
		for (int i = 0; i < a.length; i++) {
			res.setEntry(i + virtualSize, a[i]);
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldVector<T> copy() {
		return new SparseFieldVector<T>(this);
	}

	/** {@inheritDoc} */
	public T dotProduct(FieldVector<T> v) throws IllegalArgumentException {
		checkVectorDimensions(v.getDimension());
		T res = field.getZero();
		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			res = res.add(v.getEntry(iter.key()).multiply(iter.value()));
		}
		return res;
	}

	/** {@inheritDoc} */
	public T dotProduct(T[] v) throws IllegalArgumentException {
		checkVectorDimensions(v.length);
		T res = field.getZero();
		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			int idx = iter.key();
			T value = field.getZero();
			if (idx < v.length) {
				value = v[idx];
			}
			res = res.add(value.multiply(iter.value()));
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldVector<T> ebeDivide(FieldVector<T> v) throws IllegalArgumentException {
		checkVectorDimensions(v.getDimension());
		SparseFieldVector<T> res = new SparseFieldVector<T>(this);
		OpenIntToFieldHashMap<T>.Iterator iter = res.entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			res.setEntry(iter.key(), iter.value().divide(v.getEntry(iter.key())));
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldVector<T> ebeDivide(T[] v) throws IllegalArgumentException {
		checkVectorDimensions(v.length);
		SparseFieldVector<T> res = new SparseFieldVector<T>(this);
		OpenIntToFieldHashMap<T>.Iterator iter = res.entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			res.setEntry(iter.key(), iter.value().divide(v[iter.key()]));
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldVector<T> ebeMultiply(FieldVector<T> v) throws IllegalArgumentException {
		checkVectorDimensions(v.getDimension());
		SparseFieldVector<T> res = new SparseFieldVector<T>(this);
		OpenIntToFieldHashMap<T>.Iterator iter = res.entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			res.setEntry(iter.key(), iter.value().multiply(v.getEntry(iter.key())));
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldVector<T> ebeMultiply(T[] v) throws IllegalArgumentException {
		checkVectorDimensions(v.length);
		SparseFieldVector<T> res = new SparseFieldVector<T>(this);
		OpenIntToFieldHashMap<T>.Iterator iter = res.entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			res.setEntry(iter.key(), iter.value().multiply(v[iter.key()]));
		}
		return res;
	}

	/** {@inheritDoc} */
	public T[] getData() {
		T[] res = buildArray(virtualSize);
		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			res[iter.key()] = iter.value();
		}
		return res;
	}

	/** {@inheritDoc} */
	public int getDimension() {
		return virtualSize;
	}

	/** {@inheritDoc} */
	public T getEntry(int index) throws MatrixIndexException {
		checkIndex(index);
		return entries.get(index);
	}

	/** {@inheritDoc} */
	public Field<T> getField() {
		return field;
	}

	/** {@inheritDoc} */
	public FieldVector<T> getSubVector(int index, int n) throws MatrixIndexException {
		checkIndex(index);
		checkIndex(index + n - 1);
		SparseFieldVector<T> res = new SparseFieldVector<T>(field, n);
		int end = index + n;
		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			int key = iter.key();
			if (key >= index && key < end) {
				res.setEntry(key - index, iter.value());
			}
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapAdd(T d) {
		return copy().mapAddToSelf(d);
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapAddToSelf(T d) {
		for (int i = 0; i < virtualSize; i++) {
			setEntry(i, getEntry(i).add(d));
		}
		return this;
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapDivide(T d) {
		return copy().mapDivideToSelf(d);
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapDivideToSelf(T d) {
		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			entries.put(iter.key(), iter.value().divide(d));
		}
		return this;
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapInv() {
		return copy().mapInvToSelf();
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapInvToSelf() {
		for (int i = 0; i < virtualSize; i++) {
			setEntry(i, field.getOne().divide(getEntry(i)));
		}
		return this;
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapMultiply(T d) {
		return copy().mapMultiplyToSelf(d);
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapMultiplyToSelf(T d) {
		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			entries.put(iter.key(), iter.value().multiply(d));
		}
		return this;
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapSubtract(T d) {
		return copy().mapSubtractToSelf(d);
	}

	/** {@inheritDoc} */
	public FieldVector<T> mapSubtractToSelf(T d) {
		return mapAddToSelf(field.getZero().subtract(d));
	}

	/**
	 * Optimized method to compute outer product when both vectors are sparse.
	 * 
	 * @param v
	 *            vector with which outer product should be computed
	 * @return the square matrix outer product between instance and v
	 * @throws IllegalArgumentException
	 *             if v is not the same size as {@code this}
	 */
	public FieldMatrix<T> outerProduct(SparseFieldVector<T> v) throws IllegalArgumentException {
		checkVectorDimensions(v.getDimension());
		SparseFieldMatrix<T> res = new SparseFieldMatrix<T>(field, virtualSize, virtualSize);
		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			OpenIntToFieldHashMap<T>.Iterator iter2 = v.entries.iterator();
			while (iter2.hasNext()) {
				iter2.advance();
				res.setEntry(iter.key(), iter2.key(), iter.value().multiply(iter2.value()));
			}
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldMatrix<T> outerProduct(T[] v) throws IllegalArgumentException {
		checkVectorDimensions(v.length);
		FieldMatrix<T> res = new SparseFieldMatrix<T>(field, virtualSize, virtualSize);
		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			int row = iter.key();
			FieldElement<T> value = iter.value();
			for (int col = 0; col < virtualSize; col++) {
				res.setEntry(row, col, value.multiply(v[col]));
			}
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldMatrix<T> outerProduct(FieldVector<T> v) throws IllegalArgumentException {
		if (v instanceof SparseFieldVector<?>)
			return outerProduct((SparseFieldVector<T>) v);
		else
			return outerProduct(v.toArray());
	}

	/** {@inheritDoc} */
	public FieldVector<T> projection(FieldVector<T> v) throws IllegalArgumentException {
		checkVectorDimensions(v.getDimension());
		return v.mapMultiply(dotProduct(v).divide(v.dotProduct(v)));
	}

	/** {@inheritDoc} */
	public FieldVector<T> projection(T[] v) throws IllegalArgumentException {
		checkVectorDimensions(v.length);
		return projection(new SparseFieldVector<T>(field, v));
	}

	/** {@inheritDoc} */
	public void set(T value) {
		for (int i = 0; i < virtualSize; i++) {
			setEntry(i, value);
		}
	}

	/** {@inheritDoc} */
	public void setEntry(int index, T value) throws MatrixIndexException {
		checkIndex(index);
		entries.put(index, value);
	}

	/** {@inheritDoc} */
	public void setSubVector(int index, FieldVector<T> v) throws MatrixIndexException {
		checkIndex(index);
		checkIndex(index + v.getDimension() - 1);
		setSubVector(index, v.getData());
	}

	/** {@inheritDoc} */
	public void setSubVector(int index, T[] v) throws MatrixIndexException {
		checkIndex(index);
		checkIndex(index + v.length - 1);
		for (int i = 0; i < v.length; i++) {
			setEntry(i + index, v[i]);
		}

	}

	/**
	 * Optimized method to subtract SparseRealVectors.
	 * 
	 * @param v
	 *            The vector to subtract from <code>this</code>
	 * @return The difference of <code>this</code> and <code>v</code>
	 * @throws IllegalArgumentException
	 *             If the dimensions don't match
	 */
	public SparseFieldVector<T> subtract(SparseFieldVector<T> v) throws IllegalArgumentException {
		checkVectorDimensions(v.getDimension());
		SparseFieldVector<T> res = (SparseFieldVector<T>) copy();
		OpenIntToFieldHashMap<T>.Iterator iter = v.getEntries().iterator();
		while (iter.hasNext()) {
			iter.advance();
			int key = iter.key();
			if (entries.containsKey(key)) {
				res.setEntry(key, entries.get(key).subtract(iter.value()));
			} else {
				res.setEntry(key, field.getZero().subtract(iter.value()));
			}
		}
		return res;
	}

	/** {@inheritDoc} */
	public FieldVector<T> subtract(FieldVector<T> v) throws IllegalArgumentException {
		if (v instanceof SparseFieldVector<?>)
			return subtract((SparseFieldVector<T>) v);
		else
			return subtract(v.toArray());
	}

	/** {@inheritDoc} */
	public FieldVector<T> subtract(T[] v) throws IllegalArgumentException {
		checkVectorDimensions(v.length);
		SparseFieldVector<T> res = new SparseFieldVector<T>(this);
		for (int i = 0; i < v.length; i++) {
			if (entries.containsKey(i)) {
				res.setEntry(i, entries.get(i).subtract(v[i]));
			} else {
				res.setEntry(i, field.getZero().subtract(v[i]));
			}
		}
		return res;
	}

	/** {@inheritDoc} */
	public T[] toArray() {
		return getData();
	}

	/**
	 * Check if an index is valid.
	 *
	 * @param index
	 *            index to check
	 * @exception MatrixIndexException
	 *                if index is not valid
	 */
	private void checkIndex(final int index) throws MatrixIndexException {
		if (index < 0 || index >= getDimension()) {
			throw new MatrixIndexException("index {0} out of allowed range [{1}, {2}]", index, 0, getDimension() - 1);
		}
	}

	/**
	 * Check if instance dimension is equal to some expected value.
	 *
	 * @param n
	 *            expected dimension.
	 * @exception IllegalArgumentException
	 *                if the dimension is inconsistent with vector size
	 */
	protected void checkVectorDimensions(int n) throws IllegalArgumentException {
		if (getDimension() != n) {
			throw MathRuntimeException.createIllegalArgumentException(
					"vector length mismatch: got {0} but expected {1}", getDimension(), n);
		}
	}

	/** {@inheritDoc} */
	public FieldVector<T> add(FieldVector<T> v) throws IllegalArgumentException {
		if (v instanceof SparseFieldVector<?>) {
			return add((SparseFieldVector<T>) v);
		} else {
			return add(v.toArray());
		}
	}

	/**
	 * Build an array of elements.
	 * 
	 * @param length
	 *            size of the array to build
	 * @return a new array
	 */
	@SuppressWarnings("unchecked")
	private T[] buildArray(final int length) {
		return (T[]) Array.newInstance(field.getZero().getClass(), length);
	}

	/** {@inheritDoc} */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((field == null) ? 0 : field.hashCode());
		result = prime * result + virtualSize;
		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			int temp = iter.value().hashCode();
			result = prime * result + temp;
		}
		return result;
	}

	/** {@inheritDoc} */
	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object obj) {

		if (this == obj) {
			return true;
		}

		if (obj == null) {
			return false;
		}

		if (!(obj instanceof SparseFieldVector)) {
			return false;
		}

		SparseFieldVector<T> other = (SparseFieldVector<T>) obj;
		if (field == null) {
			if (other.field != null) {
				return false;
			}
		} else if (!field.equals(other.field)) {
			return false;
		}
		if (virtualSize != other.virtualSize) {
			return false;
		}

		OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
		while (iter.hasNext()) {
			iter.advance();
			T test = other.getEntry(iter.key());
			if (!test.equals(iter.value())) {
				return false;
			}
		}
		iter = other.getEntries().iterator();
		while (iter.hasNext()) {
			iter.advance();
			T test = iter.value();
			if (!test.equals(getEntry(iter.key()))) {
				return false;
			}
		}
		return true;
	}

}
