package net.andreinc.mockneat.unit.seq;

import net.andreinc.mockneat.MockNeat;
import net.andreinc.mockneat.abstraction.MockUnitLong;

import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;

import static net.andreinc.aleph.AlephFormatter.str;
import static net.andreinc.mockneat.utils.ValidationUtils.LONG_SEQ_OVERFLOW;
import static net.andreinc.mockneat.utils.ValidationUtils.SEQ_INVALID_RANGE;
import static net.andreinc.mockneat.utils.ValidationUtils.isTrue;

public class LongSeq implements MockUnitLong {

    private long increment = 1;
    private long start = 0L;
    private boolean cycle = true;
    private long max = Long.MAX_VALUE;
    private long min = Long.MIN_VALUE;
    private AtomicLong internal;

    /**
     * <p>Returns a {@code LongSeq} object that can be used to generate arbitrary {@code Long} numbers in a sequence.</p>
     *
     * @return A re-usable {@code LongSeq} object. The {@code LongSeq} class implements {@code MockUnitLong}.
     */
    public static LongSeq longSeq() {
        return MockNeat.threadLocal().longSeq();
    }

    public LongSeq(long start, long increment, long max, long min, boolean cycle) {
        isTrue(min<max, str(SEQ_INVALID_RANGE).args("min", min, "max", max).fmt());
        this.increment = increment;
        this.start = start;
        this.cycle = cycle;
        this.max = max;
        this.min = min;
        this.internal = new AtomicLong(start);
    }

    public LongSeq(long start, long increment) {
        isTrue(min<max, str(SEQ_INVALID_RANGE).args("min", min, "max", max).fmt());
        this.start = start;
        this.increment = increment;
        this.internal = new AtomicLong(start);
    }

    public LongSeq(long increment) {
        isTrue(min<max, str(SEQ_INVALID_RANGE).args( "min", min, "max", max).fmt());
        this.increment = increment;
        this.internal = new AtomicLong(start);
    }

    public LongSeq() {
        isTrue(min<max, str(SEQ_INVALID_RANGE).args( "min", min, "max", max).fmt());
        this.internal = new AtomicLong(start);
    }

    /**
     * <p>Sets the {@code start} value of the {@code LongSeq}.</p>
     *
     * <p>By default the starting value is 0.</p>
     *
     * @param start The starting value.
     * @return The same {@code LongSeq} object.
     */
    public LongSeq start(long start) {
        this.start = start;
        this.internal = new AtomicLong(start);
        return this;
    }

    /**
     * <p>Sets the {@code increment} of the {@code LongSeq}.</p>
     *
     * <p>By default the increment value is 1.</p>
     *
     * @param increment The increment's value.
     * @return The same {@code LongSeq} object.
     */
    public LongSeq increment(long increment) {
        this.increment = increment;
        return this;
    }

    /**
     * <p>Enables or disables the "cycling" in the elements, if the max value was reached.</p>
     *
     * @param cycle The status of "cycling".
     *
     * @return The same {@code LongSeq} object.
     */
    public LongSeq cycle(boolean cycle) {
        this.cycle = cycle;
        return this;
    }

    /**
     * <p>Sets the max value that can be generated by the current {@code LongSeq}.</p>
     *
     * <p>By default, the value is set {@code Long.MAX_VALUE}.</p>
     *
     * @param max The max value that can be generated by the current {@code LongSeq}.
     *
     * @return The same {@code LongSeq} object.
     */
    public LongSeq max(long max) {
        isTrue(min<max, str(SEQ_INVALID_RANGE).args("min", min, "max", max).fmt());
        this.max = max;
        return this;
    }

    /**
     * <p>Sets the min value that can be generated by the current {@code LongSeq}.</p>
     *
     * <p>By default, the value is set to {@code Long.MIN_VALUE}</p>
     *
     * @param min The min value that can be generated by the current {@code LongSeq}
     * @return The same {@code LongSeq} object.
     */
    public LongSeq min(long min) {
        isTrue(min<max, str(SEQ_INVALID_RANGE).args("min", min, "max", max).fmt());
        this.min = min;
        return this;
    }

    private boolean nextValueOverflows() {
        return (increment>0) ? internal.get() > max :
                               internal.get() < min;
    }

    protected void fail() {
        String fmt = str(LONG_SEQ_OVERFLOW)
                .arg("min", min)
                .arg("max", max)
                .fmt();
        throw new IllegalStateException(fmt);
    }

    @Override
    public Supplier<Long> supplier() {
        return () -> {
            if (nextValueOverflows()) {
                if (cycle) internal.set(start);
                else fail();
            }
            return internal.getAndAdd(increment);
        };
    }
}
