/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.controller.client.helpers.domain.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.jboss.as.controller.client.logging.ControllerClientLogger;
import org.jboss.as.controller.client.helpers.ClientConstants;
import org.jboss.as.controller.client.helpers.domain.DeploymentAction;
import org.jboss.as.controller.client.helpers.domain.DeploymentActionResult;
import org.jboss.as.controller.client.helpers.domain.DeploymentPlanResult;
import org.jboss.as.controller.client.helpers.domain.ServerIdentity;
import org.jboss.as.controller.client.helpers.domain.UpdateFailedException;
import org.jboss.dmr.ModelNode;

/**
 * Adapts from Future<ModelNode> to Future<ServerDeploymentPlanResult>.
 *
 * @author Brian Stansberry (c) 2011 Red Hat Inc.
 */
class DomainDeploymentPlanResultFuture implements Future<DeploymentPlanResult> {

    private static final String DOMAIN_FAILURE_DESCRIPTION = "domain-failure-description";

    private static final String HOST_FAILURE_DESCRIPTION = "host-failure-description";

    private static final String ROLLED_BACK = "rolled-back";

    private final Future<ModelNode> nodeFuture;
    private final DeploymentPlanImpl plan;
    private final Map<UUID, List<String>> actionsById;
    private final Set<ServerIdentity> servers;

    DomainDeploymentPlanResultFuture(final DeploymentPlanImpl plan, final Future<ModelNode> nodeFuture, final Set<ServerIdentity> servers, final Map<UUID, List<String>> actionsById) {
        this.plan = plan;
        this.nodeFuture = nodeFuture;
        this.actionsById = actionsById;
        this.servers = servers;
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        return nodeFuture.cancel(mayInterruptIfRunning);
    }

    @Override
    public boolean isCancelled() {
        return nodeFuture.isCancelled();
    }

    @Override
    public boolean isDone() {
        return nodeFuture.isDone();
    }

    @Override
    public DeploymentPlanResult get() throws InterruptedException, ExecutionException {
        ModelNode node = nodeFuture.get();
        return getResultFromNode(node);
    }

    @Override
    public DeploymentPlanResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
                                                                        TimeoutException {
        ModelNode node = nodeFuture.get(timeout, unit);
        return getResultFromNode(node);
    }

    private DeploymentPlanResult getResultFromNode(ModelNode planResultNode) {
        Map<UUID, DeploymentActionResult> actionResults = new HashMap<UUID, DeploymentActionResult>();
        String outcome = planResultNode.get("outcome").asString();
        if ("cancelled".equals(outcome)) {
            createCancelledResults(actionResults);
        } else if ("success".equals(outcome)) {
            createSuccessResults(actionResults, planResultNode);
        } else {
            createFailureResults(actionResults, planResultNode);
        }
        return new DeploymentPlanResultImpl(plan, actionResults);
    }

    private void createFailureResults(Map<UUID, DeploymentActionResult> actionResults, ModelNode planResultNode) {
        boolean isDomainFailure = false;
        boolean isHostFailure = false;
        // Determine the failure type
        if (planResultNode.hasDefined(ClientConstants.FAILURE_DESCRIPTION)) {
            final ModelNode failureDescription = planResultNode.get(ClientConstants.FAILURE_DESCRIPTION);
            isDomainFailure = failureDescription.hasDefined(DOMAIN_FAILURE_DESCRIPTION);
            isHostFailure = failureDescription.hasDefined(HOST_FAILURE_DESCRIPTION);
        }
        final ModelNode result = planResultNode.get(ClientConstants.RESULT);
        for (DeploymentAction deploymentAction : plan.getDeploymentActions()) {
            // Get the steps used for this deployment action
            final List<String> steps = actionsById.get(deploymentAction.getId());
            for (String step : steps) {
                if (result.hasDefined(step)) {
                    // Get the result for this step
                    final ModelNode stepResult = result.get(step);
                    final boolean isRolledBack = (stepResult.hasDefined(ROLLED_BACK) && stepResult.get(ROLLED_BACK).asBoolean());
                    final UpdateFailedException updateFailedException;
                    // Create the failure description
                    if (stepResult.hasDefined(ClientConstants.FAILURE_DESCRIPTION)) {
                        updateFailedException = new UpdateFailedException(stepResult.get(ClientConstants.FAILURE_DESCRIPTION).toString());
                    } else if (planResultNode.hasDefined(ClientConstants.FAILURE_DESCRIPTION)) {
                        updateFailedException = new UpdateFailedException(planResultNode.get(ClientConstants.FAILURE_DESCRIPTION).toString());
                    } else {
                        updateFailedException = new UpdateFailedException(ControllerClientLogger.ROOT_LOGGER.noFailureDetails());
                    }
                    final BasicDomainUpdateResult domainUpdateResult;
                    if (isDomainFailure) {
                        domainUpdateResult = new BasicDomainUpdateResult(updateFailedException, isRolledBack);
                    } else if (isHostFailure) {
                        final Map<String, UpdateFailedException> hostExceptions = new HashMap<String, UpdateFailedException>();
                        for (ServerIdentity serverId : servers) {
                            hostExceptions.put(serverId.getHostName(), updateFailedException);
                        }
                        domainUpdateResult = new BasicDomainUpdateResult(hostExceptions, isRolledBack);
                    } else {
                        domainUpdateResult = new BasicDomainUpdateResult();
                    }
                    // Create the deployment action result
                    final DeploymentActionResultImpl deploymentActionResult = new DeploymentActionResultImpl(deploymentAction, domainUpdateResult);
                    final UpdateResultHandlerResponse resultHandlerResponse = UpdateResultHandlerResponse.createFailureResponse(updateFailedException);
                    // Update the action result with updates for each server
                    for (ServerIdentity serverId : servers) {
                        final ServerUpdateResultImpl serverUpdateResult = new ServerUpdateResultImpl(deploymentAction.getId(), serverId, resultHandlerResponse);
                        deploymentActionResult.storeServerUpdateResult(serverId, serverUpdateResult);
                    }
                    actionResults.put(deploymentAction.getId(), deploymentActionResult);
                }
            }
        }
    }

    private void createSuccessResults(Map<UUID, DeploymentActionResult> actionResults, ModelNode planResultNode) {
        createDefaultResults(actionResults, new BasicDomainUpdateResult(), UpdateResultHandlerResponse.createSuccessResponse(planResultNode));
    }

    private void createCancelledResults(Map<UUID, DeploymentActionResult> actionResults) {
        createDefaultResults(actionResults, new BasicDomainUpdateResult(true), UpdateResultHandlerResponse.createCancellationResponse());
    }

    private void createDefaultResults(final Map<UUID, DeploymentActionResult> actionResults, final BasicDomainUpdateResult domainUpdateResult, final UpdateResultHandlerResponse resultHandlerResponse) {
        for (DeploymentAction deploymentAction : plan.getDeploymentActions()) {
            // Create the deployment action result
            final DeploymentActionResultImpl deploymentActionResult = new DeploymentActionResultImpl(deploymentAction, domainUpdateResult);
            // Update the action result with updates for each server
            for (ServerIdentity serverId : servers) {
                final ServerUpdateResultImpl serverUpdateResult = new ServerUpdateResultImpl(deploymentAction.getId(), serverId, resultHandlerResponse);
                deploymentActionResult.storeServerUpdateResult(serverId, serverUpdateResult);
            }
            actionResults.put(deploymentAction.getId(), deploymentActionResult);
        }
    }
}