/*
 * Copyright 2011-2013, by Vladimir Kostyukov and Contributors.
 * 
 * This file is part of la4j project (http://la4j.org)
 * 
 * Licensed 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.
 * 
 * Contributor(s): Maxim Samoylov
 *                 Miron Aseev
 * 
 */

package org.la4j;

import java.math.BigDecimal;
import java.math.RoundingMode;

import org.la4j.vector.VectorFactory;
import org.la4j.vector.dense.BasicVector;
import org.la4j.vector.functor.VectorAccumulator;
import org.la4j.vector.functor.VectorFunction;
import org.la4j.vector.functor.VectorPredicate;
import org.la4j.vector.functor.VectorProcedure;
import org.la4j.vector.sparse.CompressedVector;

public final class Vectors {

    public static final double EPS = LinearAlgebra.EPS;
    public static final int ROUND_FACTOR = LinearAlgebra.ROUND_FACTOR;

    public static final VectorFactory<BasicVector> BASIC = new VectorFactory<BasicVector>() {
        @Override
        public BasicVector apply(int length) {
            return BasicVector.zero(length);
        }
    };

    public static final VectorFactory<CompressedVector> COMPRESSED = new VectorFactory<CompressedVector>() {
        @Override
        public CompressedVector apply(int length) {
            return CompressedVector.zero(length);
        }
    };

    public static final VectorFactory<?>[] FACTORIES = {
            BASIC, COMPRESSED
    };

    public static final VectorFactory<BasicVector> DENSE = BASIC;

    public static final VectorFactory<CompressedVector> SPARSE = COMPRESSED;

    /**
     * Checks whether the vector is a
     * <a href="http://mathworld.wolfram.com/ZeroMatrix.html">zero
     * vector</a>.
     */
    public static final VectorPredicate ZERO_VECTOR = new VectorPredicate() {
        @Override
        public boolean test(int i, double value) {
            return Math.abs(value) < EPS;
        }
    };

    /**
     * Checks whether the vector is a
     * <a href="http://mathworld.wolfram.com/PositiveMatrix.html">positive
     * vector</a>.
     */
    public static final VectorPredicate POSITIVE_VECTOR = new VectorPredicate() {
        @Override
        public boolean test(int i, double value) {
            return value > 0.0;
        }
    };

    /**
     * Checks whether the vector is a
     * <a href="http://mathworld.wolfram.com/NegativeMatrix.html">negative
     * vector</a>.
     */
    public static final VectorPredicate NEGATIVE_VECTOR = new VectorPredicate() {
        @Override
        public boolean test(int i, double value) {
            return value < 0.0;
        }
    };

    /**
     * Increases each element of vector by <code>1</code>.
     */
    public static final VectorFunction INC_FUNCTION = new VectorFunction() {
        @Override
        public double evaluate(int i, double value) {
            return value + 1.0;
        }
    };

    /**
     * Decreases each element of vectors by <code>1</code>.
     */
    public static final VectorFunction DEC_FUNCTION = new VectorFunction() {
        @Override
        public double evaluate(int i, double value) {
            return value - 1.0;
        }
    };

    /**
     * Inverts each element of vector.
     */
    public static final VectorFunction INV_FUNCTION = new VectorFunction() {
        @Override
        public double evaluate(int i, double value) {
            return -value;
        }
    };

    /**
     * Creates a const function that evaluates it's argument to given {@code value}.
     *
     * @param arg a const value
     *
     * @return a closure object that does {@code _}
     */
    public static VectorFunction asConstFunction(final double arg) {
        return new VectorFunction() {
            @Override
            public double evaluate(int i, double value) {
                return arg;
            }
        };
    }

    /**
     * Creates a plus function that adds given {@code value} to it's argument.
     *
     * @param arg a value to be added to function's argument
     *
     * @return a closure object that does {@code _ + _}
     */
    public static VectorFunction asPlusFunction(final double arg) {
        return new VectorFunction() {
            @Override
            public double evaluate(int i, double value) {
                return value + arg;
            }
        };
    }

    /**
     * Creates a minus function that subtracts given {@code value} from it's argument.
     *
     * @param arg a value to be subtracted from function's argument
     *
     * @return a closure that does {@code _ - _}
     */
    public static VectorFunction asMinusFunction(final double arg) {
        return new VectorFunction() {
            @Override
            public double evaluate(int i, double value) {
                return value - arg;
            }
        };
    }

    /**
     * Creates a mul function that multiplies given {@code value} by it's argument.
     *
     * @param arg a value to be multiplied by function's argument
     *
     * @return a closure that does {@code _ * _}
     */
    public static VectorFunction asMulFunction(final double arg) {
        return new VectorFunction() {
            @Override
            public double evaluate(int i, double value) {
                return value * arg;
            }
        };
    }

    /**
     * Creates a div function that divides it's argument by given {@code value}.
     *
     * @param arg a divisor value
     *
     * @return a closure that does {@code _ / _}
     */
    public static VectorFunction asDivFunction(final double arg) {
        return new VectorFunction() {
            @Override
            public double evaluate(int i, double value) {
                return value / arg;
            }
        };
    }

    /**
     * Creates a mod function that calculates the modulus of it's argument and given {@code value}.
     *
     * @param arg a divisor value
     *
     * @return a closure that does {@code _ % _}
     */
    public static VectorFunction asModFunction(final double arg) {
        return new VectorFunction() {
            @Override
            public double evaluate(int i, double value) {
                return value % arg;
            }
        };
    }

    /**
     * Creates a sum vector accumulator that calculates the sum of all elements in the vector.
     *
     * @param neutral the neutral value
     *
     * @return a sum accumulator
     */
    public static VectorAccumulator asSumAccumulator(final double neutral) {
        return new VectorAccumulator() {
            private BigDecimal result = new BigDecimal(neutral);

            @Override
            public void update(int i, double value) {
                result = result.add(new BigDecimal(value));
            }

            @Override
            public double accumulate() {
                double value = result.setScale(Vectors.ROUND_FACTOR, RoundingMode.CEILING).doubleValue();
                result = new BigDecimal(neutral);
                return value;
            }
        };
    }

    /**
     * Creates a product vector accumulator that calculates the product of all elements in the vector.
     *
     * @param neutral the neutral value
     *
     * @return a product accumulator
     */
    public static VectorAccumulator asProductAccumulator(final double neutral) {
        return new VectorAccumulator() {
            private BigDecimal result = new BigDecimal(neutral);

            @Override
            public void update(int i, double value) {
                result = result.multiply(new BigDecimal(value));
            }

            @Override
            public double accumulate() {
                double value = result.setScale(Vectors.ROUND_FACTOR, RoundingMode.CEILING).doubleValue();
                result = new BigDecimal(neutral);
                return value;
            }
        };
    }

    /**
     * Makes a minimum vector accumulator that accumulates the minimum across vector elements.
     *
     * @return a minimum vector accumulator
     */
    public static VectorAccumulator mkMinAccumulator() {
        return new VectorAccumulator() {
            private double result = Double.POSITIVE_INFINITY;

            @Override
            public void update(int i, double value) {
                result = Math.min(result, value);
            }

            @Override
            public double accumulate() {
                double value = result;
                result = Double.POSITIVE_INFINITY;
                return value;
            }
        };
    }

    /**
     * Makes a maximum vector accumulator that accumulates the maximum across vector elements.
     *
     * @return a maximum vector accumulator
     */
    public static VectorAccumulator mkMaxAccumulator() {
        return new VectorAccumulator() {
            private double result = Double.NEGATIVE_INFINITY;

            @Override
            public void update(int i, double value) {
                result = Math.max(result, value);
            }

            @Override
            public double accumulate() {
                double value = result;
                result = Double.NEGATIVE_INFINITY;
                return value;
            }
        };
    }

    /**
     * Makes an Euclidean norm accumulator that allows to use
     * {@link org.la4j.Vector#fold(org.la4j.vector.functor.VectorAccumulator)} method for norm calculation.
     *
     * @return an Euclidean norm accumulator
     */
    public static VectorAccumulator mkEuclideanNormAccumulator() {
        return new VectorAccumulator() {
            private BigDecimal result = new BigDecimal(0.0);

            @Override
            public void update(int i, double value) {
                result = result.add(new BigDecimal(value * value));
            }

            @Override
            public double accumulate() {
                double value = result.setScale(Vectors.ROUND_FACTOR, RoundingMode.CEILING).doubleValue();
                result = new BigDecimal(0.0);
                return Math.sqrt(value);
            }
        };
    }

    /**
     * Makes a Manhattan norm accumulator that allows to use
     * {@link org.la4j.Vector#fold(org.la4j.vector.functor.VectorAccumulator)} method for norm calculation.
     *
     * @return a Manhattan norm accumulator
     */
    public static VectorAccumulator mkManhattanNormAccumulator() {
        return new VectorAccumulator() {
            private double result = 0.0;

            @Override
            public void update(int i, double value) {
                result += Math.abs(value);
            }

            @Override
            public double accumulate() {
                double value = result;
                result = 0.0;
                return value;
            }
        };
    }

    /**
     * Makes an Infinity norm accumulator that allows to use
     * {@link org.la4j.Vector#fold(org.la4j.vector.functor.VectorAccumulator)} method for norm calculation.
     *
     * @return an Infinity norm accumulator
     */
    public static VectorAccumulator mkInfinityNormAccumulator() {
        return new VectorAccumulator() {
            private double result = Double.NEGATIVE_INFINITY;

            @Override
            public void update(int i, double value) {
                result = Math.max(result, Math.abs(value));
            }

            @Override
            public double accumulate() {
                double value = result;
                result = Double.NEGATIVE_INFINITY;
                return value;
            }
        };
    }

    /**
     * Creates a sum function accumulator, that calculates the sum of all
     * elements in the vector after applying given {@code function} to each of them.
     *
     * @param neutral the neutral value
     * @param function the vector function
     *
     * @return a sum function accumulator
     */
    public static VectorAccumulator asSumFunctionAccumulator(final double neutral,
                                                             final VectorFunction function) {
        return new VectorAccumulator() {
            private final VectorAccumulator sumAccumulator = Vectors.asSumAccumulator(neutral);

            @Override
            public void update(int i, double value) {
                sumAccumulator.update(i, function.evaluate(i, value));
            }

            @Override
            public double accumulate() {
                return sumAccumulator.accumulate();
            }
        };
    }

    /**
     * Creates a product function accumulator, that calculates the product of
     * all elements in the vector after applying given {@code function} to
     * each of them.
     *
     * @param neutral the neutral value
     * @param function the vector function
     *
     * @return a product function accumulator
     */
    public static VectorAccumulator asProductFunctionAccumulator(final double neutral,
                                                                 final VectorFunction function) {
        return new VectorAccumulator() {
            private final VectorAccumulator productAccumulator = Vectors.asProductAccumulator(neutral);

            @Override
            public void update(int i, double value) {
                productAccumulator.update(i, function.evaluate(i, value));
            }

            @Override
            public double accumulate() {
                return productAccumulator.accumulate();
            }
        };
    }

    /**
     * Creates an accumulator procedure that adapts a vector accumulator for procedure
     * interface. This is useful for reusing a single accumulator for multiple fold operations
     * in multiple vectors.
     *
     * @param accumulator the vector accumulator
     *
     * @return an accumulator procedure
     */
    public static VectorProcedure asAccumulatorProcedure(final VectorAccumulator accumulator) {
        return new VectorProcedure() {
            @Override
            public void apply(int i, double value) {
                accumulator.update(i, value);
            }
        };
    }
}
