/**
 * JaDOrT: JASMINe Deployment Orchestration Tool
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Copyright (C) 2008-2009 France Telecom R&D
 * Contact: jasmine@ow2.org
 *
 * This library 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 any later version.
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: StepManager.java 4961 2009-08-18 18:29:49Z alitokmen $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.service.implementation;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.ow2.jasmine.jadort.api.IJadortService;
import org.ow2.jasmine.jadort.api.InvalidStepException;
import org.ow2.jasmine.jadort.api.JadortServiceException;
import org.ow2.jasmine.jadort.api.IJadortService.OperationType;
import org.ow2.jasmine.jadort.api.entities.deployment.ApplicationBean;
import org.ow2.jasmine.jadort.api.entities.deployment.OperationStateBean;
import org.ow2.jasmine.jadort.api.entities.deployment.ServerProgressBean;
import org.ow2.jasmine.jadort.api.entities.deployment.ServerProgressState;
import org.ow2.jasmine.jadort.api.entities.deployment.VMImageBean;
import org.ow2.jasmine.jadort.api.entities.deployment.WorkerProgressBean;
import org.ow2.jasmine.jadort.api.entities.deployment.WorkerProgressState;
import org.ow2.jasmine.jadort.api.entities.deployment.OperationStateBean.ActionState;
import org.ow2.jasmine.jadort.api.entities.deployment.OperationStateBean.Step;
import org.ow2.jasmine.jadort.api.entities.topology.GroupBean;
import org.ow2.jasmine.jadort.api.entities.topology.ServerBean;
import org.ow2.jasmine.jadort.api.entities.topology.TopologyBean;
import org.ow2.jasmine.jadort.api.entities.topology.WorkerBean;
import org.ow2.jasmine.jadort.service.action.WorkerAction;

/**
 * Step management of the JaDOrT engine.
 * 
 * @author Arda Aydin
 * @author Malek Chahine
 * @author S. Ali Tokmen
 */
public class StepManager extends ActionManager {

    /**
     * @return true if going to the next step is allowed, false otherwise.
     */
    protected boolean canGoToNextStep() {
        Step currentStep = this.operation.getCurrentStep();
        if (currentStep.equals(Step.SELECT_OPERATION)) {
            return this.operation != null;
        } else if (currentStep.equals(Step.INITIALIZE_TOPOLOGY)) {
            return this.operation.getTopology() != null;
        } else if (currentStep.equals(Step.SELECT_GROUP)) {
            return this.operation.getSelectedGroup() != null;
        } else if (currentStep.equals(Step.SELECT_OPERATION_TYPE)) {
            return this.operation.getType() != null;
        } else if (currentStep.equals(Step.SELECT_VM_IMAGE)) {
            return true;
        } else if (currentStep.equals(Step.SELECT_VM_IMAGE_FOR_SERVER)) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (serverProgress.getVmImage() == null) {
                    return false;
                }
            }
            return true;
        } else if (currentStep.equals(Step.SELECT_APPLICATION)) {
            return this.operation.getApplication() != null;
        } else if (currentStep.equals(Step.EXECUTING_MIGRATION) || currentStep.equals(Step.UNDEPLOY_ERASE_OLD_VERSION)) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (!serverProgress.getActionState().equals(ActionState.WAITING)) {
                    return false;
                }
            }
            return true;
        } else if (currentStep.equals(Step.SELECT_SERVERS)) {
            return this.operation.getServerProgressList() != null && this.operation.getServerProgressList().size() > 0;
        } else if (currentStep.equals(Step.EXECUTING_MAINTENANCE_CLUSTER)
            || currentStep.equals(Step.EXECUTING_MAINTENANCE_NO_CLUSTER) || currentStep.equals(Step.DESTROY_OLD_VM_HOSTS)) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (!serverProgress.getActionState().equals(ActionState.WAITING)) {
                    return false;
                }
            }
            for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                if (!workerProgress.getActionState().equals(ActionState.WAITING)) {
                    return false;
                }
            }
            return true;
        } else if (currentStep.equals(Step.FINISHED)) {
            return false;
        } else {
            throw new IllegalStateException("Unknown Step: " + currentStep);
        }
    }

    /**
     * Executes the actions for going to the next step.
     */
    protected void executeNextStep() throws JadortServiceException {
        Step currentStep = this.operation.getCurrentStep();

        if (currentStep.equals(Step.INITIALIZE_TOPOLOGY)) {
            for (GroupBean group : this.operation.getTopology().getGroups()) {
                if (group.getWorkers() != null) {
                    for (WorkerBean worker : group.getWorkers()) {
                        WorkerAction workerAction = WorkerAction.getWorkerAction(worker);
                        try {
                            workerAction.getState();
                        } catch (Exception e) {
                            String message = e.getClass().getName() + ": " + e.getMessage();
                            throw new JadortServiceException("Failed getting the state of the worker " + worker.getName()
                                + ": " + message, e);
                        }
                    }
                }
            }
        }

        if (currentStep.equals(OperationStateBean.Step.EXECUTING_MAINTENANCE_CLUSTER)
            || currentStep.equals(OperationStateBean.Step.EXECUTING_MAINTENANCE_NO_CLUSTER)) {
            if (this.operation.getAimedWorkerProgressState().equals(WorkerProgressState.START_OK)) {
                for (ServerProgressBean serverProgressBean : this.operation.getServerProgressList()) {
                    serverProgressBean.getServer().setProcessed(true);
                }
                for (WorkerProgressBean workerProgressBean : this.operation.getWorkerProgressList()) {
                    workerProgressBean.setActionState(ActionState.FINISHED_OK);
                }

                if (this.operation.getSelectedGroup().getServers().get(0).getVm() == null) {
                    // There are no VMs, prepare for Step.FINISHED
                    for (ServerProgressBean serverProgressBean : this.operation.getServerProgressList()) {
                        serverProgressBean.setActionState(ActionState.FINISHED_OK);
                    }

                    this.fillWorkerProgressListBasedOnServerProgressList();
                } else {
                    // Else, prepare for Step.DESTROY_OLD_VM_HOSTS
                    List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();
                    serverProgressList.addAll(this.operation.getAllServerProgressList());
                    this.operation.setServerProgressList(serverProgressList);
                    for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                        serverProgress.setProgress(0);
                    }
                }
                this.goToNextStep();
            } else {
                this.executeNextInOperation();
            }
        } else if (currentStep.equals(OperationStateBean.Step.DESTROY_OLD_VM_HOSTS)) {
            if (!this.operation.getAimedServerProgressState().equals(ServerProgressState.DESTROY_VM_HOSTS_OK)) {
                // Prepare for Step.FINISHED
                for (ServerProgressBean serverProgressBean : this.operation.getServerProgressList()) {
                    serverProgressBean.setActionState(ActionState.FINISHED_OK);
                }

                this.goToNextStep();
            } else {
                this.executeNextInOperation();
            }
        } else if (currentStep.equals(OperationStateBean.Step.EXECUTING_MIGRATION)) {
            if (this.operation.getAimedServerProgressState().equals(ServerProgressState.SET_DEFAULT_OK)) {
                for (ServerProgressBean serverProgressBean : this.operation.getServerProgressList()) {
                    serverProgressBean.getServer().setProcessed(true);
                }

                this.goToNextStep();
            } else {
                this.executeNextInOperation();
            }
        } else if (currentStep.equals(OperationStateBean.Step.UNDEPLOY_ERASE_OLD_VERSION)) {
            if (this.operation.getAimedServerProgressState().equals(ServerProgressState.ERASE_OK)) {
                for (ServerProgressBean serverProgressServer : this.operation.getAllServerProgressList()) {
                    serverProgressServer.setActionState(ActionState.FINISHED_OK);
                }

                this.goToNextStep();
            } else {
                this.executeNextInOperation();
            }
        } else {
            this.goToNextStep();
        }
    }

    /**
     * Go the next step
     */
    private void goToNextStep() throws JadortServiceException {
        Step currentStep = this.operation.getCurrentStep();

        if (currentStep.equals(Step.SELECT_OPERATION)) {
            this.operation.setCurrentStep(Step.INITIALIZE_TOPOLOGY);
        } else if (currentStep.equals(Step.INITIALIZE_TOPOLOGY)) {
            this.operation.setCurrentStep(Step.SELECT_GROUP);
        } else if (currentStep.equals(Step.SELECT_GROUP)) {
            this.operation.setCurrentStep(Step.SELECT_OPERATION_TYPE);
        } else if (currentStep.equals(Step.SELECT_OPERATION_TYPE)) {
            IJadortService.OperationType operationType = this.operation.getType();
            if (operationType.equals(IJadortService.OperationType.MIGRATE)) {
                this.operation.setCurrentStep(Step.SELECT_APPLICATION);
            } else if (operationType.equals(IJadortService.OperationType.MAINTAIN)) {
                if (this.operation.getSelectedGroup().getServers().get(0).getVm() != null) {
                    this.operation.setCurrentStep(Step.SELECT_VM_IMAGE);
                } else {
                    this.operation.setCurrentStep(Step.SELECT_SERVERS);
                }
            } else {
                throw new IllegalStateException("Unknown OperationType: " + operationType);
            }
        } else if (currentStep.equals(Step.SELECT_VM_IMAGE)) {
            this.operation.setCurrentStep(Step.SELECT_SERVERS);
        } else if (currentStep.equals(Step.SELECT_SERVERS)) {
            IJadortService.OperationType operationType = this.operation.getType();
            if (operationType.equals(IJadortService.OperationType.MIGRATE)) {
                this.operation.setCurrentStep(Step.EXECUTING_MIGRATION);
            } else if (operationType.equals(IJadortService.OperationType.MAINTAIN)) {
                if (this.operation.getSelectedGroup().getServers().get(0).getVm() != null) {
                    for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                        if (serverProgress.getVmImage() == null) {
                            // There's at least one server without any VM image
                            this.operation.setCurrentStep(Step.SELECT_VM_IMAGE_FOR_SERVER);
                            break;
                        }
                    }
                }

                // Use getCurrentStep and not currentStep since
                // the for loop might have called setCurrentStep
                if (this.operation.getCurrentStep().equals(Step.SELECT_SERVERS)) {
                    if (this.operation.getSelectedGroup().getClustered()) {
                        this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_CLUSTER);
                    } else {
                        this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_NO_CLUSTER);
                    }
                }
            } else {
                throw new IllegalStateException("Unknown OperationType: " + operationType);
            }
        } else if (currentStep.equals(Step.SELECT_VM_IMAGE_FOR_SERVER)) {
            if (this.operation.getSelectedGroup().getClustered()) {
                this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_CLUSTER);
            } else {
                this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_NO_CLUSTER);
            }
        } else if (currentStep.equals(Step.EXECUTING_MAINTENANCE_CLUSTER)
            || currentStep.equals(Step.EXECUTING_MAINTENANCE_NO_CLUSTER)) {
            if (this.allServersHaveBeenProcessed()) {
                if (this.operation.getSelectedGroup().getServers().get(0).getVm() == null) {
                    List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();
                    serverProgressList.addAll(this.operation.getAllServerProgressList());
                    this.operation.setServerProgressList(serverProgressList);

                    this.operation.setCurrentStep(Step.FINISHED);
                } else {
                    List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();
                    for (ServerProgressBean serverProgressBean : this.operation.getAllServerProgressList()) {
                        if (serverProgressBean.hasOldDeploymentItem()) {
                            serverProgressList.add(serverProgressBean);
                            serverProgressBean.setProgress(0);
                        }
                    }
                    this.operation.setServerProgressList(serverProgressList);

                    // Use an empty workers progress list since we'll
                    // DESTROY_OLD_VM_HOSTS, therefore won't touch workers
                    this.operation.setWorkerProgressList(new ArrayList<WorkerProgressBean>());

                    this.operation.setCurrentStep(Step.DESTROY_OLD_VM_HOSTS);
                }
            } else {
                this.operation.setAimedProgressPercent(0);
                this.operation.setServerProgressList(null);
                this.operation.setWorkerProgressList(null);
                this.operation.setAimedWorkerProgressState(WorkerProgressState.INITIAL);
                this.operation.setAimedServerProgressState(ServerProgressState.INITIAL);
                this.operation.setCurrentStep(Step.SELECT_SERVERS);
            }
        } else if (currentStep.equals(Step.DESTROY_OLD_VM_HOSTS)) {
            List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();
            serverProgressList.addAll(this.operation.getAllServerProgressList());
            this.operation.setServerProgressList(serverProgressList);

            this.fillWorkerProgressListBasedOnServerProgressList();

            this.operation.setCurrentStep(Step.FINISHED);
        } else if (currentStep.equals(Step.SELECT_APPLICATION)) {
            this.operation.setCurrentStep(Step.SELECT_SERVERS);
        } else if (currentStep.equals(Step.EXECUTING_MIGRATION)) {
            if (this.allServersHaveBeenProcessed()) {
                if (this.hasOldVersion()) {
                    this.operation.setCurrentStep(Step.UNDEPLOY_ERASE_OLD_VERSION);
                    List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();
                    for (ServerProgressBean serverProgressBean : this.operation.getAllServerProgressList()) {
                        if (serverProgressBean.hasOldDeploymentItem()) {
                            serverProgressList.add(serverProgressBean);
                            serverProgressBean.setProgress(0);
                        }
                    }
                    this.operation.setServerProgressList(serverProgressList);
                } else {
                    List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();
                    serverProgressList.addAll(this.operation.getAllServerProgressList());
                    this.operation.setServerProgressList(serverProgressList);
                    this.operation.setCurrentStep(Step.FINISHED);
                }
            } else {
                this.operation.setAimedProgressPercent(0);
                this.operation.setServerProgressList(null);
                this.operation.setAimedServerProgressState(ServerProgressState.INITIAL);
                this.operation.setCurrentStep(Step.SELECT_SERVERS);
            }
        } else if (currentStep.equals(Step.UNDEPLOY_ERASE_OLD_VERSION)) {
            List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();
            serverProgressList.addAll(this.operation.getAllServerProgressList());
            this.operation.setServerProgressList(serverProgressList);

            this.operation.setCurrentStep(Step.FINISHED);
        } else if (currentStep.equals(Step.FINISHED)) {
            throw new IllegalStateException("No next for Step: " + currentStep);
        } else {
            throw new IllegalStateException("Unknown Step: " + currentStep);
        }
        this.mergeOperation();
    }

    /**
     * This function allows to execute the next sub step in the migration
     * execution step
     */
    private void executeNextInOperation() {
        ServerProgressState aimedServerProgressState = this.operation.getAimedServerProgressState();
        WorkerProgressState aimedWorkerProgressState = this.operation.getAimedWorkerProgressState();

        if (this.operation.getType().equals(OperationType.MAINTAIN)) {
            if (this.operation.getSelectedGroup().getClustered()) {
                if (aimedWorkerProgressState.equals(WorkerProgressState.INITIAL)) {
                    this.operation.setAimedWorkerProgressState(WorkerProgressState.STOP_OK);
                    this.operation.setAimedProgressPercent(20);
                } else if (aimedServerProgressState.equals(ServerProgressState.INITIAL)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.STOP_OK);
                    this.operation.setAimedProgressPercent(40);
                } else if (aimedServerProgressState.equals(ServerProgressState.STOP_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.MAINTAIN_OK);
                    this.operation.setAimedProgressPercent(60);
                } else if (aimedServerProgressState.equals(ServerProgressState.MAINTAIN_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.START_OK);
                    this.operation.setAimedProgressPercent(80);
                } else if (aimedWorkerProgressState.equals(WorkerProgressState.STOP_OK)) {
                    this.operation.setAimedWorkerProgressState(WorkerProgressState.START_OK);
                    this.operation.setAimedProgressPercent(100);
                } else if (aimedServerProgressState.equals(ServerProgressState.START_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.DESTROY_VM_HOSTS_OK);
                    this.operation.setAimedProgressPercent(100);
                } else {
                    throw new IllegalStateException("Unknown ServerProgressState or WorkerProgressState: "
                        + aimedServerProgressState + ", " + aimedWorkerProgressState);
                }
            } else {
                if (aimedWorkerProgressState.equals(WorkerProgressState.INITIAL)) {
                    this.operation.setAimedWorkerProgressState(WorkerProgressState.STOP_OK);
                    this.operation.setAimedProgressPercent(16);
                } else if (aimedServerProgressState.equals(ServerProgressState.INITIAL)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.DISABLE_APPLICATIONS_OK);
                    this.operation.setAimedProgressPercent(33);
                } else if (aimedServerProgressState.equals(ServerProgressState.DISABLE_APPLICATIONS_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.STOP_OK);
                    this.operation.setAimedProgressPercent(50);
                } else if (aimedServerProgressState.equals(ServerProgressState.STOP_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.MAINTAIN_OK);
                    this.operation.setAimedProgressPercent(66);
                } else if (aimedServerProgressState.equals(ServerProgressState.MAINTAIN_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.START_OK);
                    this.operation.setAimedProgressPercent(83);
                } else if (aimedWorkerProgressState.equals(WorkerProgressState.STOP_OK)) {
                    this.operation.setAimedWorkerProgressState(WorkerProgressState.START_OK);
                    this.operation.setAimedProgressPercent(100);
                } else if (aimedServerProgressState.equals(ServerProgressState.START_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.DESTROY_VM_HOSTS_OK);
                    this.operation.setAimedProgressPercent(100);
                } else {
                    throw new IllegalStateException("Unknown ServerProgressState or WorkerProgressState: "
                        + aimedServerProgressState + ", " + aimedWorkerProgressState);
                }
            }
        } else if (this.operation.getType().equals(OperationType.MIGRATE)) {
            if (aimedServerProgressState.equals(ServerProgressState.INITIAL)) {
                this.operation.setAimedServerProgressState(ServerProgressState.UPLOAD_OK);
                this.operation.setAimedProgressPercent(33);
            } else if (aimedServerProgressState.equals(ServerProgressState.UPLOAD_OK)) {
                this.operation.setAimedServerProgressState(ServerProgressState.DEPLOY_OK);
                this.operation.setAimedProgressPercent(66);
            } else if (aimedServerProgressState.equals(ServerProgressState.DEPLOY_OK)) {
                this.operation.setAimedServerProgressState(ServerProgressState.SET_DEFAULT_OK);
                this.operation.setAimedProgressPercent(100);
            } else if (aimedServerProgressState.equals(ServerProgressState.SET_DEFAULT_OK)) {
                this.operation.setAimedServerProgressState(ServerProgressState.UNDEPLOY_OK);
                this.operation.setAimedProgressPercent(50);
            } else if (aimedServerProgressState.equals(ServerProgressState.UNDEPLOY_OK)) {
                this.operation.setAimedServerProgressState(ServerProgressState.ERASE_OK);
                this.operation.setAimedProgressPercent(100);
            } else {
                throw new IllegalStateException("Unknown ServerProgressState: " + aimedServerProgressState);
            }
        } else {
            throw new IllegalStateException("Unknown OperationType: " + this.operation.getType());
        }

        if (this.operation.getServerProgressList() != null) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (serverProgress.getProgressState().equals(this.operation.getAimedServerProgressState())) {
                    serverProgress.setActionState(ActionState.WAITING);
                    serverProgress.setProgress(this.operation.getAimedProgressPercent());
                } else {
                    serverProgress.setActionState(ActionState.RUNNING);
                }
            }
        }
        if (this.operation.getWorkerProgressList() != null) {
            for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                if (workerProgress.getProgressState().equals(this.operation.getAimedWorkerProgressState())) {
                    workerProgress.setActionState(ActionState.WAITING);
                    workerProgress.setProgress(this.operation.getAimedProgressPercent());
                } else {
                    workerProgress.setActionState(ActionState.RUNNING);
                }
            }
        }

        this.mergeOperation();

        if (this.operation.getServerProgressList() != null) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (serverProgress.getActionState().equals(ActionState.RUNNING)) {
                    this.reachAimedServerProgressState(serverProgress);
                }
            }
        }
        if (this.operation.getWorkerProgressList() != null) {
            for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                if (workerProgress.getActionState().equals(ActionState.RUNNING)) {
                    this.reachAimedWorkerProgressState(workerProgress);
                }
            }
        }
    }

    /**
     * @return true if going to the previous step is allowed, false otherwise.
     */
    protected boolean canGoToPreviousStep() {
        Step currentStep = this.operation.getCurrentStep();
        if (currentStep.equals(Step.SELECT_OPERATION) || currentStep.equals(Step.INITIALIZE_TOPOLOGY)) {
            return false;
        } else if (currentStep.equals(Step.SELECT_GROUP) || currentStep.equals(Step.SELECT_APPLICATION)
            || currentStep.equals(Step.SELECT_VM_IMAGE) || currentStep.equals(Step.SELECT_VM_IMAGE_FOR_SERVER)
            || currentStep.equals(Step.SELECT_OPERATION_TYPE)) {
            return true;
        } else if (currentStep.equals(Step.EXECUTING_MIGRATION) || currentStep.equals(Step.UNDEPLOY_ERASE_OLD_VERSION)) {
            if (this.operation.getAimedServerProgressState().equals(ServerProgressState.ERASE_OK)) {
                return false;
            }

            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (serverProgress.getActionState().equals(ActionState.RUNNING)) {
                    return false;
                }
            }
            return true;
        } else if (currentStep.equals(Step.SELECT_SERVERS)) {
            return true;
        } else if (currentStep.equals(Step.EXECUTING_MAINTENANCE_CLUSTER)
            || currentStep.equals(Step.EXECUTING_MAINTENANCE_NO_CLUSTER)) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (serverProgress.getActionState().equals(ActionState.RUNNING)) {
                    return false;
                }
            }
            for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                if (workerProgress.getActionState().equals(ActionState.RUNNING)) {
                    return false;
                }
            }
            return true;
        } else if (currentStep.equals(Step.DESTROY_OLD_VM_HOSTS)) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (serverProgress.getActionState().equals(ActionState.RUNNING) || serverProgress.getProgress() > 0) {
                    return false;
                }
            }
            return true;
        } else if (currentStep.equals(Step.FINISHED)) {
            return true;
        } else {
            throw new IllegalStateException("Unknown Step: " + currentStep);
        }
    }

    /**
     * Executes the actions for going to the previous step.
     */
    protected void executePreviousStep() throws JadortServiceException {
        Step currentStep = this.operation.getCurrentStep();

        if (currentStep.equals(OperationStateBean.Step.SELECT_GROUP)) {
            TopologyBean oldTopology = this.operation.getTopology();
            this.operation.setTopology(null);
            this.goToPreviousStep();

            this.em.remove(oldTopology);
            this.em.flush();
        } else if (currentStep.equals(OperationStateBean.Step.SELECT_APPLICATION)) {
            // Don't remove the oldSelectedGroup, as the GroupBeans are
            // created at topology initialization and no copy is done when
            // selecting group).
            //
            // Remove the old progress lists as they're useless now.
            // Remember to copy them in order to avoid exceptions
            List<ServerProgressBean> oldServerProgressList = new ArrayList<ServerProgressBean>();
            if (this.operation.getServerProgressList() != null) {
                oldServerProgressList.addAll(this.operation.getServerProgressList());
            }
            this.operation.setServerProgressList(null);
            this.operation.setType(null);
            ApplicationBean oldApplication = this.operation.getApplication();
            this.operation.setApplication(null);
            this.goToPreviousStep();

            for (ServerProgressBean serverProgressBean : oldServerProgressList) {
                this.em.remove(serverProgressBean);
            }
            if (oldApplication != null) {
                this.em.remove(oldApplication);
            }
            this.em.flush();
        } else if (currentStep.equals(OperationStateBean.Step.SELECT_OPERATION_TYPE)) {
            this.operation.setSelectedGroup(null);
            this.goToPreviousStep();
        } else if (currentStep.equals(OperationStateBean.Step.SELECT_VM_IMAGE)) {
            this.operation.setType(null);
            this.goToPreviousStep();
        } else if (currentStep.equals(OperationStateBean.Step.SELECT_VM_IMAGE_FOR_SERVER)) {
            this.goToPreviousStep();
        } else if (currentStep.equals(OperationStateBean.Step.SELECT_SERVERS)) {
            if (this.anyProcessedServerPresent()) {
                this.operation.setAimedProgressPercent(100);

                List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();
                for (ServerProgressBean serverProgress : this.operation.getAllServerProgressList()) {
                    if (serverProgress.getServer().getProcessed()) {
                        serverProgress.setProgress(this.operation.getAimedProgressPercent());
                        serverProgress.setActionState(ActionState.WAITING);
                        serverProgress.getServer().setProcessed(false);
                        serverProgressList.add(serverProgress);
                    }
                }
                this.operation.setServerProgressList(serverProgressList);

                IJadortService.OperationType operationType = this.operation.getType();
                if (operationType.equals(IJadortService.OperationType.MIGRATE)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.SET_DEFAULT_OK);
                    this.operation.setCurrentStep(Step.EXECUTING_MIGRATION);
                } else if (operationType.equals(IJadortService.OperationType.MAINTAIN)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.START_OK);
                    this.operation.setAimedWorkerProgressState(WorkerProgressState.START_OK);

                    this.fillWorkerProgressListBasedOnServerProgressList();
                    for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                        workerProgress.setProgress(this.operation.getAimedProgressPercent());
                        workerProgress.setActionState(ActionState.WAITING);
                    }

                    if (this.operation.getSelectedGroup().getClustered()) {
                        this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_CLUSTER);
                    } else {
                        this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_NO_CLUSTER);
                    }
                } else {
                    throw new IllegalStateException("Unknown OperationType: " + operationType);
                }

                this.mergeOperation();
            } else {
                // Don't remove the oldSelectedGroup, as the GroupBeans are
                // created at topology initialization and no copy is done
                // when selecting group).
                //
                // Remove the old progress lists as they're useless now.
                // Remember to copy them in order to avoid exceptions
                Set<ServerProgressBean> oldServerProgressList = new HashSet<ServerProgressBean>();
                if (this.operation.getServerProgressList() != null) {
                    oldServerProgressList.addAll(this.operation.getServerProgressList());
                }
                if (this.operation.getAllServerProgressList() != null) {
                    oldServerProgressList.addAll(this.operation.getAllServerProgressList());
                }
                Set<WorkerProgressBean> oldWorkerProgressList = new HashSet<WorkerProgressBean>();
                if (this.operation.getWorkerProgressList() != null) {
                    oldWorkerProgressList.addAll(this.operation.getWorkerProgressList());
                }
                if (this.operation.getAllWorkerProgressList() != null) {
                    oldWorkerProgressList.addAll(this.operation.getAllWorkerProgressList());
                }
                this.operation.setServerProgressList(null);
                this.operation.setWorkerProgressList(null);
                this.operation.setAllServerProgressList(null);
                this.operation.setAllWorkerProgressList(null);

                VMImageBean oldVMImage = this.operation.getVmImage();
                this.operation.setVmImage(null);

                this.goToPreviousStep();

                for (WorkerProgressBean workerProgressBean : oldWorkerProgressList) {
                    this.em.remove(workerProgressBean);
                }
                for (ServerProgressBean serverProgressBean : oldServerProgressList) {
                    this.em.remove(serverProgressBean);
                }
                if (oldVMImage != null) {
                    this.em.remove(oldVMImage);
                }
                this.em.flush();
            }
        } else if (currentStep.equals(OperationStateBean.Step.EXECUTING_MAINTENANCE_CLUSTER)) {
            if (this.operation.getAimedWorkerProgressState().equals(WorkerProgressState.INITIAL)) {
                for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                    serverProgress.setProgress(this.operation.getAimedProgressPercent());
                    serverProgress.setActionState(ActionState.WAITING);
                }
                for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                    workerProgress.setProgress(this.operation.getAimedProgressPercent());
                    workerProgress.setActionState(ActionState.WAITING);
                }
                this.goToPreviousStep();
            } else {
                this.executePreviousInOperation();
            }
        } else if (currentStep.equals(OperationStateBean.Step.EXECUTING_MAINTENANCE_NO_CLUSTER)) {
            if (this.operation.getAimedWorkerProgressState().equals(WorkerProgressState.INITIAL)) {
                for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                    serverProgress.setProgress(this.operation.getAimedProgressPercent());
                    serverProgress.setActionState(ActionState.WAITING);
                }
                for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                    workerProgress.setProgress(this.operation.getAimedProgressPercent());
                    workerProgress.setActionState(ActionState.WAITING);
                }
                this.goToPreviousStep();
            } else {
                this.executePreviousInOperation();
            }
        } else if (currentStep.equals(OperationStateBean.Step.DESTROY_OLD_VM_HOSTS)) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                serverProgress.setProgress(this.operation.getAimedProgressPercent());
                serverProgress.setProgressState(ServerProgressState.START_OK);
                serverProgress.setActionState(ActionState.WAITING);
            }
            this.fillWorkerProgressListBasedOnServerProgressList();
            for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                workerProgress.setProgress(this.operation.getAimedProgressPercent());
                workerProgress.setProgressState(WorkerProgressState.START_OK);
                workerProgress.setActionState(ActionState.WAITING);
            }
            this.goToPreviousStep();
        } else if (currentStep.equals(OperationStateBean.Step.EXECUTING_MIGRATION)) {
            if (this.operation.getAimedServerProgressState().equals(ServerProgressState.INITIAL)) {
                this.goToPreviousStep();
            } else {
                this.executePreviousInOperation();
            }
        } else if (currentStep.equals(OperationStateBean.Step.UNDEPLOY_ERASE_OLD_VERSION)) {
            if (this.operation.getAimedServerProgressState().equals(ServerProgressState.SET_DEFAULT_OK)) {
                List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();
                for (ServerProgressBean serverProgress : this.operation.getAllServerProgressList()) {
                    serverProgress.getServer().setProcessed(false);
                    serverProgress.setProgress(100);
                    serverProgressList.add(serverProgress);
                }
                this.operation.setServerProgressList(serverProgressList);
                this.goToPreviousStep();
            } else {
                this.executePreviousInOperation();
            }
        } else if (currentStep.equals(OperationStateBean.Step.FINISHED)) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                serverProgress.setProgress(this.operation.getAimedProgressPercent());
                serverProgress.setActionState(ActionState.WAITING);
                serverProgress.getServer().setProcessed(false);
            }

            if (this.operation.getType().equals(OperationType.MAINTAIN)) {
                for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                    workerProgress.setProgress(this.operation.getAimedProgressPercent());
                    workerProgress.setActionState(ActionState.WAITING);
                }
            }
            this.goToPreviousStep();
        } else {
            throw new IllegalStateException("Unknown Step: " + currentStep);
        }
    }

    /**
     * Go the previous step
     */
    private void goToPreviousStep() throws JadortServiceException {
        Step currentStep = this.operation.getCurrentStep();
        List<VMImageBean> oldVMImages = new ArrayList<VMImageBean>();

        if (currentStep.equals(Step.SELECT_OPERATION) || currentStep.equals(Step.INITIALIZE_TOPOLOGY)) {
            throw new IllegalStateException("No previous for Step: " + currentStep);
        } else if (currentStep.equals(Step.SELECT_GROUP)) {
            this.operation.setCurrentStep(Step.INITIALIZE_TOPOLOGY);
        } else if (currentStep.equals(Step.SELECT_APPLICATION)) {
            this.operation.setCurrentStep(Step.SELECT_OPERATION_TYPE);
        } else if (currentStep.equals(Step.SELECT_VM_IMAGE)) {
            this.operation.setCurrentStep(Step.SELECT_OPERATION_TYPE);
        } else if (currentStep.equals(Step.SELECT_OPERATION_TYPE)) {
            this.operation.setType(null);
            this.operation.setCurrentStep(Step.SELECT_GROUP);
        } else if (currentStep.equals(Step.SELECT_SERVERS)) {
            IJadortService.OperationType operationType = this.operation.getType();
            if (operationType.equals(IJadortService.OperationType.MIGRATE)) {
                this.operation.setCurrentStep(Step.SELECT_APPLICATION);
            } else if (operationType.equals(IJadortService.OperationType.MAINTAIN)) {
                if (this.operation.getSelectedGroup().getServers().get(0).getVm() != null) {
                    this.operation.setCurrentStep(Step.SELECT_VM_IMAGE);
                } else {
                    this.operation.setType(null);
                    this.operation.setCurrentStep(Step.SELECT_OPERATION_TYPE);
                }
            } else {
                throw new IllegalStateException("Unknown OperationType: " + operationType);
            }
        } else if (currentStep.equals(Step.EXECUTING_MAINTENANCE_CLUSTER)
            || currentStep.equals(Step.EXECUTING_MAINTENANCE_NO_CLUSTER) || currentStep.equals(Step.SELECT_VM_IMAGE_FOR_SERVER)) {
            this.operation.setCurrentStep(Step.SELECT_SERVERS);

            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                serverProgress.setOldDeploymentItem(null);

                if (serverProgress.getVmImage() != null) {
                    oldVMImages.add(serverProgress.getVmImage());
                    serverProgress.setVmImage(null);
                }
            }
            this.operation.setServerProgressList(null);
            this.operation.setWorkerProgressList(null);
        } else if (currentStep.equals(Step.DESTROY_OLD_VM_HOSTS)) {
            if (this.operation.getSelectedGroup().getClustered()) {
                this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_CLUSTER);
            } else {
                this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_NO_CLUSTER);
            }
        } else if (currentStep.equals(Step.EXECUTING_MIGRATION)) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                serverProgress.setOldDeploymentItem(null);
            }
            this.operation.setCurrentStep(Step.SELECT_SERVERS);
            this.operation.setServerProgressList(null);
        } else if (currentStep.equals(Step.UNDEPLOY_ERASE_OLD_VERSION)) {
            this.operation.setAimedProgressPercent(100);
            this.operation.setCurrentStep(Step.EXECUTING_MIGRATION);
        } else if (currentStep.equals(Step.FINISHED)) {
            this.operation.setAimedProgressPercent(100);
            IJadortService.OperationType operationType = this.operation.getType();
            if (operationType.equals(IJadortService.OperationType.MIGRATE)) {
                if (this.hasOldVersion()) {
                    this.operation.setCurrentStep(Step.UNDEPLOY_ERASE_OLD_VERSION);
                } else {
                    this.operation.setCurrentStep(Step.EXECUTING_MIGRATION);
                }
            } else if (operationType.equals(IJadortService.OperationType.MAINTAIN)) {
                if (this.operation.getSelectedGroup().getServers().get(0).getVm() != null) {
                    this.operation.setCurrentStep(Step.DESTROY_OLD_VM_HOSTS);
                } else if (this.operation.getSelectedGroup().getClustered()) {
                    this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_CLUSTER);
                } else {
                    this.operation.setCurrentStep(Step.EXECUTING_MAINTENANCE_NO_CLUSTER);
                }
            } else {
                throw new IllegalStateException("Unknown OperationType: " + operationType);
            }
        } else {
            throw new IllegalStateException("Unknown Step: " + currentStep);
        }

        this.mergeOperation();

        for (VMImageBean oldVMImage : oldVMImages) {
            this.em.remove(oldVMImage);
            this.em.flush();
        }
    }

    /**
     * this function allows to execute the previous sub step in the migration
     * execution step
     */
    private void executePreviousInOperation() {
        ServerProgressState aimedServerProgressState = this.operation.getAimedServerProgressState();
        WorkerProgressState aimedWorkerProgressState = this.operation.getAimedWorkerProgressState();

        if (this.operation.getType().equals(OperationType.MAINTAIN)) {
            if (this.operation.getSelectedGroup().getClustered()) {
                if (aimedWorkerProgressState.equals(WorkerProgressState.START_OK)) {
                    this.operation.setAimedWorkerProgressState(WorkerProgressState.STOP_OK);
                    this.operation.setAimedProgressPercent(80);
                } else if (aimedServerProgressState.equals(ServerProgressState.START_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.MAINTAIN_OK);
                    this.operation.setAimedProgressPercent(60);
                } else if (aimedServerProgressState.equals(ServerProgressState.MAINTAIN_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.STOP_OK);
                    this.operation.setAimedProgressPercent(40);
                } else if (aimedServerProgressState.equals(ServerProgressState.STOP_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.INITIAL);
                    this.operation.setAimedProgressPercent(20);
                } else if (aimedWorkerProgressState.equals(WorkerProgressState.STOP_OK)) {
                    this.operation.setAimedWorkerProgressState(WorkerProgressState.INITIAL);
                    this.operation.setAimedProgressPercent(0);
                } else {
                    throw new IllegalStateException("Unknown ServerProgressState or WorkerProgressState: "
                        + aimedServerProgressState + ", " + aimedWorkerProgressState);
                }
            } else {
                if (aimedWorkerProgressState.equals(WorkerProgressState.START_OK)) {
                    this.operation.setAimedWorkerProgressState(WorkerProgressState.STOP_OK);
                    this.operation.setAimedProgressPercent(83);
                } else if (aimedServerProgressState.equals(ServerProgressState.START_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.MAINTAIN_OK);
                    this.operation.setAimedProgressPercent(66);
                } else if (aimedServerProgressState.equals(ServerProgressState.MAINTAIN_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.STOP_OK);
                    this.operation.setAimedProgressPercent(50);
                } else if (aimedServerProgressState.equals(ServerProgressState.STOP_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.DISABLE_APPLICATIONS_OK);
                    this.operation.setAimedProgressPercent(33);
                } else if (aimedServerProgressState.equals(ServerProgressState.DISABLE_APPLICATIONS_OK)) {
                    this.operation.setAimedServerProgressState(ServerProgressState.INITIAL);
                    this.operation.setAimedProgressPercent(16);
                } else if (aimedWorkerProgressState.equals(WorkerProgressState.STOP_OK)) {
                    this.operation.setAimedWorkerProgressState(WorkerProgressState.INITIAL);
                    this.operation.setAimedProgressPercent(0);
                } else {
                    throw new IllegalStateException("Unknown ServerProgressState or WorkerProgressState: "
                        + aimedServerProgressState + ", " + aimedWorkerProgressState);
                }
            }
        } else if (this.operation.getType().equals(OperationType.MIGRATE)) {
            if (aimedServerProgressState.equals(ServerProgressState.UPLOAD_OK)) {
                this.operation.setAimedServerProgressState(ServerProgressState.INITIAL);
                this.operation.setAimedProgressPercent(0);
            } else if (aimedServerProgressState.equals(ServerProgressState.DEPLOY_OK)) {
                this.operation.setAimedServerProgressState(ServerProgressState.UPLOAD_OK);
                this.operation.setAimedProgressPercent(33);
            } else if (aimedServerProgressState.equals(ServerProgressState.SET_DEFAULT_OK)) {
                this.operation.setAimedServerProgressState(ServerProgressState.DEPLOY_OK);
                this.operation.setAimedProgressPercent(66);
            } else if (aimedServerProgressState.equals(ServerProgressState.UNDEPLOY_OK)) {
                this.operation.setAimedServerProgressState(ServerProgressState.SET_DEFAULT_OK);
                this.operation.setAimedProgressPercent(0);
            } else {
                throw new IllegalStateException("Unknown ServerProgressState: " + aimedServerProgressState);
            }
        } else {
            throw new IllegalStateException("Unknown OperationType: " + this.operation.getType());
        }

        if (this.operation.getServerProgressList() != null) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (serverProgress.getProgressState().equals(this.operation.getAimedServerProgressState())) {
                    serverProgress.setActionState(ActionState.WAITING);
                    serverProgress.setProgress(this.operation.getAimedProgressPercent());
                } else {
                    serverProgress.setActionState(ActionState.RUNNING);
                }
            }
        }
        if (this.operation.getWorkerProgressList() != null) {
            for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                if (workerProgress.getProgressState().equals(this.operation.getAimedWorkerProgressState())) {
                    workerProgress.setActionState(ActionState.WAITING);
                    workerProgress.setProgress(this.operation.getAimedProgressPercent());
                } else {
                    workerProgress.setActionState(ActionState.RUNNING);
                }
            }
        }

        this.mergeOperation();

        if (this.operation.getServerProgressList() != null) {
            for (ServerProgressBean serverProgress : this.operation.getServerProgressList()) {
                if (serverProgress.getActionState().equals(ActionState.RUNNING)) {
                    this.reachAimedServerProgressState(serverProgress);
                }
            }
        }
        if (this.operation.getWorkerProgressList() != null) {
            for (WorkerProgressBean workerProgress : this.operation.getWorkerProgressList()) {
                if (workerProgress.getActionState().equals(ActionState.RUNNING)) {
                    this.reachAimedWorkerProgressState(workerProgress);
                }
            }
        }
    }

    /**
     * @return true if any serverProgress has an old version, false otherwise.
     */
    private boolean hasOldVersion() {
        for (ServerProgressBean serverProgress : this.operation.getAllServerProgressList()) {
            if (serverProgress.hasOldDeploymentItem()) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return true if all servers have been processed, false otherwise
     */
    private boolean allServersHaveBeenProcessed() {
        for (ServerBean server : this.operation.getSelectedGroup().getServers()) {
            if (!server.getProcessed()) {
                return false;
            }
        }
        return true;
    }

    /**
     * @return true if any server has been processed, false otherwise
     */
    private boolean anyProcessedServerPresent() {
        for (ServerBean server : this.operation.getSelectedGroup().getServers()) {
            if (server.getProcessed()) {
                return true;
            }
        }
        return false;
    }

    /**
     * This method checks if an method (or command) can be called in the current
     * step.
     * 
     * @throws JadortServiceException If the current operation is not in one of
     *         the expected states.
     * @param steps List of steps that are currently allowed.
     */
    protected void checkStep(final Step... steps) throws JadortServiceException {
        Step currentStep;
        if (this.operation == null) {
            currentStep = Step.SELECT_OPERATION;
        } else {
            currentStep = this.operation.getCurrentStep();
        }

        for (Step step : steps) {
            if (currentStep.equals(step)) {
                return;
            }
        }

        throw new InvalidStepException(currentStep, steps);
    }

}
