package com.github.davidmoten.rx2;

import java.util.concurrent.Callable;

import com.github.davidmoten.guavamini.annotations.VisibleForTesting;
import com.github.davidmoten.rx2.flowable.Transformers;
import com.github.davidmoten.rx2.functions.Consumer3;

import io.reactivex.BackpressureStrategy;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableTransformer;
import io.reactivex.functions.BiConsumer;
import io.reactivex.functions.BiPredicate;
import io.reactivex.functions.Function3;

public final class StateMachine {

    private StateMachine() {
        // prevent instantiation
    }

    public interface Transition<State, In, Out> extends Function3<State, In, FlowableEmitter<Out>, State> {

        // override so IDEs have better suggestions for parameters
        @Override
        State apply(State state, In value, FlowableEmitter<Out> FlowableEmitter);

    }

    public interface Transition2<State, In, Out> extends Function3<State, In, Emitter<Out>, State> {

        // override so IDEs have better suggestions for parameters
        @Override
        State apply(State state, In value, Emitter<Out> emitter);

    }
    
    public interface Completion<State, Out> extends BiPredicate<State, FlowableEmitter<Out>> {

        // override so IDEs have better suggestions for parameters
        @Override
        boolean test(State state, FlowableEmitter<Out> emitter);

    }

    public interface Completion2<State, Out> extends BiConsumer<State, Emitter<Out>> {

        // override so IDEs have better suggestions for parameters
        @Override
        void accept(State state, Emitter<Out> emitter);

    }

    
    public interface Errored<State, Out> extends Consumer3<State, Throwable, Emitter<Out>> {

        // override so IDEs have better suggestions for parameters
        @Override
        void accept(State state, Throwable error, Emitter<Out> emitter);

    }

    public static Builder builder() {
        return new Builder();
    }

    public static final class Builder {

        private Builder() {
            // prevent instantiation from other packages
        }

        public <State> Builder2<State> initialStateFactory(Callable<State> initialState) {
            return new Builder2<State>(initialState);
        }

        public <State> Builder2<State> initialState(final State initialState) {
            return initialStateFactory(Callables.constant(initialState));
        }

    }

    public static final class Builder2<State> {

        private final Callable<State> initialState;

        private Builder2(Callable<State> initialState) {
            this.initialState = initialState;
        }

        public <In, Out> Builder3<State, In, Out> transition(Transition<State, In, Out> transition) {
            return new Builder3<State, In, Out>(initialState, transition);
        }

    }

    public static final class Builder3<State, In, Out> {

        private static final int DEFAULT_REQUEST_SIZE = 1;

        private final Callable<State> initialState;
        private final Transition<State, In, Out> transition;
        private Completion<State, Out> completion = CompletionAlwaysTrueHolder.instance();
        private BackpressureStrategy backpressureStrategy = BackpressureStrategy.BUFFER;
        private int requestBatchSize = DEFAULT_REQUEST_SIZE;

        private Builder3(Callable<State> initialState, Transition<State, In, Out> transition) {
            this.initialState = initialState;
            this.transition = transition;
        }

        public Builder3<State, In, Out> completion(Completion<State, Out> completion) {
            this.completion = completion;
            return this;
        }

        public Builder3<State, In, Out> backpressureStrategy(BackpressureStrategy backpressureStrategy) {
            this.backpressureStrategy = backpressureStrategy;
            return this;
        }

        public Builder3<State, In, Out> requestBatchSize(int value) {
            this.requestBatchSize = value;
            return this;
        }

        public FlowableTransformer<In, Out> build() {
            return Transformers.stateMachine(initialState, transition, completion, backpressureStrategy,
                    requestBatchSize);
        }

    }

    @VisibleForTesting
    static final class CompletionAlwaysTrueHolder {

        private CompletionAlwaysTrueHolder() {
            // prevent instantiation
        }

        private static final Completion<Object, Object> INSTANCE = new Completion<Object, Object>() {
            @Override
            public boolean test(Object t1, FlowableEmitter<Object> t2) {
                return true;
            }
        };

        @SuppressWarnings("unchecked")
        static <State, Out> Completion<State, Out> instance() {
            return (Completion<State, Out>) INSTANCE;
        }
    }

    public static interface Emitter<T> {
        void onNext_(T t);

        void onError_(Throwable e);

        void onComplete_();

        void cancel_();
    }

}
