/*  __    __  __  __    __  ___
 * \  \  /  /    \  \  /  /  __/
 *  \  \/  /  /\  \  \/  /  /
 *   \____/__/  \__\____/__/
 *
 * Copyright 2014-2020 Vavr, http://vavr.io
 *
 * 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.
 */
package io.vavr.collection;

import io.vavr.PartialFunction;
import io.vavr.Tuple3;
import io.vavr.Tuple2;
import io.vavr.control.Option;

import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.*;

/**
 * Interface for immutable, indexed sequences.
 * <p>
 * Efficient random access is characteristic for indexed sequences.
 *
 * @param <T> component type
 * @author Daniel Dietrich
 */
public interface IndexedSeq<T> extends Seq<T> {

    long serialVersionUID = 1L;

    /**
     * Narrows a widened {@code IndexedSeq<? extends T>} to {@code IndexedSeq<T>}
     * by performing a type-safe cast. This is eligible because immutable/read-only
     * collections are covariant.
     *
     * @param indexedSeq An {@code IndexedSeq}.
     * @param <T>        Component type of the {@code IndexedSeq}.
     * @return the given {@code indexedSeq} instance as narrowed type {@code IndexedSeq<T>}.
     */
    @SuppressWarnings("unchecked")
    static <T> IndexedSeq<T> narrow(IndexedSeq<? extends T> indexedSeq) {
        return (IndexedSeq<T>) indexedSeq;
    }

    // -- Adjusted return types of Seq methods

    @Override
    IndexedSeq<T> append(T element);

    @Override
    IndexedSeq<T> appendAll(Iterable<? extends T> elements);

    @GwtIncompatible
    @Override
    IndexedSeq<T> asJava(Consumer<? super java.util.List<T>> action);

    @GwtIncompatible
    @Override
    IndexedSeq<T> asJavaMutable(Consumer<? super java.util.List<T>> action);

    @Override
    default PartialFunction<Integer, T> asPartialFunction() throws IndexOutOfBoundsException {
        return new PartialFunction<Integer, T>() {
            private static final long serialVersionUID = 1L;
            @Override
            public T apply(Integer index) {
                return get(index);
            }
            @Override
            public boolean isDefinedAt(Integer index) {
                return 0 <= index && index < length();
            }
        };
    }

    @Override
    <R> IndexedSeq<R> collect(PartialFunction<? super T, ? extends R> partialFunction);

    @Override
    IndexedSeq<? extends IndexedSeq<T>> combinations();

    @Override
    IndexedSeq<? extends IndexedSeq<T>> combinations(int k);

    @Override
    Iterator<? extends IndexedSeq<T>> crossProduct(int power);

    @Override
    IndexedSeq<T> distinct();

    @Override
    IndexedSeq<T> distinctBy(Comparator<? super T> comparator);

    @Override
    <U> IndexedSeq<T> distinctBy(Function<? super T, ? extends U> keyExtractor);

    @Override
    IndexedSeq<T> drop(int n);

    @Override
    IndexedSeq<T> dropUntil(Predicate<? super T> predicate);

    @Override
    IndexedSeq<T> dropWhile(Predicate<? super T> predicate);

    @Override
    IndexedSeq<T> dropRight(int n);

    @Override
    IndexedSeq<T> dropRightUntil(Predicate<? super T> predicate);

    @Override
    IndexedSeq<T> dropRightWhile(Predicate<? super T> predicate);

    @Override
    default boolean endsWith(Seq<? extends T> that) {
        Objects.requireNonNull(that, "that is null");
        if (that instanceof IndexedSeq) {
            int i = length() - 1;
            int j = that.length() - 1;
            if (j > i) {
                return false;
            } else {
                while (j >= 0) {
                    if (!Objects.equals(this.get(i), that.get(j))) {
                        return false;
                    }
                    i--;
                    j--;
                }
                return true;
            }
        } else {
            return Seq.super.endsWith(that);
        }
    }

    @Override
    IndexedSeq<T> filter(Predicate<? super T> predicate);

    @Override
    IndexedSeq<T> reject(Predicate<? super T> predicate);

    @Override
    <U> IndexedSeq<U> flatMap(Function<? super T, ? extends Iterable<? extends U>> mapper);

    @Override
    <C> Map<C, ? extends IndexedSeq<T>> groupBy(Function<? super T, ? extends C> classifier);

    @Override
    default int indexWhere(Predicate<? super T> predicate, int from) {
        Objects.requireNonNull(predicate, "predicate is null");
        int start = Math.max(from, 0);
        int n = start + segmentLength(predicate.negate(), start);
        return (n >= length()) ? -1 : n;
    }

    @Override
    Iterator<? extends IndexedSeq<T>> grouped(int size);

    @Override
    default int indexOfSlice(Iterable<? extends T> that, int from) {
        Objects.requireNonNull(that, "that is null");
        return IndexedSeqModule.Slice.indexOfSlice(this, that, from);
    }

    @Override
    IndexedSeq<T> init();

    @Override
    Option<? extends IndexedSeq<T>> initOption();

    @Override
    IndexedSeq<T> insert(int index, T element);

    @Override
    IndexedSeq<T> insertAll(int index, Iterable<? extends T> elements);

    @Override
    IndexedSeq<T> intersperse(T element);

    @Deprecated
    @Override
    default boolean isDefinedAt(Integer index) {
        return 0 <= index && index < length();
    }

    @Override
    default T last() {
        if (isEmpty()) {
            throw new NoSuchElementException("last of empty IndexedSeq");
        } else {
            return get(length() - 1);
        }
    }

    @Override
    default int lastIndexOfSlice(Iterable<? extends T> that, int end) {
        Objects.requireNonNull(that, "that is null");
        return IndexedSeqModule.Slice.lastIndexOfSlice(this, that, end);
    }

    @Override
    default int lastIndexWhere(Predicate<? super T> predicate, int end) {
        Objects.requireNonNull(predicate, "predicate is null");
        int i = Math.min(end, length() - 1);
        while (i >= 0 && !predicate.test(this.get(i))) {
            i--;
        }
        return i;
    }

    @Override
    <U> IndexedSeq<U> map(Function<? super T, ? extends U> mapper);

    @Override
    IndexedSeq<T> orElse(Iterable<? extends T> other);

    @Override
    IndexedSeq<T> orElse(Supplier<? extends Iterable<? extends T>> supplier);

    @Override
    IndexedSeq<T> padTo(int length, T element);

    @Override
    IndexedSeq<T> patch(int from, Iterable<? extends T> that, int replaced);

    @Override
    Tuple2<? extends IndexedSeq<T>, ? extends IndexedSeq<T>> partition(Predicate<? super T> predicate);

    @Override
    IndexedSeq<T> peek(Consumer<? super T> action);

    @Override
    IndexedSeq<? extends IndexedSeq<T>> permutations();

    @Override
    IndexedSeq<T> prepend(T element);

    @Override
    IndexedSeq<T> prependAll(Iterable<? extends T> elements);

    @Override
    IndexedSeq<T> remove(T element);

    @Override
    IndexedSeq<T> removeFirst(Predicate<T> predicate);

    @Override
    IndexedSeq<T> removeLast(Predicate<T> predicate);

    @Override
    IndexedSeq<T> removeAt(int index);

    @Override
    IndexedSeq<T> removeAll(T element);

    @Override
    IndexedSeq<T> removeAll(Iterable<? extends T> elements);

    @Override
    @Deprecated
    IndexedSeq<T> removeAll(Predicate<? super T> predicate);

    @Override
    IndexedSeq<T> replace(T currentElement, T newElement);

    @Override
    IndexedSeq<T> replaceAll(T currentElement, T newElement);

    @Override
    IndexedSeq<T> retainAll(Iterable<? extends T> elements);

    @Override
    IndexedSeq<T> reverse();

    @Override
    default Iterator<T> reverseIterator() {
        return new AbstractIterator<T>() {
            private int i = IndexedSeq.this.length();

            @Override
            public boolean hasNext() {
                return i > 0;
            }

            @Override
            public T getNext() {
                return IndexedSeq.this.get(--i);
            }
        };
    }

    @Override
    IndexedSeq<T> rotateLeft(int n);

    @Override
    IndexedSeq<T> rotateRight(int n);

    @Override
    IndexedSeq<T> scan(T zero, BiFunction<? super T, ? super T, ? extends T> operation);

    @Override
    <U> IndexedSeq<U> scanLeft(U zero, BiFunction<? super U, ? super T, ? extends U> operation);

    @Override
    <U> IndexedSeq<U> scanRight(U zero, BiFunction<? super T, ? super U, ? extends U> operation);

    @Override
    default int segmentLength(Predicate<? super T> predicate, int from) {
        Objects.requireNonNull(predicate, "predicate is null");
        int len = length();
        int i = from;
        while (i < len && predicate.test(this.get(i))) {
            i++;
        }
        return i - from;
    }

    @Override
    IndexedSeq<T> shuffle();

    @Override
    IndexedSeq<T> slice(int beginIndex, int endIndex);

    @Override
    Iterator<? extends IndexedSeq<T>> slideBy(Function<? super T, ?> classifier);

    @Override
    Iterator<? extends IndexedSeq<T>> sliding(int size);

    @Override
    Iterator<? extends IndexedSeq<T>> sliding(int size, int step);

    @Override
    IndexedSeq<T> sorted();

    @Override
    IndexedSeq<T> sorted(Comparator<? super T> comparator);

    @Override
    <U extends Comparable<? super U>> IndexedSeq<T> sortBy(Function<? super T, ? extends U> mapper);

    @Override
    <U> IndexedSeq<T> sortBy(Comparator<? super U> comparator, Function<? super T, ? extends U> mapper);

    @Override
    Tuple2<? extends IndexedSeq<T>, ? extends IndexedSeq<T>> span(Predicate<? super T> predicate);

    @Override
    default boolean startsWith(Iterable<? extends T> that, int offset) {
        Objects.requireNonNull(that, "that is null");
        if (offset < 0) { return false; }
        if (that instanceof IndexedSeq) {
            IndexedSeq<? extends T> thatIndexedSeq = (IndexedSeq<? extends T>) that;
            int i = offset;
            int j = 0;
            int thisLen = length();
            int thatLen = thatIndexedSeq.length();
            while (i < thisLen && j < thatLen && Objects.equals(this.get(i), thatIndexedSeq.get(j))) {
                i++;
                j++;
            }
            return j == thatLen;
        } else {
            int i = offset;
            int thisLen = length();
            java.util.Iterator<? extends T> thatElems = that.iterator();
            while (i < thisLen && thatElems.hasNext()) {
                if (!Objects.equals(this.get(i), thatElems.next())) {
                    return false;
                }
                i++;
            }
            return !thatElems.hasNext();
        }
    }

    @Override
    IndexedSeq<T> subSequence(int beginIndex);

    @Override
    IndexedSeq<T> subSequence(int beginIndex, int endIndex);

    @Override
    IndexedSeq<T> tail();

    @Override
    Option<? extends IndexedSeq<T>> tailOption();

    @Override
    IndexedSeq<T> take(int n);

    @Override
    IndexedSeq<T> takeUntil(Predicate<? super T> predicate);

    @Override
    IndexedSeq<T> takeWhile(Predicate<? super T> predicate);

    @Override
    IndexedSeq<T> takeRight(int n);

    @Override
    IndexedSeq<T> takeRightUntil(Predicate<? super T> predicate);

    @Override
    IndexedSeq<T> takeRightWhile(Predicate<? super T> predicate);

    @Override
    <T1, T2> Tuple2<? extends IndexedSeq<T1>, ? extends IndexedSeq<T2>> unzip(Function<? super T, Tuple2<? extends T1, ? extends T2>> unzipper);

    @Override
    <T1, T2, T3> Tuple3<? extends IndexedSeq<T1>, ? extends IndexedSeq<T2>, ? extends IndexedSeq<T3>> unzip3(Function<? super T, Tuple3<? extends T1, ? extends T2, ? extends T3>> unzipper);

    @Override
    IndexedSeq<T> update(int index, T element);

    @Override
    IndexedSeq<T> update(int index, Function<? super T, ? extends T> updater);

    @Override
    <U> IndexedSeq<Tuple2<T, U>> zip(Iterable<? extends U> that);

    @Override
    <U, R> IndexedSeq<R> zipWith(Iterable<? extends U> that, BiFunction<? super T, ? super U, ? extends R> mapper);

    @Override
    <U> IndexedSeq<Tuple2<T, U>> zipAll(Iterable<? extends U> that, T thisElem, U thatElem);

    @Override
    IndexedSeq<Tuple2<T, Integer>> zipWithIndex();

    @Override
    <U> IndexedSeq<U> zipWithIndex(BiFunction<? super T, ? super Integer, ? extends U> mapper);

    /**
     * Searches this sequence for a specific element using a binary search. The sequence must already be sorted into
     * ascending natural order. If it is not sorted, the results are undefined.
     *
     * @param element the element to find
     * @return the index of the search element, if it is contained in the sequence;
     * otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>. The
     * <i>insertion point</i> is defined as the point at which the
     * element would be inserted into the sequence. Note that this guarantees that
     * the return value will be &gt;= 0 if and only if the element is found.
     * @throws ClassCastException if T cannot be cast to {@code Comparable<? super T>}
     */
    @Override
    @SuppressWarnings("unchecked")
    default int search(T element) {
        IntUnaryOperator comparison = midIndex -> {
            Comparable<? super T> midVal = (Comparable<? super T>) get(midIndex);
            return midVal.compareTo(element);
        };
        return IndexedSeqModule.Search.binarySearch(this, comparison);
    }

    /**
     * Searches this sequence for a specific element using a binary search. The sequence must already be sorted into
     * ascending order according to the specified comparator. If it is not sorted, the results are undefined.
     *
     * @param element    the element to find
     * @param comparator the comparator by which this sequence is ordered
     * @return the index of the search element, if it is contained in the sequence;
     * otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>. The
     * <i>insertion point</i> is defined as the point at which the
     * element would be inserted into the sequence. Note that this guarantees that
     * the return value will be &gt;= 0 if and only if the element is found.
     */
    @Override
    default int search(T element, Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator, "comparator is null");
        IntUnaryOperator comparison = midIndex -> {
            T midVal = get(midIndex);
            return comparator.compare(midVal, element);
        };
        return IndexedSeqModule.Search.binarySearch(this, comparison);
    }

}

interface IndexedSeqModule {

    class Slice {

        static <T> int indexOfSlice(IndexedSeq<T> source, Iterable<? extends T> slice, int from) {
            if (source.isEmpty()) {
                return from == 0 && Collections.isEmpty(slice) ? 0 : -1;
            }
            final IndexedSeq<T> _slice = toIndexedSeq(slice);
            final int maxIndex = source.length() - _slice.length();
            return findSlice(source, _slice, Math.max(from, 0), maxIndex);
        }

        static <T> int lastIndexOfSlice(IndexedSeq<T> source, Iterable<? extends T> slice, int end) {
            if (end < 0) {
                return -1;
            } else if (source.isEmpty()) {
                return Collections.isEmpty(slice) ? 0 : -1;
            } else if (Collections.isEmpty(slice)) {
                final int len = source.length();
                return len < end ? len : end;
            }
            int index = 0;
            int result = -1;
            final IndexedSeq<T> _slice = toIndexedSeq(slice);
            final int maxIndex = source.length() - _slice.length();
            while (index <= maxIndex) {
                int indexOfSlice = findSlice(source, _slice, index, maxIndex);
                if (indexOfSlice < 0) {
                    return result;
                }
                if (indexOfSlice <= end) {
                    result = indexOfSlice;
                    index = indexOfSlice + 1;
                } else {
                    return result;
                }
            }
            return result;
        }

        private static <T> int findSlice(IndexedSeq<T> source, IndexedSeq<T> slice, int index, int maxIndex) {
            while (index <= maxIndex) {
                if (source.startsWith(slice, index)) {
                    return index;
                }
                index++;
            }
            return -1;
        }

        @SuppressWarnings("unchecked")
        private static <T> IndexedSeq<T> toIndexedSeq(Iterable<? extends T> iterable) {
            return (iterable instanceof IndexedSeq) ? (IndexedSeq<T>) iterable : Vector.ofAll(iterable);
        }
    }

    interface Search {
        
        static <T> int binarySearch(IndexedSeq<T> seq, IntUnaryOperator comparison) {
            int low = 0;
            int high = seq.size() - 1;
            while (low <= high) {
                final int mid = (low + high) >>> 1;
                final int cmp = comparison.applyAsInt(mid);
                if (cmp < 0) {
                    low = mid + 1;
                } else if (cmp > 0) {
                    high = mid - 1;
                } else {
                    return mid;
                }
            }
            return -(low + 1);
        }
    }
}
