package be.iminds.ilabt.jfed.lowlevel.stitching;

import javax.annotation.Nonnull;
import java.util.*;

/**
 * VlanRangeHelper can read Vlan ranges in the stitching rspec format
 * <p/>
 * examples:
 * 3726-3732,3747-3749
 * 3726-3732,3747
 * 670,3726-3750
 * 2-4094
 * 300
 * 580,722
 * 580,722,1024,18-30
 * <p/>
 * This class is a sort of iterator. It starts out at the first of the range, and can be incremented.
 */
//this is a sort of collection, a set even, but no reason to implement full interface yet
public class VlanRange implements Collection<Integer> {
    private List<Range> origRanges; //list of ranges. Needs to be sorted at all time. Ranges may never overlap.

    public VlanRange(VlanRange o) {
        this(o.toString());
    }

    public VlanRange(Collection<Integer> c) {
        this.origRanges = new ArrayList<>();
        SortedSet<Integer> s = new TreeSet<>(c);
        Range curRange = null;
        for (Integer i : s) {
            if (curRange != null && curRange.to+1 == i) {
                curRange = new Range(curRange.from, i);
            } else {
                if (curRange != null)
                    origRanges.add(curRange);
                curRange = new Range(i, i);
            }
        }
        if (curRange != null)
            origRanges.add(curRange);
    }

    public VlanRange(String rangeString) {
        origRanges = new ArrayList<>();

        if (rangeString.trim().isEmpty())
            return; //no point in continuing, it's an empty string, so no Ranges

        String[] parts = rangeString.split(",");
        for (String part : parts) {
            assert !part.contains(",") :
                    "part contains comma. part=\"" + part + "\" rangeString=\"" + rangeString + "\" parts=" + Arrays.toString(parts);

            if (!part.trim().isEmpty()) {
                Range r = new Range(part);
                origRanges.add(r);
            }
        }
    }

    public static String rangesToString(List<Range> ranges) {
        String res = "";
        boolean isFirst = true;
        for (Range r : ranges) {
            if (!isFirst)
                res += ",";

            if (r.to == r.from)
                res += r.to + "";
            else
                res += r.from + "-" + r.to;

            isFirst = false;
        }
        return res;
    }

    private static List<Range> sortRanges(List<Range> origRanges) {
        List<Range> res = new ArrayList<>(origRanges);
        Collections.sort(res);
        return res;
    }


    @Override
    public int size() {
        int res = 0;
        for (Range r : origRanges)
            res += r.size();
        return res;
    }

    public boolean isEmpty() {
        for (Range r : origRanges)
            if (!r.isEmpty()) return false;
        return true;
    }

    public boolean contains(int i) {
        for (Range r : origRanges) {
            if (r.contains(i))
                return true;
        }
        return false;
    }
    @Override
    public boolean contains(Object o) {
        if (o == null)
            return false;
        if (!(o instanceof Integer))
            return false;
        int i = (Integer) o;

        return contains(i);
    }

    public Integer getFirst() {
        if (isEmpty()) return null;
        Range first = origRanges.get(0);
        assert first != null;
        assert !first.isEmpty();
        return first.from;
    }

    @Nonnull
    @Override
    public Iterator iterator() {
        return new Iterator(new ArrayList<>(this.origRanges));
    }

    @Nonnull
    @Override
    public Integer[] toArray() {
        Integer[] res = new Integer[size()];
        int j = 0;
        for (Range r : origRanges) {
            for (int i = r.from; i <= r.to; i++)
                res[j++] = i;
        }
        return res;
    }

    @Nonnull
    @Override
    public <T> T[] toArray(@Nonnull T[] a) {
        throw new UnsupportedOperationException("not yet implemented");
    }

    @Override
    public boolean add(Integer integer) {
        throw new UnsupportedOperationException("not yet implemented");
    }

    @Override
    public boolean remove(Object o) {
        throw new UnsupportedOperationException("not yet implemented");
    }

    @Override
    public boolean containsAll(@Nonnull Collection<?> c) {
        //lazy inefficient implementation
        for (Object o : c)
            if (!contains(o))
                return false;
        return true;
    }

    @Override
    public boolean addAll(@Nonnull Collection<? extends Integer> c) {
        throw new UnsupportedOperationException("not yet implemented");
    }

    @Override
    public String toString() {
        return rangesToString(origRanges);
    }

    public List<Range> getRanges() {
        return new ArrayList<>(origRanges);
    }

    @Override
    public void clear() {
        origRanges.clear();
    }

    @Override
    public boolean removeAll(@Nonnull Collection<?> c) {

        SortedSet<Object> toBeRemoved = new TreeSet<>(c);

        boolean changed = false;

        List<Range> newRanges = new ArrayList<>();
        Deque<Range> oldRanges = new LinkedList<>(sortRanges(origRanges));
        //        LOG.trace("removeAll(): from "+this+" toBeRemoved="+toBeRemoved);
        while (!oldRanges.isEmpty()) {
            Range oldRange = oldRanges.removeFirst();
            //            LOG.trace("  got oldRange: "+oldRange);
            for (Object r : toBeRemoved)
                if (r instanceof Integer) {

                    //                LOG.trace("    removing: "+r);
                    if (oldRange != null && oldRange.contains((Integer)r)) {
                        changed = true;
                        Range.Pair<Range, Range> splitRanges = oldRange.split((Integer)r);
                        //                    LOG.trace("      Split old range "+oldRange+" into "+splitRanges.getFirst()+" and "+splitRanges.getSecond());
                        if (splitRanges.getFirst() != null) newRanges.add(splitRanges.getFirst());
                        oldRange = splitRanges.getSecond();
                        if (oldRange == null) break;
                    }
                }
            if (oldRange != null) {
                //                LOG.trace("    Storing leftover oldRange="+oldRange);
                newRanges.add(oldRange);
            }
        }
        //        LOG.trace("removeAll() result="+newRanges);

        origRanges = newRanges;

        return changed;
    }

    @Override
    public boolean retainAll(@Nonnull Collection<?> c) {
        //TODO: This can be done more efficiently
        SortedSet<Integer> orig = new TreeSet<>(this);
        boolean changed = orig.retainAll(c);
        origRanges = new VlanRange(orig).origRanges;

        return changed;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        VlanRange that = (VlanRange) o;

        //lazy implementation
        if (!Objects.equals(toString(), that.toString())) return false;

        return true;
    }

    @Override
    public int hashCode() {
        //lazy implementation
        return toString().hashCode();
    }


    public static class Range implements Comparable<Range> {
        final int from; //inclusive
        final int to;   //inclusive

        public Range(String r) {
            if (r == null || r.trim().isEmpty())
                throw new RuntimeException("new Range(\"" + r + "\") -> Range constructor called with empty string");

            if (!r.matches("[0-9-]*"))
                throw new RuntimeException("new Range(\"" + r + "\") -> Range constructor called with invalid string");
            try {
                if (!r.contains("-")) {
                    to = Integer.parseInt(r);
                    from = to;
                    return;
                }

                String[] parts = r.split("-");
                assert parts.length == 2;
                from = Integer.parseInt(parts[0]);
                to = Integer.parseInt(parts[1]);
            } catch (NumberFormatException e) {
                throw new RuntimeException("Error parsing Range \"" + r + "\": " + e.getMessage(), e);
            }
        }

        public Range(Range o) {
            this.from = o.from;
            this.to = o.to;
        }

        public Range(int from, int to) {
            this.from = from;
            this.to = to;
        }

        public Range(int single) {
            this.from = single;
            this.to = single;
        }

        public boolean isEmpty() {
            return to < from;
        }

        public boolean contains(int i) {
            return i >= from && i <= to;
        }

        //TODO: make unit test
        public Pair<Range, Range> split(int i) {
            if (i < from || i > to)
                throw new OutOfBoundsException("Range(" + from + "," + to + ") cannot be split at " + i);
            if (i == from && to == from) return new Pair<>(null, null);
            if (i == from) return new Pair<>(null, new Range(from + 1, to));
            if (i == to) return new Pair<>(new Range(from, to - 1), null);
            return new Pair<>(new Range(from, i - 1), new Range(i + 1, to));
        }

        //sorted by from
        @Override
        public int compareTo(@Nonnull Range o) {
            if (from < o.from) return -1;
            if (from > o.from) return 1;
            return 0;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Range range = (Range) o;

            if (from != range.from) return false;
            if (to != range.to) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = from;
            result = 31 * result + to;
            return result;
        }

        @Override
        public String toString() {
            return from + "-" + to;
        }

        public int size() {
            if (isEmpty()) return 0;
            return (to+1) - from;
        }

        public static class Pair<T, U> {
            final T t;
            final U u;

            public Pair(T t, U u) {
                this.t = t;
                this.u = u;
            }

            public T getFirst() {
                return t;
            }

            public U getSecond() {
                return u;
            }
        }

        public static class OutOfBoundsException extends RuntimeException {
            public OutOfBoundsException() {
                super();
            }

            public OutOfBoundsException(String s) {
                super(s);
            }
        }
    }

    public class Iterator implements java.util.Iterator<Integer> {
        private final List<Range> itRanges;

        /** WARNING: This is a cheating iterator: it does not allow modification as it makes a copy! */
        public Iterator(List<Range> itRanges) {
            this.itRanges = itRanges;
        }

        @Override
        public boolean hasNext() {
            if (itRanges.isEmpty()) return false;
            Range first = itRanges.get(0);
            assert !first.isEmpty();
            if (first.isEmpty()) return false;
            return true;
        }

        @Override
        public Integer next() {
            if (itRanges.isEmpty()) return null;

            Range first = itRanges.get(0);
            assert !first.isEmpty();
            int res = first.from;
            assert !first.isEmpty();
//            first.from++;
            first = new Range(first.from+1, first.to);
            itRanges.set(0, first);
            if (first.isEmpty())
                itRanges.remove(0);
            return res;
        }
    }
}
