/*
 * 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.util.Iterator;

import org.apache.commons.math.FunctionEvaluationException;
import org.apache.commons.math.MathRuntimeException;
import org.apache.commons.math.analysis.BinaryFunction;
import org.apache.commons.math.analysis.UnivariateRealFunction;
import org.apache.commons.math.analysis.ComposableFunction;

/**
 * This class provides default basic implementations for many methods in the
 * {@link RealVector} interface with.
 * @version $Revision$ $Date$
 * @since 2.1
 */
public abstract class AbstractRealVector implements RealVector {

    /**
     * Check if instance and specified vectors have the same dimension.
     * @param v vector to compare instance with
     * @exception IllegalArgumentException if the vectors do not
     * have the same dimension
     */
    protected void checkVectorDimensions(RealVector v) {
        checkVectorDimensions(v.getDimension());
    }

    /**
     * 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 {
        double d = getDimension();
        if (d != n) {
            throw MathRuntimeException.createIllegalArgumentException(
                  "vector length mismatch: got {0} but expected {1}",
                  d, n);
        }
    }

    /**
     * Check if an index is valid.
     * @param index index to check
     * @exception MatrixIndexException if index is not valid
     */
    protected 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);
        }
    }

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

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

    /** {@inheritDoc} */
    public RealVector add(double[] v) throws IllegalArgumentException {
        double[] result = v.clone();
        Iterator<Entry> it = sparseIterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            result[e.getIndex()] += e.getValue();
        }
        return new ArrayRealVector(result, false);
    }

    /** {@inheritDoc} */
    public RealVector add(RealVector v) throws IllegalArgumentException {
        if (v instanceof ArrayRealVector) {
            double[] values = ((ArrayRealVector)v).getDataRef();
            return add(values);
        }
        RealVector result = v.copy();
        Iterator<Entry> it = sparseIterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            final int index = e.getIndex();
            result.setEntry(index, e.getValue() + result.getEntry(index));
        }
        return result;
    }

    /** {@inheritDoc} */
    public RealVector subtract(double[] v) throws IllegalArgumentException {
        double[] result = v.clone();
        Iterator<Entry> it = sparseIterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            final int index = e.getIndex();
            result[index] = e.getValue() - result[index];
        }
        return new ArrayRealVector(result, false);
    }

    /** {@inheritDoc} */
    public RealVector subtract(RealVector v) throws IllegalArgumentException {
        if (v instanceof ArrayRealVector) {
            double[] values = ((ArrayRealVector)v).getDataRef();
            return add(values);
        }
        RealVector result = v.copy();
        Iterator<Entry> it = sparseIterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            final int index = e.getIndex();
            v.setEntry(index, e.getValue() - result.getEntry(index));
        }
        return result;
    }

    /** {@inheritDoc} */
    public RealVector mapAdd(double d) {
        return copy().mapAddToSelf(d);
    }

    /** {@inheritDoc} */
    public RealVector mapAddToSelf(double d) {
        if (d != 0) {
            try {
                return mapToSelf(BinaryFunction.ADD.fix1stArgument(d));
            } catch (FunctionEvaluationException e) {
                throw new IllegalArgumentException(e);
            }
        }
        return this;
    }

    /** {@inheritDoc} */
    public abstract AbstractRealVector copy();

    /** {@inheritDoc} */
    public double dotProduct(double[] v) throws IllegalArgumentException {
        return dotProduct(new ArrayRealVector(v, false));
    }

    /** {@inheritDoc} */
    public double dotProduct(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v);
        double d = 0;
        Iterator<Entry> it = sparseIterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            d += e.getValue() * v.getEntry(e.getIndex());
        }
        return d;
    }

    /** {@inheritDoc} */
    public RealVector ebeDivide(double[] v) throws IllegalArgumentException {
        return ebeDivide(new ArrayRealVector(v, false));
    }

    /** {@inheritDoc} */
    public RealVector ebeMultiply(double[] v) throws IllegalArgumentException {
        return ebeMultiply(new ArrayRealVector(v, false));
    }

    /** {@inheritDoc} */
    public double getDistance(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v);
        double d = 0;
        Iterator<Entry> it = iterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            final double diff = e.getValue() - v.getEntry(e.getIndex());
            d += diff * diff;
        }
        return Math.sqrt(d);
    }

    /** {@inheritDoc} */
    public double getNorm() {
        double sum = 0;
        Iterator<Entry> it = sparseIterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            final double value = e.getValue();
            sum += value * value;
        }
        return Math.sqrt(sum);
    }

    /** {@inheritDoc} */
    public double getL1Norm() {
        double norm = 0;
        Iterator<Entry> it = sparseIterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            norm += Math.abs(e.getValue());
        }
        return norm;
    }

    /** {@inheritDoc} */
    public double getLInfNorm() {
        double norm = 0;
        Iterator<Entry> it = sparseIterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            norm = Math.max(norm, Math.abs(e.getValue()));
        }
        return norm;
    }

    /** {@inheritDoc} */
    public double getDistance(double[] v) throws IllegalArgumentException {
        return getDistance(new ArrayRealVector(v,false));
    }

    /** {@inheritDoc} */
    public double getL1Distance(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v);
        double d = 0;
        Iterator<Entry> it = iterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            d += Math.abs(e.getValue() - v.getEntry(e.getIndex()));
        }
        return d;
    }

    /** {@inheritDoc} */
    public double getL1Distance(double[] v) throws IllegalArgumentException {
        checkVectorDimensions(v.length);
        double d = 0;
        Iterator<Entry> it = iterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            d += Math.abs(e.getValue() - v[e.getIndex()]);
        }
        return d;
    }

    /** {@inheritDoc} */
    public double getLInfDistance(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v);
        double d = 0;
        Iterator<Entry> it = iterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            d = Math.max(Math.abs(e.getValue() - v.getEntry(e.getIndex())), d);
        }
        return d;
    }

    /** {@inheritDoc} */
    public double getLInfDistance(double[] v) throws IllegalArgumentException {
        checkVectorDimensions(v.length);
        double d = 0;
        Iterator<Entry> it = iterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            d = Math.max(Math.abs(e.getValue() - v[e.getIndex()]), d);
        }
        return d;
    }

    /** {@inheritDoc} */
    public RealVector mapAbs() {
        return copy().mapAbsToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapAbsToSelf() {
        try {
            return mapToSelf(ComposableFunction.ABS);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapAcos() {
        return copy().mapAcosToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapAcosToSelf() {
        try {
            return mapToSelf(ComposableFunction.ACOS);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapAsin() {
        return copy().mapAsinToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapAsinToSelf() {
        try {
            return mapToSelf(ComposableFunction.ASIN);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapAtan() {
        return copy().mapAtanToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapAtanToSelf() {
        try {
            return mapToSelf(ComposableFunction.ATAN);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapCbrt() {
        return copy().mapCbrtToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapCbrtToSelf() {
        try {
            return mapToSelf(ComposableFunction.CBRT);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapCeil() {
        return copy().mapCeilToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapCeilToSelf() {
        try {
            return mapToSelf(ComposableFunction.CEIL);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapCos() {
        return copy().mapCosToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapCosToSelf() {
        try {
            return mapToSelf(ComposableFunction.COS);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapCosh() {
        return copy().mapCoshToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapCoshToSelf() {
        try {
            return mapToSelf(ComposableFunction.COSH);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapDivide(double d) {
        return copy().mapDivideToSelf(d);
    }

    /** {@inheritDoc} */
    public RealVector mapDivideToSelf(double d){
        try {
            return mapToSelf(BinaryFunction.DIVIDE.fix2ndArgument(d));
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapExp() {
        return copy().mapExpToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapExpToSelf() {
        try {
            return mapToSelf(ComposableFunction.EXP);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapExpm1() {
        return copy().mapExpm1ToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapExpm1ToSelf() {
        try {
            return mapToSelf(ComposableFunction.EXPM1);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapFloor() {
        return copy().mapFloorToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapFloorToSelf() {
        try {
            return mapToSelf(ComposableFunction.FLOOR);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapInv() {
        return copy().mapInvToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapInvToSelf() {
        try {
            return mapToSelf(ComposableFunction.INVERT);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapLog() {
        return copy().mapLogToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapLogToSelf() {
        try {
            return mapToSelf(ComposableFunction.LOG);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapLog10() {
        return copy().mapLog10ToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapLog10ToSelf() {
        try {
            return mapToSelf(ComposableFunction.LOG10);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapLog1p() {
        return copy().mapLog1pToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapLog1pToSelf() {
        try {
            return mapToSelf(ComposableFunction.LOG1P);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapMultiply(double d) {
        return copy().mapMultiplyToSelf(d);
    }

    /** {@inheritDoc} */
    public RealVector mapMultiplyToSelf(double d){
        try {
            return mapToSelf(BinaryFunction.MULTIPLY.fix1stArgument(d));
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapPow(double d) {
        return copy().mapPowToSelf(d);
    }

    /** {@inheritDoc} */
    public RealVector mapPowToSelf(double d){
        try {
            return mapToSelf(BinaryFunction.POW.fix2ndArgument(d));
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapRint() {
        return copy().mapRintToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapRintToSelf() {
        try {
            return mapToSelf(ComposableFunction.RINT);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapSignum() {
        return copy().mapSignumToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapSignumToSelf() {
        try {
            return mapToSelf(ComposableFunction.SIGNUM);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapSin() {
        return copy().mapSinToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapSinToSelf() {
        try {
            return mapToSelf(ComposableFunction.SIN);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapSinh() {
        return copy().mapSinhToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapSinhToSelf() {
        try {
            return mapToSelf(ComposableFunction.SINH);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapSqrt() {
        return copy().mapSqrtToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapSqrtToSelf() {
        try {
            return mapToSelf(ComposableFunction.SQRT);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapSubtract(double d) {
        return copy().mapSubtractToSelf(d);
    }

    /** {@inheritDoc} */
    public RealVector mapSubtractToSelf(double d){
        return mapAddToSelf(-d);
    }

    /** {@inheritDoc} */
    public RealVector mapTan() {
        return copy().mapTanToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapTanToSelf() {
        try {
            return mapToSelf(ComposableFunction.TAN);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapTanh() {
        return copy().mapTanhToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapTanhToSelf() {
        try {
            return mapToSelf(ComposableFunction.TANH);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealVector mapUlp() {
        return copy().mapUlpToSelf();
    }

    /** {@inheritDoc} */
    public RealVector mapUlpToSelf() {
        try {
            return mapToSelf(ComposableFunction.ULP);
        } catch (FunctionEvaluationException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /** {@inheritDoc} */
    public RealMatrix outerProduct(RealVector v) throws IllegalArgumentException {
        RealMatrix product;
        if (v instanceof SparseRealVector || this instanceof SparseRealVector) {
            product = new OpenMapRealMatrix(this.getDimension(), v.getDimension());
        } else {
            product = new Array2DRowRealMatrix(this.getDimension(), v.getDimension());
        }
        Iterator<Entry> thisIt = sparseIterator();
        Entry thisE = null;
        while (thisIt.hasNext() && (thisE = thisIt.next()) != null) {
            Iterator<Entry> otherIt = v.sparseIterator();
            Entry otherE = null;
            while (otherIt.hasNext() && (otherE = otherIt.next()) != null) {
                product.setEntry(thisE.getIndex(), otherE.getIndex(),
                                 thisE.getValue() * otherE.getValue());
            }
        }

        return product;

    }

    /** {@inheritDoc} */
    public RealMatrix outerProduct(double[] v) throws IllegalArgumentException {
        return outerProduct(new ArrayRealVector(v, false));
    }

    /** {@inheritDoc} */
    public RealVector projection(double[] v) throws IllegalArgumentException {
        return projection(new ArrayRealVector(v, false));
    }

    /** {@inheritDoc} */
    public void set(double value) {
        Iterator<Entry> it = iterator();
        Entry e = null;
        while (it.hasNext() && (e = it.next()) != null) {
            e.setValue(value);
        }
    }

    /** {@inheritDoc} */
    public double[] toArray() {
        int dim = getDimension();
        double[] values = new double[dim];
        for (int i = 0; i < dim; i++) {
            values[i] = getEntry(i);
        }
        return values;
    }

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

    /** {@inheritDoc} */
    public RealVector unitVector() {
        RealVector copy = copy();
        copy.unitize();
        return copy;
    }

    /** {@inheritDoc} */
    public void unitize() {
        mapDivideToSelf(getNorm());
    }

    /** {@inheritDoc} */
    public Iterator<Entry> sparseIterator() {
        return new SparseEntryIterator();
    }

    /** {@inheritDoc} */
    public Iterator<Entry> iterator() {
        final int dim = getDimension();
        return new Iterator<Entry>() {

            /** Current index. */
            private int i = 0;

            /** Current entry. */
            private EntryImpl e = new EntryImpl();

            /** {@inheritDoc} */
            public boolean hasNext() {
                return i < dim;
            }

            /** {@inheritDoc} */
            public Entry next() {
                e.setIndex(i++);
                return e;
            }

            /** {@inheritDoc} */
            public void remove() {
                throw new UnsupportedOperationException("Not supported");
            }
        };
    }

    /** {@inheritDoc} */
    public RealVector map(UnivariateRealFunction function) throws FunctionEvaluationException {
        return copy().mapToSelf(function);
    }

    /** {@inheritDoc} */
    public RealVector mapToSelf(UnivariateRealFunction function) throws FunctionEvaluationException {
        Iterator<Entry> it = (function.value(0) == 0) ? sparseIterator() : iterator();
        Entry e;
        while (it.hasNext() && (e = it.next()) != null) {
            e.setValue(function.value(e.getValue()));
        }
        return this;
    }

    /** An entry in the vector. */
    protected class EntryImpl extends Entry {

        /** Simple constructor. */
        public EntryImpl() {
            setIndex(0);
        }

        /** {@inheritDoc} */
        @Override
        public double getValue() {
            return getEntry(getIndex());
        }

        /** {@inheritDoc} */
        @Override
        public void setValue(double newValue) {
            setEntry(getIndex(), newValue);
        }
    }

    /**
     * This class should rare be used, but is here to provide
     * a default implementation of sparseIterator(), which is implemented
     * by walking over the entries, skipping those whose values are the default one.
     *
     * Concrete subclasses which are SparseVector implementations should
     * make their own sparse iterator, not use this one.
     *
     * This implementation might be useful for ArrayRealVector, when expensive
     * operations which preserve the default value are to be done on the entries,
     * and the fraction of non-default values is small (i.e. someone took a
     * SparseVector, and passed it into the copy-constructor of ArrayRealVector)
     */
    protected class SparseEntryIterator implements Iterator<Entry> {

        /** Dimension of the vector. */
        private final int dim;

        /** Temporary entry (reused on each call to {@link #next()}. */
        private EntryImpl tmp = new EntryImpl();

        /** Current entry. */
        private EntryImpl current;

        /** Next entry. */
        private EntryImpl next;

        /** Simple constructor. */
        protected SparseEntryIterator() {
            dim = getDimension();
            current = new EntryImpl();
            if (current.getValue() == 0) {
                advance(current);
            }
            if(current.getIndex() >= 0){
                // There is at least one non-zero entry
                next = new EntryImpl();
                next.setIndex(current.getIndex());
                advance(next);
            } else {
                // The vector consists of only zero entries, so deny having a next
                current = null;
            }
        }

        /** Advance an entry up to the next non null one.
         * @param e entry to advance
         */
        protected void advance(EntryImpl e) {
            if (e == null) {
                return;
            }
            do {
                e.setIndex(e.getIndex() + 1);
            } while (e.getIndex() < dim && e.getValue() == 0);
            if (e.getIndex() >= dim) {
                e.setIndex(-1);
            }
        }

        /** {@inheritDoc} */
        public boolean hasNext() {
            return current != null;
        }

        /** {@inheritDoc} */
        public Entry next() {
            tmp.setIndex(current.getIndex());
            if (next != null) {
                current.setIndex(next.getIndex());
                advance(next);
                if (next.getIndex() < 0) {
                    next = null;
                }
            } else {
                current = null;
            }
            return tmp;
        }

        /** {@inheritDoc} */
        public void remove() {
            throw new UnsupportedOperationException("Not supported");
        }
    }

}
