/*
 * Copyright 2019, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.client;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.spine.base.CommandMessage;
import io.spine.base.EventMessage;
import io.spine.core.Command;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.function.Consumer;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Allows to post a command optionally subscribing to events that are immediate results
 * of handling this command.
 */
public final class CommandRequest extends ClientRequest {

    private final CommandMessage commandMessage;
    private final MultiEventConsumers.Builder builder;
    private @Nullable ErrorHandler streamingErrorHandler;

    CommandRequest(ClientRequest parent, CommandMessage c) {
        super(parent);
        this.commandMessage = c;
        this.builder = MultiEventConsumers.newBuilder();
    }

    /**
     * Adds the passed consumer to the subscribers of the event of the passed type.
     *
     * @param type
     *          the type of the event message to be received by the consumer
     * @param consumer
     *          the consumer
     * @param <E>
     *          the type of the event
     */
    @CanIgnoreReturnValue
    public <E extends EventMessage> CommandRequest
    observe(Class<E> type, Consumer<E> consumer) {
        checkNotNull(consumer);
        builder.observe(type, consumer);
        return this;
    }

    /**
     * Adds the passed event consumer to the subscribers of the event of the passed type.
     *
     * @param type
     *          the type of the event message to be received by the consumer
     * @param consumer
     *          the consumer of the event message and its context
     * @param <E>
     *          the type of the event
     */
    @CanIgnoreReturnValue
    public <E extends EventMessage> CommandRequest
    observe(Class<E> type, EventConsumer<E> consumer) {
        checkNotNull(consumer);
        builder.observe(type, consumer);
        return this;
    }

    /**
     * Assigns a handler for errors occurred when delivering events.
     */
    @CanIgnoreReturnValue
    public CommandRequest onStreamingError(ErrorHandler handler) {
        this.streamingErrorHandler = checkNotNull(handler);
        return this;
    }

    /**
     * Subscribes the consumers to events to receive events resulting from the command as
     * the happen, then sends the command to the server.
     *
     * <p>The returned {@code Subscription} instance should be
     * {@linkplain Client#cancel(Subscription) canceled} after the requesting code receives all
     * the expected events, or after a reasonable timeout.
     *
     * @return the subscription to all the events
     * @implNote Subscriptions should be cancelled to free up client and server resources required
     * for their maintenance. It is not possible to cancel the returned subscription in an automatic
     * way because of the following. Subscriptions by nature are asynchronous and infinite requests.
     * Even that we know expected types of the events produced by the command, only the
     * client code “knows” how many of them it expects. Also, some events may not arrive because of
     * communication of business logic reasons. That's why the returned subscription should be
     * cancelled by the client code when it no longer needs it.
     */
    public Subscription post() {
        MultiEventConsumers consumers = builder.build();
        Client client = client();
        Command command =
                client.requestOf(user())
                      .command()
                      .create(this.commandMessage);
        Subscription result =
                EventsAfterCommand.subscribe(client, command, consumers, streamingErrorHandler);
        client().post(command);
        return result;
    }
}

@@ -1032,16 +1032,72 @@
 lient;%0A%0A
+import com.google.common.annotations.VisibleForTesting;%0A
 import c
@@ -1151,16 +1151,16 @@
 nValue;%0A
-
 import i
@@ -1538,16 +1538,670 @@
 ommand.%0A
+ *%0A * %3Cp%3EUsage example:%0A * %3Cpre%3E%7B@code%0A * Subscription loginSubscription =%0A *     client.asGuest()%0A *           .command(logInUser)%0A *           .observe(UserLoggedIn.class, (event, context) -%3E %7B ... %7D)%0A *           .observe(UserAlreadyLoggedIn.class, (rejection, context) -%3E %7B ... %7D)%0A *           .onStreamingError((throwable) -%3E %7B%7D)%0A *           .post();%0A * %7D%3C/pre%3E%0A *%0A * %3Cp%3EThe subscription obtained from the %7B@link #post()%7D should must be cancelled to preserve%0A * both client-side and server-side resources. The moment of cancelling the subscriptions%0A * depends on the nature of the posted command and the outcome expected by the client application.%0A
  */%0Apubl
@@ -2288,24 +2288,17 @@
 Message 
-commandM
+m
 essage;%0A
@@ -2504,32 +2504,25 @@
        this.
-commandM
+m
 essage = c;%0A
@@ -3786,24 +3786,157 @@
 ing events.%0A
+     *%0A     * %3Cp%3EIf such an error occur, no more events resulting from the posted command will be%0A     * delivered to the consumers.%0A
      */%0A    
@@ -4220,16 +4220,17 @@
    * the
+y
  happen,
@@ -4433,23 +4433,15 @@
 ives
- all
 %0A     *
- the
  exp
@@ -5456,21 +5456,9 @@
 ate(
-this.commandM
+m
 essa
@@ -5621,16 +5621,16 @@
 mmand);%0A
-
         
@@ -5649,11 +5649,96 @@
 t;%0A    %7D
+%0A%0A    @VisibleForTesting%0A    CommandMessage message() %7B%0A        return message;%0A    %7D
 %0A%7D%0A

Improve Javadoc