package com.github.phantomthief.util;

import static com.github.phantomthief.util.MoreStreams.toStream;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.partition;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;

import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.LongStream;
import java.util.stream.Stream;

import com.google.common.collect.Range;

/**
 * @author w.vela
 */
public final class MoreIterables {

    private MoreIterables() {
        throw new UnsupportedOperationException();
    }

    public static Stream<List<Long>> batchClosedRangeStream(long from, long to, int batch) {
        return toStream(batchClosedRange(from, to, batch));
    }

    public static Iterable<List<Long>> batchClosedRange(long from, long to, int batch) {
        checkArgument(batch > 0);
        if (from == to) {
            return singleton(singletonList(from));
        }
        LongStream longStream;
        if (from > to) {
            longStream = LongStream.rangeClosed(to, from).map(i -> from + to - i);
        } else {
            longStream = LongStream.rangeClosed(from, to);
        }
        return partition(longStream.boxed()::iterator, batch);
    }

    public static Stream<Range<Long>> batchClosedSimpleRangeStream(long from, long to, int batch) {
        return toStream(batchClosedSimpleRange(from, to, batch));
    }

    public static Iterable<Range<Long>> batchClosedSimpleRange(long from, long to, int batch) {
        checkArgument(batch > 0);
        if (from == to) {
            return singleton(Range.closed(from, to));
        }
        boolean reversed = from > to;
        return () -> new Iterator<Range<Long>>() {

            private Range<Long> current = reversed ? Range.closed((max(from - batch, to) + 1), from)
                                                   : Range.closed(from, min(batch + from, to) - 1);

            @Override
            public boolean hasNext() {
                return current != null && !current.isEmpty();
            }

            @Override
            public Range<Long> next() {
                if (current == null) {
                    throw new NoSuchElementException();
                }
                Range<Long> result = current;
                calcNext();
                return result;
            }

            private void calcNext() {
                if (current.isEmpty()) {
                    current = null;
                    return;
                }
                long newStart;
                if (reversed) {
                    newStart = current.lowerEndpoint() - 1;
                } else {
                    newStart = current.upperEndpoint() + 1;
                }
                if ((!reversed && newStart > to) || (reversed && to > newStart)) {
                    current = null;
                    return;
                }
                long newEnd;
                if (reversed) {
                    newEnd = max(to, newStart - batch + 1);
                } else {
                    newEnd = min(to, newStart + batch - 1);
                }
                current = reversed ? Range.closed(newEnd, newStart) : Range.closed(newStart,
                        newEnd);
            }
        };
    }
}