/*
 * Copyright 2018, 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.system.server;

import com.google.protobuf.Timestamp;
import io.spine.annotation.Internal;
import io.spine.core.CommandId;
import io.spine.core.EventContext;
import io.spine.core.Responses;
import io.spine.core.Status;
import io.spine.server.aggregate.Aggregate;
import io.spine.server.aggregate.Apply;
import io.spine.server.command.Assign;

/**
 * The aggregate representing the lifecycle of a command.
 *
 * <p>All the commands in the system (except the commands in the {@code System} bounded context)
 * have an associated {@code CommandLifecycle}.
 *
 * <p>When a command is received by the {@link io.spine.server.commandbus.CommandBus CommandBus},
 * the {@link CommandReceived} event occurs. Then the command passes a number of
 * {@linkplain io.spine.server.bus.BusFilter filters} and either is acknowledged
 * ({@link CommandAcknowledged}) or causes an error ({@link CommandErrored}).
 * If the command is acknowledged, it is passed to a dispatcher ({@link CommandDispatched}).
 * After dispatching, the command is handled and either {@link CommandHandled},
 * or {@link CommandRejected}, or {@link CommandErrored} event occurs.
 *
 * @author Dmytro Dashenkov
 */
@SuppressWarnings("OverlyCoupledClass") // OK for an aggregate class.
@Internal
public final class CommandLifecycleAggregate
        extends Aggregate<CommandId, CommandLifecycle, CommandLifecycleVBuilder> {

    private CommandLifecycleAggregate(CommandId id) {
        super(id);
    }

    @Assign
    CommandReceived handle(ReceiveCommand command) {
        return CommandReceived.newBuilder()
                              .setId(command.getId())
                              .setPayload(command.getPayload())
                              .build();
    }

    @Assign
    CommandAcknowledged handle(AcknowledgeCommand command) {
        return CommandAcknowledged.newBuilder()
                                  .setId(command.getId())
                                  .build();
    }

    @Assign
    CommandDispatched handle(DispatchCommand command) {
        return CommandDispatched.newBuilder()
                                .setId(command.getId())
                                .setReceiver(command.getReceiver())
                                .build();
    }

    @Assign
    CommandHandled handle(MarkCommandAsHandled command) {
        return CommandHandled.newBuilder()
                             .setId(command.getId())
                             .build();
    }

    @Assign
    CommandErrored handle(MarkCommandAsErrored command) {
        return CommandErrored.newBuilder()
                             .setId(command.getId())
                             .setError(command.getError())
                             .build();
    }

    @Assign
    CommandRejected handle(MarkCommandAsRejected command) {
        return CommandRejected.newBuilder()
                              .setId(command.getId())
                              .setRejection(command.getRejection())
                              .build();
    }

    @Apply
    private void on(CommandReceived event, EventContext context) {
        CommandStatus status = CommandStatus
                .newBuilder()
                .setWhenReceived(context.getTimestamp())
                .build();
        getBuilder().setId(event.getId())
                    .setCommand(event.getPayload())
                    .setStatus(status);
    }

    @Apply
    private void on(CommandAcknowledged event, EventContext context) {
        CommandStatus status = getBuilder().getStatus()
                                    .toBuilder()
                                    .setWhenAcknowledged(context.getTimestamp())
                                    .build();
        getBuilder().setStatus(status);
    }

    @Apply
    private void on(CommandDispatched event, EventContext context) {
        CommandStatus status = getBuilder().getStatus()
                                    .toBuilder()
                                    .setWhenDispatched(context.getTimestamp())
                                    .build();
        getBuilder().setStatus(status)
                    .setReceiver(event.getReceiver());
    }

    @Apply
    private void on(CommandHandled event, EventContext context) {
        setProcessingStatus(Responses.statusOk(), context.getTimestamp());
    }

    @Apply
    private void on(CommandErrored event, EventContext context) {
        Status status = Status
                .newBuilder()
                .setError(event.getError())
                .build();
        setProcessingStatus(status, context.getTimestamp());
    }

    @Apply
    private void on(CommandRejected event, EventContext context) {
        Status status = Status
                .newBuilder()
                .setRejection(event.getRejection())
                .build();
        setProcessingStatus(status, context.getTimestamp());
    }

    private void setProcessingStatus(Status status, Timestamp whenProcessed) {
        CommandStatus commandStatus = getBuilder().getStatus()
                                                  .toBuilder()
                                                  .setWhenProcessed(whenProcessed)
                                                  .setProcessingStatus(status)
                                                  .build();
        getBuilder().setStatus(commandStatus);
    }
}

@@ -1585,17 +1585,76 @@
 *%0A * %3Cp%3E
-W
+Emits events:%0A * %3Cul%3E%0A *     %3Cli%3E%7B@link CommandReceived%7D - w
 hen a co
@@ -1673,24 +1673,35 @@
 eived by the
+%0A *        
  %7B@link io.s
@@ -1749,17 +1749,21 @@
 Bus%7D
-,
+;
 %0A * 
-the 
+    %3Cli%3E
 %7B@li
@@ -1776,33 +1776,25 @@
 mand
-Received%7D event occurs. T
+Acknowledged%7D - w
 hen 
@@ -1815,23 +1815,23 @@
 sses
- a number of%0A *
+%0A *         the
  %7B@l
@@ -1869,16 +1869,20 @@
 sFilter 
+bus 
 filters%7D
@@ -1886,231 +1886,338 @@
 rs%7D 
-and either is acknowledged%0A * (%7B@link CommandAcknowledged%7D) or causes an error (%7B@link CommandErrored%7D).%0A * If the c
+successfully;%0A *     %3Cli%3E%7B@link CommandErrored%7D - when the command causes an error in%0A *         the %7B@linkplain io.spine.server.bus.BusFilter bus filters%7D;%0A *     %3Cli%3E%7B@link C
 ommand
- is acknowledged, it is passed to a dispatcher (%7B@link CommandDispatched%7D).%0A * After dispatching, the
+Dispatched%7D - when the command is passed to a dispatcher after%0A *         being acknowledged;%0A *     %3Cli%3E%7B@link CommandHandled%7D - after a successful
  com
@@ -2225,127 +2225,200 @@
 and 
-is 
 handl
-ed and either %7B@link CommandHandled%7D,%0A * or %7B@link CommandRejected%7D, or %7B@link CommandErrored%7D event occurs.%0A *
+ing;%0A *     %3Cli%3E%7B@link CommandErrored%7D - if the command caused a runtime error during handling;%0A *     %3Cli%3E%7B@link CommandRejected%7D - if the command handler rejected the command.%0A * %3C/ul%3E%0A
 %0A * 

Make the list of command lifecycle events more comprehensible.