package org.checkerframework.common.value.util;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * The Range class models a 64-bit two's-complement integral interval, such as all integers between
 * 1 and 10, inclusive. Ranges are immutable.
 */
public class Range {

    /** The lower bound of the interval, inclusive. */
    public final long from;

    /** The upper bound of the interval, inclusive. */
    public final long to;

    /**
     * Should ranges take overflow into account or ignore it?
     *
     * <ul>
     *   <li>If {@code ignoreOverflow} is true, then operations that would result in more than the
     *       max value are clipped to the max value (and similarly for the min).
     *   <li>If {@code ignoreOverflow} is false, then operations that would result in more than the
     *       max wrap around according to the rules of twos-complement arithmetic and produce a
     *       smaller value (and similarly for the min).
     * </ul>
     *
     * <p>Any checker that uses this library should set this field. By default, this field is set to
     * false (meaning overflow is taken into account), but a previous checker might have set it to
     * true.
     *
     * <p>A static field is used because passing an instance field throughout the class bloats the
     * code.
     */
    public static boolean ignoreOverflow = false;

    /** A range containing all possible 64-bit values. */
    public static final Range LONG_EVERYTHING = new Range(Long.MIN_VALUE, Long.MAX_VALUE);

    /** A range containing all possible 32-bit values. */
    public static final Range INT_EVERYTHING = new Range(Integer.MIN_VALUE, Integer.MAX_VALUE);

    /** A range containing all possible 16-bit values. */
    public static final Range SHORT_EVERYTHING = new Range(Short.MIN_VALUE, Short.MAX_VALUE);

    /** A range containing all possible char values. */
    public static final Range CHAR_EVERYTHING = new Range(Character.MIN_VALUE, Character.MAX_VALUE);

    /** A range containing all possible 8-bit values. */
    public static final Range BYTE_EVERYTHING = new Range(Byte.MIN_VALUE, Byte.MAX_VALUE);

    /** The empty range. */
    public static final Range NOTHING = new Range();

    /** An alias to the range containing all possible 64-bit values. */
    public static final Range EVERYTHING = LONG_EVERYTHING;

    /**
     * Create a Range from a collection of Longs.
     *
     * @param collection from which min and max values will be used as from and to values in the
     *     range
     */
    public Range(Collection<Long> collection) {
        this(Collections.min(collection), Collections.max(collection));
    }

    /**
     * Create a Range from a collection of Integers.
     *
     * @param list from which min and max values will be used as from and to values in the range
     */
    // The formal parameter type cannot be Collection<Integer> because that would erase to the same
    // raw types as the Range(Collection<Long>) constructor.
    public Range(List<Integer> list) {
        this(Collections.min(list), Collections.max(list));
    }

    /**
     * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}.
     *
     * @param from the lower bound (inclusive)
     * @param to the upper bound (inclusive)
     */
    public Range(long from, long to) {
        if (!(from <= to)) {
            throw new IllegalArgumentException(String.format("Invalid Range: %s %s", from, to));
        }
        this.from = from;
        this.to = to;
    }

    /** Creates the singleton empty range. */
    private Range() {
        this.from = Long.MAX_VALUE;
        this.to = Long.MIN_VALUE;
    }

    /**
     * Returns a range with its bounds specified by two parameters, {@code from} and {@code to}. If
     * {@code from} is greater than {@code to}, returns {@link #NOTHING}.
     *
     * @param from the lower bound (inclusive)
     * @param to the upper bound (inclusive)
     */
    private static Range createRangeOrNothing(long from, long to) {
        if (from <= to) {
            return new Range(from, to);
        } else {
            return NOTHING;
        }
    }

    /**
     * Returns a range with its bounds specified by two parameters, {@code from} and {@code to}. If
     * {@code from} is greater than {@code to}, returns {@link #EVERYTHING}.
     *
     * @param from the lower bound (inclusive)
     * @param to the upper bound (inclusive)
     * @return newly-created Range or EVERYTHING
     */
    private static Range createRangeOrEverything(long from, long to) {
        if (from <= to) {
            return new Range(from, to);
        } else {
            return EVERYTHING;
        }
    }

    /**
     * Returns the number of values in this range.
     *
     * @return how many values are in the range
     */
    private long width() {
        return to - from + 1;
    }

    @Override
    public String toString() {
        if (this.isNothing()) {
            return "[]";
        } else {
            return String.format("[%s..%s]", from, to);
        }
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (obj instanceof Range) {
            return equalsRange((Range) obj);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hash(from, to);
    }

    /**
     * Compare two ranges in a type safe manner for equality without incurring the cost of an
     * instanceof check such as equals(Object) does.
     *
     * @param range to compare against
     * @return true for ranges that match from and to respectively
     */
    private boolean equalsRange(Range range) {
        return from == range.from && to == range.to;
    }

    /** Return true if this range contains every {@code long} value. */
    public boolean isLongEverything() {
        return equalsRange(LONG_EVERYTHING);
    }

    /** Return true if this range contains every {@code int} value. */
    public boolean isIntEverything() {
        return equalsRange(INT_EVERYTHING);
    }

    /** Return true if this range contains every {@code short} value. */
    public boolean isShortEverything() {
        return equalsRange(SHORT_EVERYTHING);
    }

    /** Return true if this range contains every {@code char} value. */
    public boolean isCharEverything() {
        return equalsRange(CHAR_EVERYTHING);
    }

    /** Return true if this range contains every {@code byte} value. */
    public boolean isByteEverything() {
        return equalsRange(BYTE_EVERYTHING);
    }

    /** Return true if this range contains no values. */
    public boolean isNothing() {
        return this == NOTHING;
    }

    /** The number of values representable in 32 bits: 2^32 or 1&lt;&lt;32. */
    private static final long INT_WIDTH = INT_EVERYTHING.width();

    /**
     * Converts this range to a 32-bit integral range.
     *
     * <p>If {@link #ignoreOverflow} is true and one of the bounds is outside the Integer range,
     * then that bound is set to the bound of the Integer range.
     *
     * <p>If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full
     * range of the Integer class, return INT_EVERYTHING.
     *
     * <p>If {@link #ignoreOverflow} is false and the bounds of this range are not representable as
     * 32-bit integers, convert the bounds to Integer type in accordance with Java twos-complement
     * overflow rules, e.g., Integer.MAX_VALUE + 1 is converted to Integer.MIN_VALUE.
     */
    public Range intRange() {
        if (this.isNothing()) {
            return this;
        }
        if (ignoreOverflow) {
            return new Range(Math.max(from, Integer.MIN_VALUE), Math.min(to, Integer.MAX_VALUE));
        }
        if (this.isWiderThan(INT_WIDTH)) {
            return INT_EVERYTHING;
        }
        int intFrom = (int) this.from;
        int intTo = (int) this.to;
        if (intFrom <= intTo) {
            return new Range(intFrom, intTo);
        }
        return INT_EVERYTHING;
    }

    /** The number of values representable in 16 bits: 2^16 or 1&lt;&lt;16. */
    private static final long SHORT_WIDTH = SHORT_EVERYTHING.width();

    /**
     * Converts a this range to a 16-bit short range.
     *
     * <p>If {@link #ignoreOverflow} is true and one of the bounds is outside the Short range, then
     * that bound is set to the bound of the Short range.
     *
     * <p>If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full
     * range of the Short class, return SHORT_EVERYTHING.
     *
     * <p>If {@link #ignoreOverflow} is false and the bounds of this range are not representable as
     * 16-bit integers, convert the bounds to Short type in accordance with Java twos-complement
     * overflow rules, e.g., Short.MAX_VALUE + 1 is converted to Short.MIN_VALUE.
     */
    public Range shortRange() {
        if (this.isNothing()) {
            return this;
        }
        if (ignoreOverflow) {
            return new Range(Math.max(from, Short.MIN_VALUE), Math.min(to, Short.MAX_VALUE));
        }
        if (this.isWiderThan(SHORT_WIDTH)) {
            // short is promoted to int before the operation so no need for explicit casting
            return SHORT_EVERYTHING;
        }
        short shortFrom = (short) this.from;
        short shortTo = (short) this.to;
        if (shortFrom <= shortTo) {
            return new Range(shortFrom, shortTo);
        }
        return SHORT_EVERYTHING;
    }

    /** The number of values representable in char: */
    private static final long CHAR_WIDTH = CHAR_EVERYTHING.width();

    /**
     * Converts this range to a char range.
     *
     * <p>If {@link #ignoreOverflow} is true and one of the bounds is outside the Character range,
     * then that bound is set to the bound of the Character range.
     *
     * <p>If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full
     * range of the Character class, return CHAR_EVERYTHING.
     *
     * <p>If {@link #ignoreOverflow} is false and the bounds of this range are not representable as
     * 8-bit integers, convert the bounds to Character type in accordance with Java overflow rules
     * (twos-complement), e.g., Character.MAX_VALUE + 1 is converted to Character.MIN_VALUE.
     */
    public Range charRange() {
        if (this.isNothing()) {
            return this;
        }
        if (ignoreOverflow) {
            return new Range(
                    Math.max(from, Character.MIN_VALUE), Math.min(to, Character.MAX_VALUE));
        }
        if (this.isWiderThan(CHAR_WIDTH)) {
            // char is promoted to int before the operation so no need for explicit casting
            return CHAR_EVERYTHING;
        }
        char charFrom = (char) this.from;
        char charTo = (char) this.to;
        if (charFrom <= charTo) {
            return new Range(charFrom, charTo);
        }
        return CHAR_EVERYTHING;
    }

    /** The number of values representable in 8 bits: 2^8 or 1&lt;&lt;8. */
    private static final long BYTE_WIDTH = BYTE_EVERYTHING.width();

    /**
     * Converts a this range to a 8-bit byte range.
     *
     * <p>If {@link #ignoreOverflow} is true and one of the bounds is outside the Byte range, then
     * that bound is set to the bound of the Byte range.
     *
     * <p>If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full
     * range of the Byte class, return BYTE_EVERYTHING.
     *
     * <p>If {@link #ignoreOverflow} is false and the bounds of this range are not representable as
     * 8-bit integers, convert the bounds to Byte type in accordance with Java twos-complement
     * overflow rules, e.g., Byte.MAX_VALUE + 1 is converted to Byte.MIN_VALUE.
     */
    public Range byteRange() {
        if (this.isNothing()) {
            return this;
        }
        if (ignoreOverflow) {
            return new Range(Math.max(from, Byte.MIN_VALUE), Math.min(to, Byte.MAX_VALUE));
        }
        if (this.isWiderThan(BYTE_WIDTH)) {
            // byte is promoted to int before the operation so no need for explicit casting
            return BYTE_EVERYTHING;
        }
        byte byteFrom = (byte) this.from;
        byte byteTo = (byte) this.to;
        if (byteFrom <= byteTo) {
            return new Range(byteFrom, byteTo);
        }
        return BYTE_EVERYTHING;
    }

    /**
     * Returns true if the element is contained in this range.
     *
     * @param element the value to seek
     * @return true if {@code element} is in this range
     */
    public boolean contains(long element) {
        return from <= element && element <= to;
    }

    /**
     * Returns true if the other range is contained in this range.
     *
     * @param other the range that might be within this one
     * @return true if {@code other} is within this range
     */
    public boolean contains(Range other) {
        return other.isWithin(from, to);
    }

    /**
     * Returns the smallest range that includes all values contained in either of the two ranges. We
     * call this the union of two ranges.
     *
     * @param right a range to union with this range
     * @return a range resulting from the union of the specified range and this range
     */
    public Range union(Range right) {
        if (this.isNothing()) {
            return right;
        } else if (right.isNothing()) {
            return this;
        }

        long resultFrom = Math.min(from, right.from);
        long resultTo = Math.max(to, right.to);
        return new Range(resultFrom, resultTo);
    }

    /**
     * Returns the smallest range that includes all values contained in both of the two ranges. We
     * call this the intersection of two ranges. If there is no overlap between the two ranges,
     * returns an empty range.
     *
     * @param right the range to intersect with this range
     * @return a range resulting from the intersection of the specified range and this range
     */
    public Range intersect(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        long resultFrom = Math.max(from, right.from);
        long resultTo = Math.min(to, right.to);
        return createRangeOrNothing(resultFrom, resultTo);
    }

    /** @return the range with the lowest to and from values of this range and the passed range */
    public Range min(Range other) {
        return new Range(Math.min(this.from, other.from), Math.min(this.to, other.to));
    }

    /** @return the range with the highest to and from values of this range and the passed range */
    public Range max(Range other) {
        return new Range(Math.max(this.from, other.from), Math.max(this.to, other.to));
    }

    /**
     * Returns the smallest range that includes all possible values resulting from adding an
     * arbitrary value in the specified range to an arbitrary value in this range. We call this the
     * addition of two ranges.
     *
     * @param right a range to be added to this range
     * @return the range resulting from the addition of the specified range and this range
     */
    public Range plus(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        if (this.isWithinHalfLong() && right.isWithinHalfLong()) {
            // This bound is adequate to guarantee no overflow when using long to evaluate
            long resultFrom = from + right.from;
            long resultTo = to + right.to;
            if (from > to) {
                return Range.EVERYTHING;
            } else {
                return new Range(resultFrom, resultTo);
            }
        } else {
            BigInteger bigFrom = BigInteger.valueOf(from).add(BigInteger.valueOf(right.from));
            BigInteger bigTo = BigInteger.valueOf(to).add(BigInteger.valueOf(right.to));
            return bigRangeToLongRange(bigFrom, bigTo);
        }
    }

    /**
     * Returns the smallest range that includes all possible values resulting from subtracting an
     * arbitrary value in the specified range from an arbitrary value in this range. We call this
     * the subtraction of two ranges.
     *
     * @param right the range to be subtracted from this range
     * @return the range resulting from subtracting the specified range from this range
     */
    public Range minus(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        if (this.isWithinHalfLong() && right.isWithinHalfLong()) {
            // This bound is adequate to guarantee no overflow when using long to evaluate
            long resultFrom = from - right.to;
            long resultTo = to - right.from;
            return new Range(resultFrom, resultTo);
        } else {
            BigInteger bigFrom = BigInteger.valueOf(from).subtract(BigInteger.valueOf(right.to));
            BigInteger bigTo = BigInteger.valueOf(to).subtract(BigInteger.valueOf(right.from));
            return bigRangeToLongRange(bigFrom, bigTo);
        }
    }

    /**
     * Returns the smallest range that includes all possible values resulting from multiplying an
     * arbitrary value in the specified range by an arbitrary value in this range. We call this the
     * multiplication of two ranges.
     *
     * @param right the specified range to be multiplied by this range
     * @return the range resulting from multiplying the specified range by this range
     */
    public Range times(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        // These bounds are adequate:  Integer.MAX_VALUE^2 is still a bit less than Long.MAX_VALUE.
        if (this.isWithinInteger() && right.isWithinInteger()) {
            List<Long> possibleValues =
                    Arrays.asList(
                            from * right.from, from * right.to, to * right.from, to * right.to);
            return new Range(possibleValues);
        } else {
            final BigInteger bigLeftFrom = BigInteger.valueOf(from);
            final BigInteger bigRightFrom = BigInteger.valueOf(right.from);
            final BigInteger bigRightTo = BigInteger.valueOf(right.to);
            final BigInteger bigLeftTo = BigInteger.valueOf(to);
            List<BigInteger> bigPossibleValues =
                    Arrays.asList(
                            bigLeftFrom.multiply(bigRightFrom),
                            bigLeftFrom.multiply(bigRightTo),
                            bigLeftTo.multiply(bigRightFrom),
                            bigLeftTo.multiply(bigRightTo));
            return bigRangeToLongRange(
                    Collections.min(bigPossibleValues), Collections.max(bigPossibleValues));
        }
    }

    /**
     * Returns the smallest range that includes all possible values resulting from dividing (integer
     * division) an arbitrary value in this range by an arbitrary value in the specified range. We
     * call this the division of two ranges.
     *
     * @param right the specified range by which this range is divided
     * @return the range resulting from dividing this range by the specified range
     */
    public Range divide(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }
        if (right.from == 0 && right.to == 0) {
            return NOTHING;
        }
        // Special cases that involve overflow.
        // The only overflow in integer division is Long.MIN_VALUE / -1 == Long.MIN_VALUE.
        if (from == Long.MIN_VALUE && right.contains(-1)) {
            // The values in the right range are all negative because right does not contain 0 but
            // does contain 1.
            if (from != to) {
                // Special case 1:
                // This range contains Long.MIN_VALUE and Long.MIN_VALUE + 1, which makes the
                // result range EVERYTHING.
                return EVERYTHING;
            } else if (right.from != right.to) {
                // Special case 2:
                // This range contains only Long.MIN_VALUE, and the right range contains at least -1
                // and -2. The result range is from Long.MIN_VALUE to Long.MIN_VALUE / -2.
                return new Range(Long.MIN_VALUE, Long.MIN_VALUE / -2);
            } else {
                // Special case 3:
                // This range contains only Long.MIN_VALUE, and right contains only -1.
                return new Range(Long.MIN_VALUE, Long.MIN_VALUE);
            }
        }
        // We needn't worry about the overflow issue starting from here.

        // There are 9 different cases:
        // (note: pos=positive, neg=negative, unk=unknown sign, np=non-positive, nn=non-negative)
        long resultFrom;
        long resultTo;
        if (from > 0) { // this range is positive
            if (right.from >= 0) {
                // 1. right: nn
                resultFrom = from / Math.max(right.to, 1);
                resultTo = to / Math.max(right.from, 1);
            } else if (right.to <= 0) {
                // 2. right: np
                resultFrom = to / Math.min(right.to, -1);
                resultTo = from / Math.min(right.from, -1);
            } else {
                // 3. right: unk; values include -1 and 1
                resultFrom = -to;
                resultTo = to;
            }
        } else if (to < 0) { // this range is negative
            if (right.from >= 0) {
                // 4. right: nn
                resultFrom = from / Math.max(right.from, 1);
                resultTo = to / Math.max(right.to, 1);
            } else if (right.to <= 0) {
                // 5. right: np
                resultFrom = to / Math.min(right.from, -1);
                resultTo = from / Math.min(right.to, -1);
            } else {
                // 6. right: unk; values include -1 and 1
                resultFrom = from;
                resultTo = -from;
            }
        } else { // this range spans both signs
            if (right.from >= 0) {
                // 7. right: nn
                resultFrom = from / Math.max(right.from, 1);
                resultTo = to / Math.max(right.from, 1);
            } else if (right.to <= 0) {
                // 8. right: np
                resultFrom = to / Math.min(right.to, -1);
                resultTo = from / Math.min(right.to, -1);
            } else {
                // 9. right: unk; values include -1 and 1
                resultFrom = Math.min(from, -to);
                resultTo = Math.max(-from, to);
            }
        }
        return new Range(resultFrom, resultTo);
    }

    /**
     * Returns a range that includes all possible values of the remainder of dividing an arbitrary
     * value in this range by an arbitrary value in the specified range.
     *
     * <p>In the current implementation, the result might not be the smallest range that includes
     * all the possible values.
     *
     * @param right the specified range by which this range is divided
     * @return the range of the remainder of dividing this range by the specified range
     */
    public Range remainder(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }
        if (right.from == 0 && right.to == 0) {
            return NOTHING;
        }
        // Special cases that would cause overflow if we use the general method below
        if (right.from == Long.MIN_VALUE) {
            Range range;
            // The value Long.MIN_VALUE as a divisor needs special handling as follows:
            if (from == Long.MIN_VALUE) {
                if (to == Long.MIN_VALUE) {
                    // This range only contains Long.MIN_VALUE, so the result range is {0}.
                    range = new Range(0, 0);
                } else { // (to > Long.MIN_VALUE)
                    // When this range contains Long.MIN_VALUE, which would have a remainder of 0 if
                    // divided by Long.MIN_VALUE, the result range is {0} unioned with [from + 1,
                    // to]
                    range = new Range(from + 1, to).union(new Range(0, 0));
                }
            } else { // (from > Long.MIN_VALUE)
                // When this range doesn't contain Long.MIN_VALUE, the remainder of each value
                // in this range divided by Long.MIN_VALUE is this value itself. Therefore the
                // result range is this range itself.
                range = this;
            }
            // If right.to > Long.MIN_VALUE, union the previous result with the result of range
            // [right.from + 1, right.to] divided by this range, which can be calculated using
            // the general method (see below)
            if (right.to > Long.MIN_VALUE) {
                Range rangeAdditional = this.remainder(new Range(right.from + 1, right.to));
                range = range.union(rangeAdditional);
            }
            return range;
        }
        // General method:
        // Calculate range1: the result range of this range divided by EVERYTHING. For example,
        // if this range is [3, 5], then the result range would be [0, 5]. If this range is [-3, 4],
        // then the result range would be [-3, 4]. In general, the result range is {0} union with
        // this range excluding the value Long.MIN_VALUE.
        Range range1 =
                new Range(Math.max(Long.MIN_VALUE + 1, from), Math.max(Long.MIN_VALUE + 1, to))
                        .union(new Range(0, 0));
        // Calculate range2: the result range of range EVERYTHING divided by the right range. For
        // example, if the right range is [-5, 3], then the result range would be [-4, 4]. If the
        // right range is [3, 6], then the result range would be [-5, 5]. In general, the result
        // range is calculated as following:
        long maxAbsolute = Math.max(Math.abs(right.from), Math.abs(right.to));
        Range range2 = new Range(-maxAbsolute + 1, maxAbsolute - 1);
        // Since range1 and range2 are both super sets of the minimal result range, we return the
        // intersection of range1 and range2, which is correct (super set) and precise enough.
        return range1.intersect(range2);
    }

    /**
     * Returns a range that includes all possible values resulting from left shifting an arbitrary
     * value in this range by an arbitrary number of bits in the specified range. We call this the
     * left shift of a range.
     *
     * @param right the range of bits by which this range is left shifted
     * @return the range resulting from left shifting this range by the specified range
     */
    public Range shiftLeft(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        // Shifting operations in Java are depending on the type of the left-hand operand:
        // If the left-hand operand is int  type, only the 5 lowest-order bits of the right-hand
        // operand are used.
        // If the left-hand operand is long type, only the 6 lowest-order bits of the right-hand
        // operand are used.
        // For example, while 1 << -1== 1 << 31, 1L << -1 == 1L << 63.
        // For ths reason, we restrict the shift-bits to analyze in [0. 31] and give up the analysis
        // when out of this range.
        //
        // Other possible solutions:
        // 1. create different methods for int type and long type and use them accordingly
        // 2. add an additional boolean parameter to indicate the type of the left-hand operand
        //
        // see https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.19 for more
        // detail.
        if (right.isWithin(0, 31)) {
            if (this.isWithinInteger()) {
                // This bound is adequate to guarantee no overflow when using long to evaluate
                long resultFrom = from << (from >= 0 ? right.from : right.to);
                long resultTo = to << (to >= 0 ? right.to : right.from);
                return new Range(resultFrom, resultTo);
            } else {
                BigInteger bigFrom =
                        BigInteger.valueOf(from)
                                .shiftLeft(from >= 0 ? (int) right.from : (int) right.to);
                BigInteger bigTo =
                        BigInteger.valueOf(to)
                                .shiftLeft(to >= 0 ? (int) right.to : (int) right.from);
                return bigRangeToLongRange(bigFrom, bigTo);
            }
        } else {
            // In other cases, we give up on the calculation and return EVERYTHING (rare in
            // practice).
            return EVERYTHING;
        }
    }

    /**
     * Returns a range that includes all possible values resulting from signed right shifting an
     * arbitrary value in this range by an arbitrary number of bits in the specified range. We call
     * this the signed right shift operation of a range.
     *
     * @param right the range of bits by which this range is signed right shifted
     * @return the range resulting from signed right shifting this range by the specified range
     */
    public Range signedShiftRight(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        if (this.isWithinInteger() && right.isWithin(0, 31)) {
            // This bound is adequate to guarantee no overflow when using long to evaluate
            long resultFrom = from >> (from >= 0 ? right.to : right.from);
            long resultTo = to >> (to >= 0 ? right.from : right.to);
            return new Range(resultFrom, resultTo);
        } else {
            // Signed shift right operation for long type cannot be simulated with BigInteger.
            // Give up on the calculation and return EVERYTHING instead.
            return EVERYTHING;
        }
    }

    /**
     * When this range only contains non-negative values, the refined result should be the same as
     * {@link #signedShiftRight(Range)}. We give up the analysis when this range contains negative
     * value(s).
     */
    public Range unsignedShiftRight(Range right) {
        if (this.from >= 0) {
            return signedShiftRight(right);
        }

        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        return EVERYTHING;
    }

    /**
     * Returns a range that includes all possible values resulting from performing the bitwise and
     * operation on a value in this range by a mask in the specified range. We call this the bitwise
     * and operation of a range.
     *
     * <p>The current implementation is conservative: it only refines the cases where the range of
     * mask represents a constant. In other cases, it gives up on the refinement and returns {@code
     * EVERYTHING} instead.
     *
     * @param right the range of mask of the bitwise and operation
     * @return the range resulting from the bitwise and operation of this range and the specified
     *     range of mask
     */
    public Range bitwiseAnd(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        // We only refine the cases where the range of mask represent a constant.
        // Recall these two's-complement facts:
        //   11111111  represents  -1
        //   10000000  represents  MIN_VALUE

        Range constant = null;
        Range variable = null;
        if (right.isConstant()) {
            constant = right;
            variable = this;
        } else if (this.isConstant()) {
            constant = this;
            variable = right;
        }

        if (constant != null) {
            long mask = constant.from;
            if (mask >= 0) {
                // Sign bit of mask is 0.  The elements in the result range must be positive, and
                // the result range is upper-bounded by the mask.
                if (variable.from >= 0) {
                    // Case 1.1: The result range is upper-bounded by the upper bound of this range.
                    return new Range(0, Math.min(mask, variable.to));
                } else if (variable.to < 0) {
                    // Case 1.2: The result range is upper-bounded by the upper bound of this range
                    // after ignoring the sign bit. The upper bound of this range has the most bits
                    // (of the highest place values) set to 1.
                    return new Range(0, Math.min(mask, noSignBit(variable.to)));
                } else {
                    // Case 1.3:  Since this range contains -1, the upper bound of this range after
                    // ignoring the sign bit is Long.MAX_VALUE and thus doesn't contribute to
                    // further refinement.
                    return new Range(0, mask);
                }
            } else {
                // Sign bit of mask is 1.
                if (variable.from >= 0) {
                    // Case 2.1: Similar to case 1.1 except that the sign bit of the mask can be
                    // ignored.
                    return new Range(0, Math.min(noSignBit(mask), variable.to));
                } else if (variable.to < 0) {
                    // Case 2.2: The sign bit of the elements in the result range must be 1.
                    // Therefore the lower bound of the result range is Long.MIN_VALUE (when all
                    // 1-bits are mismatched between the mask and the element in this range). The
                    // result range is also upper-bounded by this mask itself and the upper bound of
                    // this range.  (Because more set bits means a larger number -- still negative,
                    // but closer to 0.)
                    return new Range(Long.MIN_VALUE, Math.min(mask, variable.to));
                } else {
                    // Case 2.3: Similar to case 2.2 except that the elements in this range could
                    // be positive, and thus the result range is upper-bounded by the upper bound
                    // of this range and the mask after ignoring the sign bit.
                    return new Range(Long.MIN_VALUE, Math.min(noSignBit(mask), variable.to));
                }
            }
        }

        return EVERYTHING;
    }

    /** Return the argument, with its sign bit zeroed out. */
    private long noSignBit(Long mask) {
        return mask & (-1L >>> 1);
    }

    /** We give up the analysis for bitwise OR operation. */
    public Range bitwiseOr(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        return EVERYTHING;
    }

    /** We give up the analysis for bitwise XOR operation. */
    public Range bitwiseXor(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        return EVERYTHING;
    }

    /**
     * Returns the range of a variable that falls within this range after applying the unary plus
     * operation (which is a no-op).
     *
     * @return this range
     */
    public Range unaryPlus() {
        return this;
    }

    /**
     * Returns the range of a variable that falls within this range after applying the unary minus
     * operation.
     *
     * @return the resulted range of applying unary minus on an arbitrary value in this range
     */
    public Range unaryMinus() {
        if (this.isNothing()) {
            return NOTHING;
        }

        if (from == Long.MIN_VALUE && from != to) {
            // the only case that needs special handling because of overflow
            return EVERYTHING;
        }

        return new Range(-to, -from);
    }

    /**
     * Returns the range of a variable that falls within this range after applying the bitwise
     * complement operation.
     *
     * @return the resulting range of applying bitwise complement on an arbitrary value in this
     *     range
     */
    public Range bitwiseComplement() {
        if (this.isNothing()) {
            return NOTHING;
        }

        return new Range(~to, ~from);
    }

    /**
     * Refines this range to reflect that some value in it can be less than a value in the given
     * range. This is used for calculating the control-flow-refined result of the &lt; operator. For
     * example:
     *
     * <pre>
     * <code>
     *    {@literal @}IntRange(from = 0, to = 10) int a;
     *    {@literal @}IntRange(from = 3, to = 7) int b;
     *     ...
     *     if (a &lt; b) {
     *         // range of <i>a</i> is now refined to [0, 6] because a value in range [7, 10]
     *         // cannot be smaller than variable <i>b</i> with range [3, 7].
     *         ...
     *     }
     * </code>
     * </pre>
     *
     * Use the {@link #refineGreaterThanEq(Range)} method if you are also interested in refining the
     * range of {@code b} in the code above.
     *
     * @param right the specified {@code Range} to compare with
     * @return the refined {@code Range}
     */
    public Range refineLessThan(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        if (right.to == Long.MIN_VALUE) {
            return NOTHING;
        }

        long resultTo = Math.min(to, right.to - 1);
        return createRangeOrNothing(from, resultTo);
    }

    /**
     * Refines this range to reflect that some value in it can be less than or equal to a value in
     * the given range. This is used for calculating the control-flow-refined result of the &lt;=
     * operator. For example:
     *
     * <pre>
     * <code>
     *    {@literal @}IntRange(from = 0, to = 10) int a;
     *    {@literal @}IntRange(from = 3, to = 7) int b;
     *     ...
     *     if (a &lt;= b) {
     *         // range of <i>a</i> is now refined to [0, 7] because a value in range [8, 10]
     *         // cannot be less than or equal to variable <i>b</i> with range [3, 7].
     *         ...
     *     }
     * </code>
     * </pre>
     *
     * Use the {@link #refineGreaterThan(Range)} method if you are also interested in refining the
     * range of {@code b} in the code above.
     *
     * @param right the specified {@code Range} to compare with
     * @return the refined {@code Range}
     */
    public Range refineLessThanEq(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        long resultTo = Math.min(to, right.to);
        return createRangeOrNothing(from, resultTo);
    }

    /**
     * Refines this range to reflect that some value in it can be greater than a value in the given
     * range. This is used for calculating the control-flow-refined result of the &gt; operator. For
     * example:
     *
     * <pre>
     * <code>
     *    {@literal @}IntRange(from = 0, to = 10) int a;
     *    {@literal @}IntRange(from = 3, to = 7) int b;
     *     ...
     *     if (a &gt; b) {
     *         // range of <i>a</i> is now refined to [4, 10] because a value in range [0, 3]
     *         // cannot be greater than variable <i>b</i> with range [3, 7].
     *         ...
     *     }
     * </code>
     * </pre>
     *
     * Use the {@link #refineLessThanEq(Range)} method if you are also interested in refining the
     * range of {@code b} in the code above.
     *
     * @param right the specified {@code Range} to compare with
     * @return the refined {@code Range}
     */
    public Range refineGreaterThan(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        if (right.from == Long.MAX_VALUE) {
            return NOTHING;
        }

        long resultFrom = Math.max(from, right.from + 1);
        return createRangeOrNothing(resultFrom, to);
    }

    /**
     * Refines this range to reflect that some value in it can be greater than or equal to a value
     * in the given range. This is used for calculating the control-flow-refined result of the &gt;=
     * operator. For example:
     *
     * <pre>
     * <code>
     *    {@literal @}IntRange(from = 0, to = 10) int a;
     *    {@literal @}IntRange(from = 3, to = 7) int b;
     *     ...
     *     if (a &gt;= b) {
     *         // range of <i>a</i> is now refined to [3, 10] because a value in range [0, 2]
     *         // cannot be greater than or equal to variable <i>b</i> with range [3, 7].
     *         ...
     *     }
     * </code>
     * </pre>
     *
     * Use the {@link #refineLessThan(Range)} method if you are also interested in refining the
     * range of {@code b} in the code above.
     *
     * @param right the specified {@code Range} to compare with
     * @return the refined {@code Range}
     */
    public Range refineGreaterThanEq(Range right) {
        if (this.isNothing() || right.isNothing()) {
            return NOTHING;
        }

        long resultFrom = Math.max(from, right.from);
        return createRangeOrNothing(resultFrom, to);
    }

    /**
     * Refines this range to reflect that some value in it can be equal to a value in the given
     * range. This is used for calculating the control-flow-refined result of the == operator. For
     * example:
     *
     * <pre>
     * <code>
     *    {@literal @}IntRange(from = 0, to = 10) int a;
     *    {@literal @}IntRange(from = 3, to = 15) int b;
     *     ...
     *     if (a == b) {
     *         // range of <i>a</i> is now refined to [3, 10] because a value in range [0, 2]
     *         // cannot be equal to variable <i>b</i> with range [3, 15].
     *         ...
     *     }
     * </code>
     * </pre>
     *
     * @param right the specified {@code Range} to compare with
     * @return the refined {@code Range}
     */
    public Range refineEqualTo(Range right) {
        return this.intersect(right);
    }

    /**
     * Refines this range to reflect that some value in it must not be equal to a value in the given
     * range. This only changes the range if the given range (right) contains exactly one integer,
     * and that integer is one of the bounds of this range. This is used for calculating the
     * control-flow-refined result of the != operator. For example:
     *
     * <pre>
     * <code>
     *    {@literal @}IntRange(from = 0, to = 10) int a;
     *    {@literal @}IntRange(from = 0, to = 0) int b;
     *     ...
     *     if (a != b) {
     *         // range of <i>a</i> is now refined to [1, 10] because it cannot
     *         // be zero.
     *         ...
     *     }
     * </code>
     * </pre>
     *
     * @param right the specified {@code Range} to compare with
     * @return the refined {@code Range}
     */
    public Range refineNotEqualTo(Range right) {
        if (right.isConstant()) {
            if (this.to == right.to) {
                return new Range(this.from, this.to - 1);
            } else if (this.from == right.from) {
                return new Range(this.from + 1, this.to);
            }
        }
        return this;
    }

    /**
     * Determines if the range is wider than a given value, i.e., if the number of possible values
     * enclosed by this range is more than the given value.
     *
     * @param value the value to compare with
     * @return true if wider than the given value
     */
    public boolean isWiderThan(long value) {
        if (this.isWithin((Long.MIN_VALUE >> 1) + 1, Long.MAX_VALUE >> 1)) {
            // This bound is adequate to guarantee no overflow when using long to evaluate.
            // Long.MIN_VALUE >> 1 + 1 = -4611686018427387903
            // Long.MAX_VALUE >> 1 = 4611686018427387903
            return width() > value;
        } else {
            return BigInteger.valueOf(to)
                            .subtract(BigInteger.valueOf(from))
                            .add(BigInteger.ONE)
                            .compareTo(BigInteger.valueOf(value))
                    > 0;
        }
    }

    /** Determines if this range represents a constant value. */
    public boolean isConstant() {
        return from == to;
    }

    /**
     * Determines if this range is completely contained in the range specified by the given lower
     * bound inclusive and upper bound inclusive.
     *
     * @param lb lower bound for the range that might contain this one
     * @param ub upper bound for the range that might contain this one
     * @return true if this range is within the given bounds
     */
    public boolean isWithin(long lb, long ub) {
        assert lb <= ub;
        return lb <= from && to <= ub;
    }

    /**
     * Determines if this range is contained inclusively between Long.MIN_VALUE/2 and
     * Long.MAX_VALUE/2. Note: Long.MIN_VALUE/2 != -Long.MAX_VALUE/2
     */
    private boolean isWithinHalfLong() {
        return isWithin(Long.MIN_VALUE >> 1, Long.MAX_VALUE >> 1);
    }

    /**
     * Determines if this range is completely contained in the scope of the Integer type.
     *
     * @return true if the range is contained within the Integer range inclusive
     */
    public boolean isWithinInteger() {
        return isWithin(Integer.MIN_VALUE, Integer.MAX_VALUE);
    }

    /** Long.MIN_VALUE, as a BigInteger. */
    private static final BigInteger BIG_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
    /** Long.MAX_VALUE, as a BigInteger. */
    private static final BigInteger BIG_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
    /** The number of Long values, as a BigInteger. */
    private static final BigInteger BIG_LONG_WIDTH =
            BIG_LONG_MAX_VALUE.subtract(BIG_LONG_MIN_VALUE).add(BigInteger.ONE);

    /**
     * Converts a range with BigInteger type bounds to a range with Long type bounds.
     *
     * <p>If the BigInteger range is wider than the full range of the Long class, return EVERYTHING.
     *
     * <p>If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is
     * false, convert the bounds to Long type in accordance with Java twos-complement overflow
     * rules, e.g., Long.MAX_VALUE + 1 is converted to Long.MIN_VALUE.
     *
     * <p>If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is
     * true, convert the bound that is outside Long's range to max/min value of a Long.
     *
     * @param bigFrom the lower bound of the BigInteger range
     * @param bigTo the upper bound of the BigInteger range
     * @return a range with Long type bounds converted from the BigInteger range
     */
    private static Range bigRangeToLongRange(BigInteger bigFrom, BigInteger bigTo) {
        if (ignoreOverflow) {
            bigFrom = bigFrom.max(BIG_LONG_MIN_VALUE);
            bigTo = bigTo.min(BIG_LONG_MAX_VALUE);
        } else {
            BigInteger bigWidth = bigTo.subtract(bigFrom).add(BigInteger.ONE);
            if (bigWidth.compareTo(BIG_LONG_WIDTH) > 0) {
                return EVERYTHING;
            }
        }
        long longFrom = bigFrom.longValue();
        long longTo = bigTo.longValue();
        return createRangeOrEverything(longFrom, longTo);
    }
}
