package lc.mecha.math;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class represents a closed integer interval.
 *
 * @author Alex Leigh
 */
public class IntInterval implements Comparable<IntInterval>, Serializable {
    private final static Pattern p = Pattern.compile("\\d+");
    public static final String SET_DELIM = "..";
    private final int startIndex;
    private final int stopIndex;

    /**
     * Create a new interval from the provided string. The string is expected to be a simple integer if the
     * start and stop integers are the same, and, to be in the format <code>[i1..i2]</code> if they are not
     * the same. i2 is expected always to be larger than i1. For example, <code>[5..9]</code>.
     *
     * @param str The interval in strong form.
     */
    public IntInterval(String str) {
        if (!str.startsWith("[")) {
            int x = Integer.parseInt(str);
            startIndex = stopIndex = x;
        } else {
            Matcher m = p.matcher(str);
            m.find();
            startIndex = Integer.parseInt(m.group());
            m.find();
            stopIndex = Integer.parseInt(m.group());
        }
    }

    /**
     * Create a new closed integer range from the values start to stop, inclusive.
     */
    public IntInterval(int startIndex, int stopIndex) {
        if (startIndex <= stopIndex) {
            this.startIndex = startIndex;
            this.stopIndex = stopIndex;
        } else {
            this.startIndex = stopIndex;
            this.stopIndex = startIndex;
        }
    }

    public IntInterval(int idx) {
        this.startIndex = idx;
        this.stopIndex = idx;
    }

    /**
     * Return the number of integers present in this interval.
     */
    public int size() {
        if (startIndex == stopIndex) return 1;
        return stopIndex - startIndex + 1;
    }

    /**
     * Returns <code>true</code> if the provided integer falls within the range.
     */
    public boolean contains(int x) {
        return x >= startIndex && x <= stopIndex;
    }

    /**
     * Return the start integer for this range.
     */
    public int getStartIndex() {
        return startIndex;
    }

    /**
     * Return the stop integer for this range.
     */
    public int getStopIndex() {
        return stopIndex;
    }

    @Override
    public String toString() {
        return startIndex + "-" + stopIndex;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        IntInterval range = (IntInterval) o;
        return startIndex == range.startIndex &&
                stopIndex == range.stopIndex;
    }

    @Override
    public int hashCode() {
        return Objects.hash(startIndex, stopIndex);
    }

    /**
     * Remove the given integer from this range, and return a new range (or ranges) representing this change.
     */
    public Set<IntInterval> remove(int x) {
        Set<IntInterval> replacement = new HashSet<>();

        if (x < startIndex || x > stopIndex) throw new IllegalArgumentException();

        if (x == startIndex) {
            if (x == stopIndex) {
                return replacement;
            }
            replacement.add(new IntInterval(startIndex + 1, stopIndex));
            return replacement;
        } else if (x == stopIndex) {
            replacement.add(new IntInterval(startIndex, stopIndex - 1));
            return replacement;
        }

        replacement.add(new IntInterval(startIndex, x - 1));
        replacement.add(new IntInterval(x + 1, stopIndex));

        return replacement;
    }

    @Override
    /**
     * Implementation of the {@link Comparable} interface for this class. The comparator will first evaluate the
     * start ranges, and if finding them equal, it will then evaluate the stop ranges.
     */
    public int compareTo(IntInterval o) {
        if (o.getStartIndex() != startIndex) {
            return Integer.compare(startIndex, o.getStopIndex());
        } else {
            return Integer.compare(stopIndex, o.getStopIndex());
        }
    }


    /**
     * Return the interval to the caller. If the start and stop integers are the same, a simple string for the
     * number is returned. If the start and stop integers are not the same, the interval is returned in the form
     * <code>[i1..i2]</code>.
     *
     * @return The interval formatted as a string.
     */
    public String asString() {
        if (startIndex == stopIndex) return Integer.toString(startIndex);
        return "[" + startIndex + SET_DELIM + stopIndex + "]";
    }
}
