/**
 * 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: OperationStateBean.java 3601 2009-04-30 13:58:52Z alitokmen $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.api.entities.deployment;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.ow2.jasmine.jadort.api.IJadortService;
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;

/**
 * Bean that represents an operation
 * 
 * @author Malek Chahine
 * @author Remy Bresson
 * @author S. Ali Tokmen
 */
@Entity(name = "JaDOrT_OperationStateBean")
public class OperationStateBean implements Serializable {

    /**
     * Different steps of an operation.<br>
     * <br>
     * We don't use an enum here to preserve compatibility with IIOP runtimes on
     * Java EE 5. You can check out the bug report and comments on
     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781 for details.
     */
    public static final class Step implements Serializable {

        private static final long serialVersionUID = 8454822640970419681L;

        public static final Step SELECT_OPERATION = new Step("SELECT_OPERATION");

        public static final Step INITIALIZE_TOPOLOGY = new Step("INITIALIZE_TOPOLOGY");

        public static final Step SELECT_GROUP = new Step("SELECT_GROUP");

        public static final Step SELECT_OPERATION_TYPE = new Step("SELECT_OPERATION_TYPE");

        public static final Step SELECT_VM_IMAGE = new Step("SELECT_VM_IMAGE");

        public static final Step SELECT_APPLICATION = new Step("SELECT_APPLICATION");

        public static final Step EXECUTING_MIGRATION_PART1 = new Step("EXECUTING_MIGRATION_PART1");

        public static final Step EXECUTING_MIGRATION_PART2 = new Step("EXECUTING_MIGRATION_PART2");

        public static final Step SELECT_SERVERS = new Step("SELECT_SERVERS");

        public static final Step SELECT_VM_IMAGE_FOR_SERVER = new Step("SELECT_VM_IMAGE_FOR_SERVER");

        public static final Step EXECUTING_MAINTENANCE_CLUSTER = new Step("EXECUTING_MAINTENANCE_CLUSTER");

        public static final Step EXECUTING_MAINTENANCE_NO_CLUSTER = new Step("EXECUTING_MAINTENANCE_NO_CLUSTER");

        public static final Step DESTROY_OLD_VM_HOSTS = new Step("DESTROY_OLD_VM_HOSTS");

        public static final Step FINISHED = new Step("FINISHED");

        private static final Step[] values = new Step[] {Step.SELECT_OPERATION, Step.INITIALIZE_TOPOLOGY, Step.SELECT_GROUP,
            Step.SELECT_OPERATION_TYPE, Step.SELECT_VM_IMAGE, Step.SELECT_APPLICATION, Step.EXECUTING_MIGRATION_PART1,
            Step.EXECUTING_MIGRATION_PART2, Step.SELECT_SERVERS, Step.SELECT_VM_IMAGE_FOR_SERVER,
            Step.EXECUTING_MAINTENANCE_CLUSTER, Step.EXECUTING_MAINTENANCE_NO_CLUSTER, Step.DESTROY_OLD_VM_HOSTS, Step.FINISHED};

        private String step;

        /**
         * @param step Step.
         */
        private Step(final String step) {
            this.step = step;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(final Object object) {
            if ((object != null) && (object instanceof Step)) {
                Step step = (Step) object;
                if (step.step.equals(this.step)) {
                    return true;
                }
            }

            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return this.step.hashCode();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return this.step;
        }

        /**
         * @return All Step values.
         */
        public static Step[] values() {
            return Step.values;
        }

        /**
         * Get the Step for a string.
         * 
         * @param string Step string.
         * @return Step for that string.
         */
        public static Step valueOf(final String string) {
            for (Step step : Step.values) {
                if (step.step.equals(string)) {
                    return step;
                }
            }

            throw new IllegalArgumentException("Unknown value: " + string);
        }
    }

    /**
     * Different states for a progress action.<br>
     * <br>
     * We don't use an enum here to preserve compatibility with IIOP runtimes on
     * Java EE 5. You can check out the bug report and comments on
     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781 for details.
     */
    public static final class ActionState implements Serializable {

        private static final long serialVersionUID = -4302491945199396705L;

        public static final ActionState WAITING = new ActionState("WAITING");

        public static final ActionState RUNNING = new ActionState("RUNNING");

        public static final ActionState FINISHED_OK = new ActionState("FINISHED_OK");

        public static final ActionState FINISHED_ERROR = new ActionState("FINISHED_ERROR");

        private static final ActionState[] values = new ActionState[] {ActionState.WAITING, ActionState.RUNNING,
            ActionState.FINISHED_OK, ActionState.FINISHED_ERROR};

        private String actionState;

        /**
         * @param actionState ActionState.
         */
        private ActionState(final String actionState) {
            this.actionState = actionState;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(final Object object) {
            if ((object != null) && (object instanceof ActionState)) {
                ActionState actionState = (ActionState) object;
                if (actionState.actionState.equals(this.actionState)) {
                    return true;
                }
            }

            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return this.actionState.hashCode();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return this.actionState;
        }

        /**
         * @return All ActionState values.
         */
        public static ActionState[] values() {
            return ActionState.values;
        }

        /**
         * Get the ActionState for a string.
         * 
         * @param string ActionState string.
         * @return ActionState for that string.
         */
        public static ActionState valueOf(final String string) {
            for (ActionState actionState : ActionState.values) {
                if (actionState.actionState.equals(string)) {
                    return actionState;
                }
            }

            throw new IllegalArgumentException("Unknown value: " + string);
        }
    }

    /**
     * Auto-generated serial version ID.
     */
    private static final long serialVersionUID = -4060088928914512618L;

    /**
     * Auto-generated identifier of the OperationStateBean in the database.
     */
    @Id
    @GeneratedValue
    private Integer id;

    /**
     * Auto-generated version of the OperationStateBean. Used internally for
     * checking integrity.
     */
    @Version
    @SuppressWarnings("unused")
    private Integer version;

    /**
     * Operation creation date
     */
    @Temporal(TemporalType.TIMESTAMP)
    private Date date;

    /**
     * Operation name
     */
    private String name;

    /**
     * The topology to use with this operation
     */
    @OneToOne(cascade = CascadeType.ALL)
    private TopologyBean topology;

    /**
     * Active serverProgressList of the operation
     */
    @OneToMany(cascade = CascadeType.ALL)
    private List<ServerBean> selectedServers;

    /**
     * Active ServerProgressBean objects of the operation
     */
    @OneToMany(cascade = CascadeType.ALL)
    private List<ServerProgressBean> serverProgressList;

    /**
     * Active WorkerProgressBean objects of the operation
     */
    @OneToMany(cascade = CascadeType.ALL)
    private List<WorkerProgressBean> workerProgressList;

    /**
     * All (currently active + past) ServerProgressBean objects of the operation
     * We need the @JoinTable otherwise there are two @OneToMany relations with
     * the same source and destination types, creating exceptions. We use a Set
     * and not a List since the elements in here need to be unique.
     */
    @OneToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "JaDOrT_OperationStateBean_allServerProgressList")
    private Set<ServerProgressBean> allServerProgressList;

    /**
     * All (currently active + past) WorkerProgressBean objects of the operation
     * We need the @JoinTable otherwise there are two @OneToMany relations with
     * the same source and destination types, creating exceptions. We use a Set
     * and not a List since the elements in here need to be unique.
     */
    @OneToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "JaDOrT_OperationStateBean_allWorkerProgressList")
    private Set<WorkerProgressBean> allWorkerProgressList;

    /**
     * The application the operation has to deploy
     */
    @OneToOne(cascade = CascadeType.ALL)
    private ApplicationBean application;

    /**
     * The VM image the operation has to deploy
     */
    @OneToOne(cascade = CascadeType.ALL)
    private VMImageBean vmImage;

    /**
     * The state of serverProgress the operation has to reach (use for the
     * serverProgress phase (upload/deploy/set default/...)
     */
    private ServerProgressState aimedServerProgressState = ServerProgressState.INITIAL;

    /**
     * The state of workerProgress the operation has to reach (use for the
     * serverProgress phase (start/stop/...)
     */
    private WorkerProgressState aimedWorkerProgressState = WorkerProgressState.INITIAL;

    /**
     * The progress percent to reach, when the operation is in the
     * {@link Step#EXECUTING_MIGRATION_PART1},
     * {@link Step#EXECUTING_MIGRATION_PART2},
     * {@link Step#EXECUTING_MAINTENANCE_CLUSTER} or
     * {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps.
     */
    private int aimedProgressPercent;

    /**
     * Current operation step
     */
    private Step currentStep;

    /**
     * Whether we can go to the next step<br/>
     * <br/>
     * This value is re-set every time the object is refreshed on the stateful
     * bean, and not persisted.
     */
    @Transient
    private boolean canGoToNextStep;

    /**
     * Whether we can go to the next step<br/>
     * <br/>
     * This value is re-set every time the object is refreshed on the stateful
     * bean, and not persisted.
     */
    @Transient
    private boolean canGoToPreviousStep;

    /**
     * The selected group of server
     */
    @OneToOne
    private GroupBean selectedGroup;

    /**
     * Operation type
     */
    private IJadortService.OperationType type;

    /**
     * Default OperationStateBean constructor
     */
    public OperationStateBean() {
        this.aimedProgressPercent = 0;
        this.currentStep = Step.INITIALIZE_TOPOLOGY;
    }

    /**
     * Get the operation id
     * 
     * @return the operation id
     */
    public Integer getId() {
        return this.id;
    }

    /**
     * Set the operation id
     */
    public void setId(final Integer id) {
        this.id = id;
    }

    /**
     * Get the serverProgress step to reach
     * 
     * @return serverProgress step to reach
     */
    public ServerProgressState getAimedServerProgressState() {
        return this.aimedServerProgressState;
    }

    /**
     * Set the serverProgress step to reach
     * 
     * @param globalServerProgressState the new serverProgress step to reach
     */
    public void setAimedServerProgressState(final ServerProgressState globalServerProgressState) {
        this.aimedServerProgressState = globalServerProgressState;
    }

    /**
     * Get the progress percent to reach, when the operation is in the
     * {@link Step#EXECUTING_MIGRATION_PART1},
     * {@link Step#EXECUTING_MIGRATION_PART2},
     * {@link Step#EXECUTING_MAINTENANCE_CLUSTER} or
     * {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps.
     * 
     * @return The progress percent to reach.
     */
    public int getAimedProgressPercent() {
        return this.aimedProgressPercent;
    }

    /**
     * Set the progress percent to reach, when the operation is in the
     * {@link Step#EXECUTING_MIGRATION_PART1},
     * {@link Step#EXECUTING_MIGRATION_PART2},
     * {@link Step#EXECUTING_MAINTENANCE_CLUSTER} or
     * {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps.
     * 
     * @param aimedProgressPercent The progress percent to reach.
     */
    public void setAimedProgressPercent(final int aimedProgressPercent) {
        if (aimedProgressPercent < 0 || aimedProgressPercent > 100) {
            throw new IllegalArgumentException("AimedProgressPercent is a percentage (between 0 and 100)");
        }

        this.aimedProgressPercent = aimedProgressPercent;
    }

    /**
     * Get the current operation step
     * 
     * @return the current operation step
     */
    public Step getCurrentStep() {
        return this.currentStep;
    }

    /**
     * Set the current operation step
     * 
     * @param step the current operation step
     * @throws IllegalArgumentException If step is {@link Step#SELECT_OPERATION}
     */
    public void setCurrentStep(final Step step) throws IllegalArgumentException {
        if (Step.SELECT_OPERATION.equals(step)) {
            throw new IllegalArgumentException("You cannot set a real operation's step to " + Step.SELECT_OPERATION);
        }
        this.currentStep = step;
    }

    /**
     * @return Whether we can go to the next step<br/>
     * <br/>
     *         This value is re-set every time the object is refreshed on the
     *         stateful bean, and not persisted.
     */
    public boolean getCanGoToNextStep() {
        return this.canGoToNextStep;
    }

    /**
     * @param canGoToNextStep Whether we can go to the next step<br/>
     * <br/>
     *        This value is re-set every time the object is refreshed on the
     *        stateful bean, and not persisted.
     */
    public void setCanGoToNextStep(final boolean canGoToNextStep) {
        this.canGoToNextStep = canGoToNextStep;
    }

    /**
     * @return Whether we can go to the previous step<br/>
     * <br/>
     *         This value is re-set every time the object is refreshed on the
     *         stateful bean, and not persisted.
     */
    public boolean getCanGoToPreviousStep() {
        return this.canGoToPreviousStep;
    }

    /**
     * @param canGoToNextStep Whether we can go to the previous step<br/>
     * <br/>
     *        This value is re-set every time the object is refreshed on the
     *        stateful bean, and not persisted.
     */
    public void setCanGoToPreviousStep(final boolean canGoToPreviousStep) {
        this.canGoToPreviousStep = canGoToPreviousStep;
    }

    /**
     * Get the selected group
     * 
     * @return the selected group
     */
    public GroupBean getSelectedGroup() {
        return this.selectedGroup;
    }

    /**
     * Set the selected group
     * 
     * @param selectedGroup The group to select
     */
    public void setSelectedGroup(final GroupBean selectedGroup) {
        this.selectedGroup = selectedGroup;
    }

    /**
     * Get the topology
     * 
     * @return the topology
     */
    public TopologyBean getTopology() {
        return this.topology;
    }

    /**
     * Set the topology
     * 
     * @param topology The topology to set
     */
    public void setTopology(final TopologyBean topology) {
        this.topology = topology;
    }

    /**
     * Get the list of ServerProgressBean that compose the operation
     * 
     * @return the list of ServerProgressBean that compose the operation
     */
    public List<ServerProgressBean> getServerProgressList() {
        return this.serverProgressList;
    }

    /**
     * Set the list of ServerProgressBean that compose the operation
     * 
     * @param serverProgressList the new list of ServerProgressBean that compose
     *        the operation
     */
    public void setServerProgressList(final List<ServerProgressBean> serverProgressList) {
        this.serverProgressList = serverProgressList;
    }

    /**
     * @return the operation type
     */
    public IJadortService.OperationType getType() {
        return this.type;
    }

    /**
     * @param type operation type (migrateApplication or maintain)
     */
    public void setType(final IJadortService.OperationType type) {
        this.type = type;
    }

    /**
     * Get the list of WorkerProgressBean that compose the operation
     * 
     * @return the list of WorkerProgressBean that compose the operation
     */
    public List<WorkerProgressBean> getWorkerProgressList() {
        return this.workerProgressList;
    }

    /**
     * Set the list of WorkerProgressBean that compose the operation
     * 
     * @param serverProgressList the new list of WorkerProgressBean that compose
     *        the operation
     */
    public void setWorkerProgressList(final List<WorkerProgressBean> workerProgressList) {
        this.workerProgressList = workerProgressList;
    }

    /**
     * @return the selected servers list
     */
    public List<ServerBean> getSelectedServers() {
        return this.selectedServers;
    }

    /**
     * @param selectedServers the selected servers list
     */
    public void setSelectedServers(final List<ServerBean> selectedServers) {
        this.selectedServers = selectedServers;
    }

    /**
     * @return aimed worker progress state
     */
    public WorkerProgressState getAimedWorkerProgressState() {
        return this.aimedWorkerProgressState;
    }

    /**
     * @param aimedWorkerProgressState aimed worker progress state
     */
    public void setAimedWorkerProgressState(final WorkerProgressState aimedWorkerProgressState) {
        this.aimedWorkerProgressState = aimedWorkerProgressState;
    }

    /**
     * @return All (currently active + past) ServerProgressBean objects of the
     *         operation
     */
    public Set<ServerProgressBean> getAllServerProgressList() {
        return this.allServerProgressList;
    }

    /**
     * @param allServerProgressList All (currently active + past)
     *        ServerProgressBean objects of the operation
     */
    public void setAllServerProgressList(final Set<ServerProgressBean> allServerProgressList) {
        this.allServerProgressList = allServerProgressList;
    }

    /**
     * @param serverProgressListToAdd The ServerProgressBean objects to add
     */
    public void addAllServerProgressList(final Collection<ServerProgressBean> serverProgressListToAdd) {
        if (this.allServerProgressList == null) {
            this.allServerProgressList = new HashSet<ServerProgressBean>(serverProgressListToAdd);
        } else {
            this.allServerProgressList.addAll(serverProgressListToAdd);
        }
    }

    /**
     * @param serverProgressToAdd The ServerProgressBean to add
     */
    public void addAllServerProgressList(final ServerProgressBean serverProgressToAdd) {
        if (this.allServerProgressList == null) {
            this.allServerProgressList = new HashSet<ServerProgressBean>(1);
        }
        this.allServerProgressList.add(serverProgressToAdd);
    }

    /**
     * @return All (currently active + past) WorkerProgressBean objects of the
     *         operation
     */
    public Set<WorkerProgressBean> getAllWorkerProgressList() {
        return this.allWorkerProgressList;
    }

    /**
     * @param allWorkerProgressList All (currently active + past)
     *        WorkerProgressBean objects of the operation
     */
    public void setAllWorkerProgressList(final Set<WorkerProgressBean> allWorkerProgressList) {
        this.allWorkerProgressList = allWorkerProgressList;
    }

    /**
     * @param workerProgressListToAdd The WorkerProgressBean objects to add
     */
    public void addAllWorkerProgressList(final Collection<WorkerProgressBean> workerProgressListToAdd) {
        if (this.allWorkerProgressList == null) {
            this.allWorkerProgressList = new HashSet<WorkerProgressBean>(workerProgressListToAdd);
        } else {
            this.allWorkerProgressList.addAll(workerProgressListToAdd);
        }
    }

    /**
     * @param workerProgressToAdd The WorkerProgressBean to add
     */
    public void addAllWorkerProgressList(final WorkerProgressBean workerProgressToAdd) {
        if (this.allWorkerProgressList == null) {
            this.allWorkerProgressList = new HashSet<WorkerProgressBean>(1);
        }
        this.allWorkerProgressList.add(workerProgressToAdd);
    }

    /**
     * Get the application to deploy
     * 
     * @return the application to deploy
     */
    public ApplicationBean getApplication() {
        return this.application;
    }

    /**
     * Set the application to deploy
     * 
     * @param application the application to deploy
     */
    public void setApplication(final ApplicationBean application) {
        this.application = application;
    }

    /**
     * Get the VM Image to deploy
     * 
     * @return the VM Image to deploy
     */
    public VMImageBean getVmImage() {
        return this.vmImage;
    }

    /**
     * Set the VM Image to deploy
     * 
     * @param VMImage the VM Image to deploy
     */
    public void setVmImage(final VMImageBean vmImage) {
        this.vmImage = vmImage;
    }

    /**
     * Get the operation creation date
     * 
     * @return the operation creation date
     */
    public Date getDate() {
        return this.date;
    }

    /**
     * Set the operation creation date
     * 
     * @param date the operation creation date
     */
    public void setDate(final Date date) {
        this.date = date;
    }

    /**
     * Get the operation name
     * 
     * @return the operation name
     */
    public String getName() {
        return this.name;
    }

    /**
     * Set the operation name
     * 
     * @param name the operation name
     */
    public void setName(final String name) {
        this.name = name;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(this.getClass().getSimpleName());
        sb.append("[aimedServerProgressState='");
        sb.append(this.aimedServerProgressState);
        sb.append("', currentStep='");
        sb.append(this.currentStep);
        sb.append("']");
        return sb.toString();
    }

    /**
     * {@inheritDoc}
     * 
     * @return true if <code>obj</code> is an {@link OperationStateBean} and the
     *         {@link OperationStateBean#getId()} is the same for both objects,
     *         false otherwise.
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj != null && obj instanceof OperationStateBean) {
            OperationStateBean other = (OperationStateBean) obj;
            if (this.id != null && other.id != null) {
                return this.id.equals(other.id);
            }
        }

        // Else not same type or some parts are null
        return false;
    }

    /**
     * @return Whether the operation can be deleted.
     */
    public boolean getIsDeletable() {
        if (this.currentStep.equals(Step.EXECUTING_MIGRATION_PART1) || this.currentStep.equals(Step.EXECUTING_MIGRATION_PART2)
            || this.currentStep.equals(Step.SELECT_SERVERS) || this.currentStep.equals(Step.SELECT_VM_IMAGE_FOR_SERVER)
            || this.currentStep.equals(Step.EXECUTING_MAINTENANCE_CLUSTER)
            || this.currentStep.equals(Step.EXECUTING_MAINTENANCE_NO_CLUSTER)) {
            return false;
        } else {
            return true;
        }
    }

}
