import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
import org.checkerframework.checker.index.qual.HasSubsequence;
import org.checkerframework.checker.index.qual.IndexFor;
import org.checkerframework.checker.index.qual.IndexOrHigh;
import org.checkerframework.checker.index.qual.IndexOrLow;
import org.checkerframework.checker.index.qual.LTEqLengthOf;
import org.checkerframework.checker.index.qual.LTLengthOf;
import org.checkerframework.checker.index.qual.LessThan;
import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.common.value.qual.MinLen;

/**
 * A simplified version of the Guava primitives classes (such as Bytes, Longs, Shorts, etc.) with
 * all expected warnings suppressed.
 */
public class GuavaPrimitives extends AbstractList<Short> {
    @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end")
    final short @MinLen(1) [] array;

    final @IndexFor("array") @LessThan("end") int start;
    final @Positive @LTEqLengthOf("array") int end;

    public static @IndexOrLow("#1") int indexOf(short[] array, short target) {
        return indexOf(array, target, 0, array.length);
    }

    private static @IndexOrLow("#1") @LessThan("#4") int indexOf(
            short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) {
        for (int i = start; i < end; i++) {
            if (array[i] == target) {
                return i;
            }
        }
        return -1;
    }

    private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf(
            short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) {
        for (int i = end - 1; i >= start; i--) {
            if (array[i] == target) {
                return i;
            }
        }
        return -1;
    }

    GuavaPrimitives(short @MinLen(1) [] array) {
        this(array, 0, array.length);
    }

    @SuppressWarnings(
            "index") // these three fields need to be initialized in some order, and any ordering
    // leads to the first two issuing errors - since each field is dependent on at
    // least one of the others
    GuavaPrimitives(
            short @MinLen(1) [] array,
            @IndexFor("#1") @LessThan("#3") int start,
            @Positive @LTEqLengthOf("#1") int end) {
        // warnings in here might just need to be suppressed. A single @SuppressWarnings("index") to
        // establish rep. invariant might be okay?
        this.array = array;
        this.start = start;
        this.end = end;
    }

    public @Positive @LTLengthOf(
            value = {"this", "array"},
            offset = {"-1", "start - 1"}) int
            size() { // INDEX: Annotation on a public method refers to private member.
        return end - start;
    }

    public boolean isEmpty() {
        return false;
    }

    public Short get(@IndexFor("this") int index) {
        return array[start + index];
    }

    @SuppressWarnings(
            "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 indexOf()
    public @IndexOrLow("this") int indexOf(Object target) {
        // Overridden to prevent a ton of boxing
        if (target instanceof Short) {
            int i = GuavaPrimitives.indexOf(array, (Short) target, start, end);
            if (i >= 0) {
                return i - start;
            }
        }
        return -1;
    }

    @SuppressWarnings(
            "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 lastIndexOf()
    public @IndexOrLow("this") int lastIndexOf(Object target) {
        // Overridden to prevent a ton of boxing
        if (target instanceof Short) {
            int i = GuavaPrimitives.lastIndexOf(array, (Short) target, start, end);
            if (i >= 0) {
                return i - start;
            }
        }
        return -1;
    }

    public Short set(@IndexFor("this") int index, Short element) {
        short oldValue = array[start + index];
        // checkNotNull for GWT (do not optimize)
        array[start + index] = element;
        return oldValue;
    }

    @SuppressWarnings("index") // needs https://github.com/kelloggm/checker-framework/issues/229
    public List<Short> subList(
            @IndexOrHigh("this") @LessThan("#2") int fromIndex, @IndexOrHigh("this") int toIndex) {
        int size = size();
        if (fromIndex == toIndex) {
            return Collections.emptyList();
        }
        return new GuavaPrimitives(array, start + fromIndex, start + toIndex);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder(size() * 6);
        builder.append('[').append(array[start]);
        for (int i = start + 1; i < end; i++) {
            builder.append(", ").append(array[i]);
        }
        return builder.append(']').toString();
    }
}
