/*
 * Copyright 1997-2025 Optimatika
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.ojalgo.array;

import java.math.MathContext;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.stream.IntStream;

import org.ojalgo.function.BinaryFunction;
import org.ojalgo.function.NullaryFunction;
import org.ojalgo.function.UnaryFunction;
import org.ojalgo.function.VoidFunction;
import org.ojalgo.function.constant.PrimitiveMath;
import org.ojalgo.scalar.PrimitiveScalar;
import org.ojalgo.structure.Access1D;
import org.ojalgo.structure.ElementView1D;
import org.ojalgo.structure.Mutate1D;
import org.ojalgo.structure.Structure1D;
import org.ojalgo.type.NumberDefinition;
import org.ojalgo.type.context.NumberContext;

/**
 * <p>
 * Only stores nonzero elements and/or elements specifically set by the user. The nonzero elements are stored
 * internally in a {@link DenseArray}.
 * </p>
 *
 * @author apete
 */
public final class SparseArray<N extends Comparable<N>> extends BasicArray<N> {

    @FunctionalInterface
    public interface NonzeroPrimitiveCallback {

        /**
         * @param index Index
         * @param value Value (nonzero) at that index
         */
        void call(long index, double value);

    }

    @FunctionalInterface
    public interface NonzeroReferenceTypeCallback<N extends Comparable<N>> {

        /**
         * @param index  Index
         * @param number Number (nonzero) at that index
         */
        void call(long index, N number);

    }

    public static final class NonzeroView<N extends Comparable<N>> implements ElementView1D<N, NonzeroView<N>> {

        private int myCursor = -1;
        private final int[] myIndices;
        private final int myLastCursor;
        private final DenseArray<N> myValues;

        private NonzeroView(final int[] indices, final DenseArray<N> values, final int initial, final int last) {

            super();

            myIndices = indices;
            myValues = values;

            myCursor = initial;
            myLastCursor = last;
        }

        NonzeroView(final int[] indices, final DenseArray<N> values, final int actualLength) {
            this(indices, values, -1, actualLength - 1);
        }

        @Override
        public double doubleValue() {
            return myValues.doubleValue(myCursor);
        }

        @Override
        public long estimateSize() {
            return myLastCursor - myCursor;
        }

        @Override
        public void forEachRemaining(final Consumer<? super NonzeroView<N>> action) {

            // BasicLogger.debug("forEachRemaining [{}, {})", myCursor, myLastCursor);

            ElementView1D.super.forEachRemaining(action);
        }

        @Override
        public N get() {
            return myValues.get(myCursor);
        }

        @Override
        public boolean hasNext() {
            return myCursor < myLastCursor;
        }

        @Override
        public boolean hasPrevious() {
            return myCursor > 0;
        }

        @Override
        public long index() {
            return myIndices[myCursor];
        }

        @Override
        public NonzeroView<N> iterator() {
            return new NonzeroView<>(myIndices, myValues, -1, myLastCursor);
        }

        public void modify(final BinaryFunction<N> function, final double right) {
            myValues.set(myCursor, function.invoke(myValues.doubleValue(myCursor), right));
        }

        public void modify(final BinaryFunction<N> function, final N right) {
            myValues.set(myCursor, function.invoke(myValues.get(myCursor), right));
        }

        public void modify(final double left, final BinaryFunction<N> function) {
            myValues.set(myCursor, function.invoke(left, myValues.doubleValue(myCursor)));
        }

        public void modify(final N left, final BinaryFunction<N> function) {
            myValues.set(myCursor, function.invoke(left, myValues.get(myCursor)));
        }

        @Override
        public NonzeroView<N> next() {
            myCursor++;
            return this;
        }

        @Override
        public long nextIndex() {
            return myIndices[myCursor + 1];
        }

        @Override
        public NonzeroView<N> previous() {
            myCursor--;
            return this;
        }

        @Override
        public long previousIndex() {
            return myIndices[myCursor - 1];
        }

        @Override
        public boolean tryAdvance(final Consumer<? super NonzeroView<N>> action) {
            return ElementView1D.super.tryAdvance(action);
        }

        @Override
        public NonzeroView<N> trySplit() {

            int remaining = myLastCursor - myCursor;

            if (remaining > 1) {

                int split = myCursor + remaining / 2;

                // BasicLogger.debug("Splitting [{}, {}) into [{}, {}) and [{}, {})", myCursor, myLastCursor,
                // myCursor, split, split, myLastCursor);

                NonzeroView<N> retVal = new NonzeroView<>(myIndices, myValues, myCursor, split);

                myCursor = split;

                return retVal;

            } else {

                return null;
            }
        }

    }

    public static final class SparseFactory<N extends Comparable<N>> extends BasicArray.BaseFactory<N, BasicArray<N>> {

        private GrowthStrategy myGrowthStrategy;
        private final PlainArray.Factory<N, ?> myPlainFactory;

        SparseFactory(final PlainArray.Factory<N, ?> plainFactory) {
            super(plainFactory.getMathType());
            myGrowthStrategy = GrowthStrategy.from(plainFactory.getMathType());
            myPlainFactory = plainFactory;
        }

        /**
         * @param chunk Defines a capacity break point. Below this point the capacity is doubled when needed.
         *              Above it, it is grown by adding one "chunk" at the time. Must be a power of 2. (The
         *              builder will enforce that for you.)
         * @return this
         */
        public SparseFactory<N> chunk(final long chunk) {
            myGrowthStrategy = myGrowthStrategy.chunk(chunk);
            return this;
        }

        /**
         * @param initial Sets the initial capacity of the "arrays" to be created using this factory.
         * @return this
         */
        public SparseFactory<N> initial(final long initial) {
            myGrowthStrategy = myGrowthStrategy.initial(initial);
            return this;
        }

        @Override
        public SparseArray<N> make(final int size) {
            return new SparseArray<>(myPlainFactory, myGrowthStrategy, size);
        }

        @Override
        public BasicArray<N> make(final long count) {
            if (myGrowthStrategy.isSegmented(count)) {
                return SegmentedArray.newInstance(this, count);
            } else {
                return new SparseArray<>(myPlainFactory, myGrowthStrategy, Math.toIntExact(count));
            }
        }

        /**
         * With very large data structures, particularly sparse ones, the underlying (dense) storage is
         * segmented. (Very large arrays are implemented as an array of arrays.) This determines the
         * size/length of one such segment. Must be a multiple of the chunk size as well as a power of 2. (The
         * builder will enforce this for you.)
         */
        public SparseFactory<N> segment(final long segment) {
            myGrowthStrategy = myGrowthStrategy.segment(segment);
            return this;
        }

        @Override
        long getCapacityLimit() {
            return Long.MAX_VALUE;
        }

    }

    private static final NumberContext MATH_CONTEXT = NumberContext.ofMath(MathContext.DECIMAL64);

    public static <N extends Comparable<N>> SparseFactory<N> factory(final PlainArray.Factory<N, ?> denseFactory) {
        return new SparseFactory<>(denseFactory);
    }

    /**
     * The number of nonzero elements
     */
    private int myActualLength = 0;
    /**
     * The capacity
     */
    private final int mySize;
    private final PlainArray.Factory<N, ?> myDenseFactory;
    private final GrowthStrategy myGrowthStrategy;
    private int[] myIndices;
    private PlainArray<N> myValues;

    SparseArray(final PlainArray.Factory<N, ?> denseFactory, final GrowthStrategy growthStrategy, final int size) {

        super(denseFactory);

        mySize = size;

        myDenseFactory = denseFactory;
        myGrowthStrategy = growthStrategy;

        myIndices = new int[growthStrategy.initial()];
        myValues = growthStrategy.makeInitial(denseFactory::make);
    }

    @Override
    public void add(final int index, final double addend) {
        int tmpIndex = this.index(index);
        if (tmpIndex >= 0) {
            myValues.add(tmpIndex, addend);
        } else {
            this.set(index, addend);
        }
    }

    @Override
    public void add(final long index, final Comparable<?> addend) {
        int tmpIndex = this.index(index);
        if (tmpIndex >= 0) {
            myValues.add(tmpIndex, addend);
        } else {
            this.set(index, addend);
        }
    }

    @Override
    public void add(final long index, final double addend) {
        int tmpIndex = this.index(index);
        if (tmpIndex >= 0) {
            myValues.add(tmpIndex, addend);
        } else {
            this.set(index, addend);
        }
    }

    @Override
    public void add(final long index, final float addend) {
        int tmpIndex = this.index(index);
        if (tmpIndex >= 0) {
            myValues.add(tmpIndex, addend);
        } else {
            this.set(index, addend);
        }
    }

    @Override
    public void axpy(final double a, final Mutate1D.Modifiable<?> y) {
        for (int n = 0; n < myActualLength; n++) {
            y.add(myIndices[n], a * myValues.doubleValue(n));
        }
    }

    @Override
    public long count() {
        return mySize;
    }

    public int countNonzeros() {
        return myActualLength;
    }

    public long countZeros() {
        return mySize - myActualLength;
    }

    @Override
    public double dot(final Access1D<?> vector) {

        double retVal = PrimitiveMath.ZERO;

        for (int n = 0; n < myActualLength; n++) {
            retVal += myValues.doubleValue(n) * vector.doubleValue(myIndices[n]);
        }

        return retVal;
    }

    @Override
    public double doubleValue(final int index) {

        int tmpIndex = this.index(index);
        if (tmpIndex >= 0) {
            return this.doubleValueInternally(tmpIndex);
        } else {
            return PrimitiveMath.ZERO;
        }
    }

    @Override
    public double doubleValue(final long index) {

        int tmpIndex = this.index(index);
        if (tmpIndex >= 0) {
            return this.doubleValueInternally(tmpIndex);
        } else {
            return PrimitiveMath.ZERO;
        }
    }

    /**
     * Efficiently exchanges two elements in the sparse array.
     * <p>
     * This method is optimized for sparse arrays and handles all cases efficiently:
     * <ul>
     * <li>Both elements are nonzero (direct swap)</li>
     * <li>One element is zero, other is nonzero (remove one, add other)</li>
     * <li>Both elements are zero (no operation needed)</li>
     * </ul>
     *
     * @param indexA the first index to exchange
     * @param indexB the second index to exchange
     */
    public void exchange(final int indexA, final int indexB) {

        if (indexA == indexB || myActualLength == 0) {
            return; // No operation needed
        }

        int internalA = this.index(indexA);
        int internalB = this.index(indexB);

        boolean existsA = internalA >= 0;
        boolean existsB = internalB >= 0;

        if (existsA && existsB) {
            // Both elements exist - swap values only
            if (this.isPrimitive()) {
                double tmpVal = myValues.doubleValue(internalA);
                myValues.set(internalA, myValues.doubleValue(internalB));
                myValues.set(internalB, tmpVal);
            } else {
                N tmpVal = myValues.get(internalA);
                myValues.set(internalA, myValues.get(internalB));
                myValues.set(internalB, tmpVal);
            }
            // No need to swap indices - they represent the original positions

        } else if (existsA && !existsB) {
            // Element A exists, B doesn't - move A to B's position
            if (this.isPrimitive()) {
                double valueA = myValues.doubleValue(internalA);
                // Remove A from its current position
                this.remove(indexA, internalA);
                // Add A to B's position
                this.set(indexB, valueA);
            } else {
                N valueA = myValues.get(internalA);
                // Remove A from its current position
                this.remove(indexA, internalA);
                // Add A to B's position
                this.set(indexB, valueA);
            }

        } else if (!existsA && existsB) {
            // Element B exists, A doesn't - move B to A's position
            if (this.isPrimitive()) {
                double valueB = myValues.doubleValue(internalB);
                // Remove B from its current position
                this.remove(indexB, internalB);
                // Add B to A's position
                this.set(indexA, valueB);
            } else {
                N valueB = myValues.get(internalB);
                // Remove B from its current position
                this.remove(indexB, internalB);
                // Add B to A's position
                this.set(indexA, valueB);
            }

        } else {
            // Neither element exists - no operation needed
            // Both are zero, so swapping them has no effect
        }
    }

    @Override
    public void fillAll(final N value) {

        if (PrimitiveScalar.isSmall(PrimitiveMath.ONE, NumberDefinition.doubleValue(value))) {

            myValues.fillAll(myDenseFactory.scalar().zero().get());

        } else {

            // Bad idea...

            int tmpSize = Math.toIntExact(this.count());

            if (tmpSize != myIndices.length) {
                myIndices = Structure1D.newIncreasingRange(0, tmpSize);
                myValues = myDenseFactory.make(tmpSize);
                myActualLength = tmpSize;
            }

            myValues.fillAll(value);
        }
    }

    @Override
    public void fillAll(final NullaryFunction<?> supplier) {

        // Bad idea...

        int tmpSize = Math.toIntExact(this.count());

        if (tmpSize != myIndices.length) {
            myIndices = Structure1D.newIncreasingRange(0, tmpSize);
            myValues = myDenseFactory.make(tmpSize);
            myActualLength = tmpSize;
        }

        myValues.fillAll(supplier);
    }

    @Override
    public void fillRange(final long first, final long limit, final N value) {
        this.fill(first, limit, 1L, value);
    }

    @Override
    public void fillRange(final long first, final long limit, final NullaryFunction<?> supplier) {
        this.fill(first, limit, 1L, supplier);
    }

    public long firstInRange(final long rangeFirst, final long rangeLimit) {
        int tmpFoundAt = this.index(rangeFirst);
        if (tmpFoundAt < 0) {
            tmpFoundAt = -(tmpFoundAt + 1);
        }
        if (tmpFoundAt >= myActualLength) {
            return rangeLimit;
        }
        return Math.min(myIndices[tmpFoundAt], rangeLimit);
    }

    @Override
    public N get(final long index) {

        int tmpIndex = this.index(index);
        if (tmpIndex >= 0) {
            return this.getInternally(tmpIndex);
        } else {
            return myDenseFactory.scalar().zero().get();
        }
    }

    @Override
    public long indexOfLargest() {
        return myIndices[myValues.indexOfLargest(0, myActualLength, 1)];
    }

    public long limitOfRange(final long rangeFirst, final long rangeLimit) {
        int tmpFoundAt = this.index(rangeLimit - 1L);
        if (tmpFoundAt < 0) {
            tmpFoundAt = -(tmpFoundAt + 2);
        }
        if (tmpFoundAt < 0) {
            return rangeFirst;
        }
        return Math.min(myIndices[tmpFoundAt] + 1L, rangeLimit);
    }

    @Override
    public void modifyAll(final UnaryFunction<N> modifier) {

        double zeroValue = modifier.invoke(PrimitiveMath.ZERO);

        if (MATH_CONTEXT.isDifferent(PrimitiveMath.ZERO, zeroValue)) {
            throw new IllegalArgumentException("SparseArray zero-value modification!");
        }

        myValues.modifyAll(modifier);
    }

    @Override
    public void modifyOne(final long index, final UnaryFunction<N> modifier) {
        this.set(index, modifier.invoke(this.get(index)));
    }

    @Override
    public NonzeroView<N> nonzeros() {
        return new NonzeroView<>(myIndices, myValues, myActualLength);
    }

    /**
     * Efficiently appends a new nonzero element at the end of this sparse array.
     * <p>
     * This method assumes that the supplied {@code index} is strictly greater than all existing indices in
     * the array. No search is performed; the value is simply appended. If the ascending order of indices is
     * broken, future behavior is unspecified. If the value is zero, nothing is stored.
     *
     * @param index the index at which to insert the new value (must be after all existing indices)
     * @param value the value to insert (only nonzero values are actually stored)
     */
    public void putLast(final int index, final double value) {
        this.update(index, -(myActualLength + 1), value, false);
    }

    /**
     * All elements with indices in the range [first,last] should be shifted one step to the left (their
     * indices decreased by 1). It is safe to assume that there is currently nothing stored at 'from'. The new
     * value 'newValue' should be placed at position 'last'. That position is typically freed by the shift. If
     * 'newValue' is zero it doesn't have to stored. The case where the range [first,last] was originally
     * empty, but the new value is non-zero requires special logic.
     */
    public void removeShiftAndInsert(final int first, final int last, final double newValue) {

        boolean atFirst = false;
        int insertPos = -1;

        for (int i = 0; i < myActualLength; i++) {
            int index = myIndices[i];

            if (first == index) {
                atFirst = true;
            }

            if (atFirst) {
                if (first == index) {
                    insertPos = i;
                } else if (first < index && index <= last) {
                    // Shift value left
                    myIndices[i - 1] = index - 1;
                    myValues.set(i - 1, myValues.doubleValue(i));
                    insertPos = i; // This slot is now free for the new value
                }
            } else if (first < index && index <= last) {
                // Just decrement index
                myIndices[i] = index - 1;
            }

            if (last <= index) {
                break;
            }
        }

        if (insertPos >= 0) {
            // A spot was freed, write the new value at insertPos
            myIndices[insertPos] = last;
            myValues.set(insertPos, newValue);
        } else if (newValue != PrimitiveMath.ZERO) {
            // No spot freed, newValue is nonzero, use set to insert
            this.set(last, newValue);
        }
        // No spot freed, newValue is zero: do nothing
    }

    @Override
    public void reset() {
        myActualLength = 0;
    }

    @Override
    public void set(final int index, final double value) {

        int internalIndex = this.index(index);

        this.update(index, internalIndex, value, false);
    }

    @Override
    public void set(final long index, final Comparable<?> value) {

        int internalIndex = this.index(index);

        this.update(index, internalIndex, value, false);
    }

    @Override
    public void set(final long index, final double value) {

        int internalIndex = this.index(index);

        this.update(index, internalIndex, value, false);
    }

    @Override
    public void set(final long index, final float value) {

        int internalIndex = this.index(index);

        this.update(index, internalIndex, value, false);
    }

    @Override
    public int size() {
        return mySize;
    }

    /**
     * Does NOT first reset the receiver! That means the elements in the receiver corresponding to zeros in
     * this sparse array are not zero:ed or modified in any way.
     */
    public void supplyNonZerosTo(final double[] receiver) {
        for (int n = 0; n < myActualLength; n++) {
            receiver[myIndices[n]] = myValues.doubleValue(n);
        }
    }

    /**
     * Does NOT first reset the receiver! That means the elements in the receiver corresponding to zeros in
     * this sparse array are not zero:ed or modified in any way.
     */
    public void supplyNonZerosTo(final Mutate1D receiver) {
        if (this.isPrimitive()) {
            for (int n = 0; n < myActualLength; n++) {
                receiver.set(myIndices[n], myValues.doubleValue(n));
            }
        } else {
            for (int n = 0; n < myActualLength; n++) {
                receiver.set(myIndices[n], myValues.get(n));
            }
        }
    }

    @Override
    public void supplyTo(final double[] receiver) {

        Arrays.fill(receiver, PrimitiveMath.ZERO);

        this.supplyNonZerosTo(receiver);
    }

    @Override
    public void supplyTo(final Mutate1D receiver) {

        receiver.reset();

        this.supplyNonZerosTo(receiver);
    }

    @Override
    public void visitOne(final long index, final VoidFunction<N> visitor) {
        if (this.isPrimitive()) {
            visitor.invoke(this.doubleValue(index));
        } else {
            visitor.invoke(this.get(index));
        }
    }

    public void visitPrimitiveNonzerosInRange(final long first, final long limit, final NonzeroPrimitiveCallback visitor) {

        int localFirst = this.index(first);
        if (localFirst < 0) {
            localFirst = -(localFirst + 1);
        }
        int localLimit = this.index(limit);
        if (localLimit < 0) {
            localLimit = -(localLimit + 1);
        }

        for (int i = localFirst; i < localLimit; i++) {
            visitor.call(myIndices[i], myValues.doubleValue(i));
        }
    }

    @Override
    public void visitRange(final long first, final long limit, final VoidFunction<N> visitor) {

        int localFirst = this.index(first);
        if (localFirst < 0) {
            localFirst = -(localFirst + 1);
        }
        int localLimit = this.index(limit);
        if (localLimit < 0) {
            localLimit = -(localLimit + 1);
        }

        if (limit - first > localLimit - localFirst) {
            visitor.invoke(PrimitiveMath.ZERO);
        }

        for (int i = localFirst; i < localLimit; i++) {
            myValues.visitOne(i, visitor);
        }
    }

    public void visitReferenceTypeNonzerosInRange(final long first, final long limit, final NonzeroReferenceTypeCallback<N> visitor) {

        int localFirst = this.index(first);
        if (localFirst < 0) {
            localFirst = -(localFirst + 1);
        }
        int localLimit = this.index(limit);
        if (localLimit < 0) {
            localLimit = -(localLimit + 1);
        }

        for (int i = localFirst; i < localLimit; i++) {
            visitor.call(myIndices[i], myValues.get(i));
        }
    }

    /**
     * Will never remove anything - just insert or update
     */
    private void update(final int externalIndex, final int internalIndex, final Comparable<?> value, final boolean shouldStoreZero) {

        if (internalIndex >= 0) {
            // Existing value, just update

            myValues.set(internalIndex, value);

        } else if (shouldStoreZero || !value.equals(myDenseFactory.scalar().zero().get())) {
            // Not existing value, insert new
            int tmpInsInd = -(internalIndex + 1);

            if (myActualLength + 1 <= myIndices.length) {
                // No need to grow the backing arrays

                for (int i = myActualLength; i > tmpInsInd; i--) {
                    myIndices[i] = myIndices[i - 1];
                    myValues.set(i, myValues.get(i - 1));
                }
                myIndices[tmpInsInd] = externalIndex;
                myValues.set(tmpInsInd, value);

            } else {
                // Needs to grow the backing arrays

                int tmpCapacity = myGrowthStrategy.grow(myIndices.length);
                int[] tmpIndices = new int[tmpCapacity];
                PlainArray<N> tmpValues = myDenseFactory.make(tmpCapacity);

                for (int i = 0; i < tmpInsInd; i++) {
                    tmpIndices[i] = myIndices[i];
                    tmpValues.set(i, myValues.get(i));
                }
                tmpIndices[tmpInsInd] = externalIndex;
                tmpValues.set(tmpInsInd, value);
                for (int i = tmpInsInd; i < myIndices.length; i++) {
                    tmpIndices[i + 1] = myIndices[i];
                    tmpValues.set(i + 1, myValues.get(i));
                }
                // for (int i = myIndices.length + 1; i < tmpIndices.length; i++) {
                // tmpIndices[i] = Long.MAX_VALUE;
                // }

                myIndices = tmpIndices;
                myValues = tmpValues;
            }
            myActualLength++;
        }
    }

    /**
     * Will never remove anything - just insert or update
     */
    private void update(final int externalIndex, final int internalIndex, final double value, final boolean shouldStoreZero) {

        if (internalIndex >= 0) {
            // Existing value, just update

            myValues.set(internalIndex, value);

        } else if (shouldStoreZero || NumberContext.compare(value, PrimitiveMath.ZERO) != 0) {
            // Not existing value, insert new
            int tmpInsInd = -(internalIndex + 1);

            if (myActualLength + 1 <= myIndices.length) {
                // No need to grow the backing arrays

                for (int i = myActualLength; i > tmpInsInd; i--) {
                    myIndices[i] = myIndices[i - 1];
                    myValues.set(i, myValues.doubleValue(i - 1));
                }
                myIndices[tmpInsInd] = externalIndex;
                myValues.set(tmpInsInd, value);

            } else {
                // Needs to grow the backing arrays

                int tmpCapacity = myGrowthStrategy.grow(myIndices.length);
                int[] tmpIndices = new int[tmpCapacity];
                PlainArray<N> tmpValues = myDenseFactory.make(tmpCapacity);

                for (int i = 0; i < tmpInsInd; i++) {
                    tmpIndices[i] = myIndices[i];
                    tmpValues.set(i, myValues.doubleValue(i));
                }
                tmpIndices[tmpInsInd] = externalIndex;
                tmpValues.set(tmpInsInd, value);
                for (int i = tmpInsInd; i < myIndices.length; i++) {
                    tmpIndices[i + 1] = myIndices[i];
                    tmpValues.set(i + 1, myValues.doubleValue(i));
                }
                // for (int i = myIndices.length + 1; i < tmpIndices.length; i++) {
                // tmpIndices[i] = Long.MAX_VALUE;
                // }

                myIndices = tmpIndices;
                myValues = tmpValues;
            }
            myActualLength++;
        }
    }

    private void update(final long externalIndex, final int internalIndex, final Comparable<?> value, final boolean shouldStoreZero) {
        this.update((int) externalIndex, internalIndex, value, shouldStoreZero);
    }

    private void update(final long externalIndex, final int internalIndex, final double value, final boolean shouldStoreZero) {
        this.update((int) externalIndex, internalIndex, value, shouldStoreZero);
    }

    @Override
    protected void exchange(final long firstA, final long firstB, final long step, final long count) {

        int tmpIndexA = (int) firstA;
        int tmpIndexB = (int) firstB;

        for (int i = 0, limit = (int) count; i < limit; i++) {

            this.exchange(tmpIndexA, tmpIndexB);

            tmpIndexA += (int) step;
            tmpIndexB += (int) step;
        }
    }

    @Override
    protected void fill(final long first, final long limit, final long step, final N value) {
        for (long i = first; i < limit; i += step) {
            this.set(i, value);
        }
    }

    @Override
    protected void fill(final long first, final long limit, final long step, final NullaryFunction<?> supplier) {
        for (long i = first; i < limit; i += step) {
            this.set(i, supplier.get());
        }
    }

    @Override
    protected long indexOfLargest(final long first, final long limit, final long step) {

        long retVal = first;
        double tmpLargest = PrimitiveMath.ZERO;
        double tmpValue;

        for (int i = 0; i < myIndices.length; i++) {
            int tmpIndex = myIndices[i];
            if (tmpIndex >= first && tmpIndex < limit && (tmpIndex - first) % step == 0L) {
                tmpValue = PrimitiveMath.ABS.invoke(myValues.doubleValue(i));
                if (tmpValue > tmpLargest) {
                    tmpLargest = tmpValue;
                    retVal = i;
                }
            }
        }

        return retVal;
    }

    @Override
    protected void modify(final long first, final long limit, final long step, final Access1D<N> left, final BinaryFunction<N> function) {

        double tmpZeroValue = function.invoke(PrimitiveMath.ZERO, PrimitiveMath.ZERO);

        if (!PrimitiveScalar.isSmall(PrimitiveMath.ONE, tmpZeroValue)) {

            throw new IllegalArgumentException("SparseArray zero modification!");
        }

        for (int i = 0; i < myIndices.length; i++) {
            int tmpIndex = myIndices[i];
            if (tmpIndex >= first && tmpIndex < limit && (tmpIndex - first) % step == 0L) {
                myValues.modify(tmpIndex, i, left, function);
            }
        }
    }

    @Override
    protected void modify(final long first, final long limit, final long step, final BinaryFunction<N> function, final Access1D<N> right) {

        double tmpZeroValue = function.invoke(PrimitiveMath.ZERO, PrimitiveMath.ZERO);

        if (!PrimitiveScalar.isSmall(PrimitiveMath.ONE, tmpZeroValue)) {

            throw new IllegalArgumentException("SparseArray zero modification!");
        }
        for (int i = 0; i < myIndices.length; i++) {
            long tmpIndex = myIndices[i];
            if (tmpIndex >= first && tmpIndex < limit && (tmpIndex - first) % step == 0L) {
                myValues.modify((int) tmpIndex, i, function, right);
            }
        }
    }

    @Override
    protected void modify(final long first, final long limit, final long step, final UnaryFunction<N> function) {

        double tmpZeroValue = function.invoke(PrimitiveMath.ZERO);

        if (!PrimitiveScalar.isSmall(PrimitiveMath.ONE, tmpZeroValue)) {

            throw new IllegalArgumentException("SparseArray zero modification!");
        }
        for (int i = 0; i < myActualLength; i++) {
            int tmpIndex = myIndices[i];
            if (tmpIndex >= first && tmpIndex < limit && (tmpIndex - first) % step == 0L) {
                myValues.modify(tmpIndex, i, function);
            }
        }
    }

    @Override
    protected void visit(final long first, final long limit, final long step, final VoidFunction<N> visitor) {
        boolean tmpOnlyOnce = true;
        for (int i = 0; i < myIndices.length; i++) {
            int tmpIndex = myIndices[i];
            if (tmpIndex >= first && tmpIndex < limit && (tmpIndex - first) % step == 0L) {
                myValues.visitOne(i, visitor);
            } else if (tmpOnlyOnce) {
                visitor.invoke(PrimitiveMath.ZERO);
                tmpOnlyOnce = false;
            }
        }
    }

    long capacity() {
        return myValues.count();
    }

    PlainArray<N> densify() {

        PlainArray<N> retVal = myDenseFactory.make(this.size());

        if (this.isPrimitive()) {
            for (int i = 0; i < myActualLength; i++) {
                retVal.set(myIndices[i], myValues.doubleValue(i));

            }
        } else {
            for (int i = 0; i < myActualLength; i++) {
                retVal.set(myIndices[i], myValues.get(i));
            }
        }

        return retVal;
    }

    double doubleValueInternally(final int internalIndex) {
        return myValues.doubleValue(internalIndex);
    }

    long firstIndex() {
        return myIndices[0];
    }

    int getActualLength() {
        return myActualLength;
    }

    N getInternally(final int internalIndex) {
        return myValues.get(internalIndex);
    }

    DenseArray<N> getValues() {
        return myValues;
    }

    Access1D<N> getValues(final long fromIncl, final long toExcl) {

        int intFrom = this.index(fromIncl);
        if (intFrom < 0) {
            intFrom = -(intFrom + 1);
        }
        int first = intFrom;

        int intTo = this.index(toExcl);
        if (intTo < 0) {
            intTo = -(intTo + 1);
        }
        int limit = intTo;

        return new Access1D<>() {

            @Override
            public double doubleValue(final int index) {
                return myValues.doubleValue(first + index);
            }

            @Override
            public double doubleValue(final long index) {
                return myValues.doubleValue(first + (int) index);
            }

            @Override
            public N get(final long index) {
                return myValues.get(first + (int) index);
            }

            @Override
            public int size() {
                return limit - first;
            }

        };
    }

    int index(final int index) {
        return Arrays.binarySearch(myIndices, 0, myActualLength, index);
    }

    int index(final long index) {
        return this.index((int) index);
    }

    IntStream indices() {
        return Arrays.stream(myIndices, 0, myActualLength);
    }

    long lastIndex() {
        return myIndices[myActualLength - 1];
    }

    void put(final long key, final int index, final double value) {
        this.update(key, index, value, true);
    }

    void put(final long key, final int index, final N value) {
        this.update(key, index, value, true);
    }

    void remove(final long externalIndex, final int internalIndex) {

        if (internalIndex >= 0) {
            // Existing value, remove

            myActualLength--;

            if (myValues.isPrimitive()) {
                for (int i = internalIndex; i < myActualLength; i++) {
                    myIndices[i] = myIndices[i + 1];
                    myValues.set(i, myValues.doubleValue(i + 1));
                }
            } else {
                for (int i = internalIndex; i < myActualLength; i++) {
                    myIndices[i] = myIndices[i + 1];
                    myValues.set(i, myValues.get(i + 1));
                }
            }
        }
    }

}
