/*******************************************************************************
 * Copyright (c) 2018 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package org.eclipse.microprofile.reactive.streams.operators.core;

import org.eclipse.microprofile.reactive.streams.operators.CompletionRunner;
import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder;
import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder;
import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder;
import org.eclipse.microprofile.reactive.streams.operators.spi.ReactiveStreamsEngine;
import org.eclipse.microprofile.reactive.streams.operators.spi.Stage;
import org.reactivestreams.Processor;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;

final class PublisherBuilderImpl<T> extends ReactiveStreamsGraphBuilder implements PublisherBuilder<T> {

    PublisherBuilderImpl(Stage stage, ReactiveStreamsGraphBuilder previous) {
        super(stage, previous);
    }

    PublisherBuilderImpl(Stage stage) {
        super(stage, null);
    }

    @Override
    public <R> PublisherBuilder<R> map(Function<? super T, ? extends R> mapper) {
        return addStage(new Stages.Map(mapper));
    }

    @Override
    public PublisherBuilder<T> peek(Consumer<? super T> consumer) {
        return addStage(new Stages.Peek(consumer));
    }

    @Override
    public PublisherBuilder<T> filter(Predicate<? super T> predicate) {
        return addStage(new Stages.Filter(predicate));
    }

    @Override
    public PublisherBuilder<T> distinct() {
        return addStage(Stages.Distinct.INSTANCE);
    }

    @Override
    public <S> PublisherBuilder<S> flatMap(Function<? super T, ? extends PublisherBuilder<? extends S>> mapper) {
        return addStage(new Stages.FlatMap(mapper.andThen(ReactiveStreamsGraphBuilder::rsBuilderToGraph)));
    }

    @Override
    public <S> PublisherBuilder<S> flatMapRsPublisher(Function<? super T, ? extends Publisher<? extends S>> mapper) {
        return addStage(new Stages.FlatMap(mapper.andThen(ReactiveStreamsGraphBuilder::publisherToGraph)));
    }

    @Override
    public <S> PublisherBuilder<S> flatMapCompletionStage(Function<? super T, ? extends CompletionStage<? extends S>> mapper) {
        return addStage(new Stages.FlatMapCompletionStage((Function) mapper));
    }

    @Override
    public <S> PublisherBuilder<S> flatMapIterable(Function<? super T, ? extends Iterable<? extends S>> mapper) {
        return addStage(new Stages.FlatMapIterable((Function) mapper));
    }

    @Override
    public PublisherBuilder<T> limit(long maxSize) {
        return addStage(new Stages.Limit(maxSize));
    }

    @Override
    public PublisherBuilder<T> skip(long n) {
        return addStage(new Stages.Skip(n));
    }

    @Override
    public PublisherBuilder<T> takeWhile(Predicate<? super T> predicate) {
        return addStage(new Stages.TakeWhile(predicate));
    }

    @Override
    public PublisherBuilder<T> dropWhile(Predicate<? super T> predicate) {
        return addStage(new Stages.DropWhile(predicate));
    }

    @Override
    public CompletionRunner<Void> forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action, "Action must not be null");
        return collect(Collector.<T, Void, Void>of(
            () -> null,
            (n, t) -> action.accept(t),
            (v1, v2) -> null,
            v -> null
        ));
    }

    @Override
    public CompletionRunner<Void> ignore() {
        return forEach(r -> {
        });
    }

    @Override
    public CompletionRunner<Void> cancel() {
        return addTerminalStage(Stages.Cancel.INSTANCE);
    }

    @Override
    public CompletionRunner<T> reduce(T identity, BinaryOperator<T> accumulator) {
        return addTerminalStage(new Stages.Collect(Reductions.reduce(identity, accumulator)));
    }

    @Override
    public CompletionRunner<Optional<T>> reduce(BinaryOperator<T> accumulator) {
        return addTerminalStage(new Stages.Collect(Reductions.reduce(accumulator)));
    }

    @Override
    public CompletionRunner<Optional<T>> findFirst() {
        return addTerminalStage(Stages.FindFirst.INSTANCE);
    }

    @Override
    public <R, A> CompletionRunner<R> collect(Collector<? super T, A, R> collector) {
        return addTerminalStage(new Stages.Collect(collector));
    }

    @Override
    public <R> CompletionRunner<R> collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator) {
        // The combiner is not used, so the used, but should not be null
        return addTerminalStage(new Stages.Collect(Collector.of(supplier, accumulator, (a, b) -> a)));
    }

    @Override
    public CompletionRunner<List<T>> toList() {
        return collect(Collectors.toList());
    }

    @Override
    public CompletionRunner<Void> to(Subscriber<? super T> subscriber) {
        return addTerminalStage(new Stages.SubscriberStage(subscriber));
    }

    @Override
    public <R> CompletionRunner<R> to(SubscriberBuilder<? super T, ? extends R> subscriber) {
        Objects.requireNonNull(subscriber, "Subscriber must not be null");
        return addTerminalStage(InternalStages.nested(subscriber));
    }

    @Override
    public <R> PublisherBuilder<R> via(ProcessorBuilder<? super T, ? extends R> processor) {
        return addStage(InternalStages.nested(processor));
    }

    @Override
    public <R> PublisherBuilder<R> via(Processor<? super T, ? extends R> processor) {
        return addStage(new Stages.ProcessorStage(processor));
    }

    @Override
    public PublisherBuilder<T> onError(Consumer<Throwable> errorHandler) {
        return addStage(new Stages.OnError(errorHandler));
    }

    @Override
    public PublisherBuilder<T> onErrorResume(Function<Throwable, ? extends T> errorHandler) {
        return addStage(new Stages.OnErrorResume(errorHandler));
    }

    @Override
    public PublisherBuilder<T> onErrorResumeWith(Function<Throwable, ? extends PublisherBuilder<? extends T>> errorHandler) {
        return addStage(new Stages.OnErrorResumeWith(errorHandler.andThen(ReactiveStreamsGraphBuilder::rsBuilderToGraph)));
    }

    @Override
    public PublisherBuilder<T> onErrorResumeWithRsPublisher(Function<Throwable, ? extends Publisher<? extends T>> errorHandler) {
        return addStage(new Stages.OnErrorResumeWith(errorHandler.andThen(ReactiveStreamsGraphBuilder::publisherToGraph)));
    }

    @Override
    public PublisherBuilder<T> onTerminate(Runnable action) {
        return addStage(new Stages.OnTerminate(action));
    }

    @Override
    public PublisherBuilder<T> onComplete(Runnable action) {
        return addStage(new Stages.OnComplete(action));
    }

    @Override
    public Publisher<T> buildRs() {
        return buildRs(ReactiveStreamsEngineResolver.instance());
    }

    @Override
    public Publisher<T> buildRs(ReactiveStreamsEngine engine) {
        Objects.requireNonNull(engine, "Engine must not be null");
        return engine.buildPublisher(toGraph());
    }

    private <R> PublisherBuilder<R> addStage(Stage stage) {
        return new PublisherBuilderImpl<>(stage, this);
    }

    private <R> CompletionRunner<R> addTerminalStage(Stage stage) {
        return new CompletionRunnerImpl<>(stage, this);
    }
}