/*
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.openfire.commands;

import org.dom4j.Element;
import org.jivesoftware.openfire.admin.AdminManager;
import org.xmpp.packet.JID;

import java.util.List;
import java.util.Map;

/**
 * An ad-hoc command is a stateless object responsbile for executing the provided service. Each
 * subclass will only have one instance that will be shared across all users sessions. Therefore,
 * it is important to not keep any information related to executions as permanent data
 * (i.e. as instance or static variables). Each command has a <tt>code</tt> that should be
 * unique within a given JID.<p>
 *
 * Commands may have zero or more stages. Each stage is usually used for gathering information
 * required for the command execution. Users are able to move forward or backward across the
 * different stages. Commands may not be cancelled while they are beig executed. However, users
 * may request the "cancel" action when submiting a stage response indicating that the command
 * execution should be aborted. Thus, releasing any collected information. Commands that require
 * user interaction (i.e. have more than one stage) will have to provide the data forms the user
 * must complete in each stage and the allowed actions the user might perform during each stage
 * (e.g. go to the previous stage or go to the next stage).
 *
 * @author Gaston Dombiak
 */
public abstract class AdHocCommand {

    /**
     * Label of the command. This information may be used to display the command as a button
     * or menu item.
     */
    private String label = getDefaultLabel();

    public AdHocCommand() {
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    /**
     * Returns true if the requester is allowed to execute this command. By default only admins
     * are allowed to execute commands. Subclasses may redefine this method with any specific
     * logic.<p>
     *
     * Note: The bare JID of the requester will be compared with the bare JID of the admins.
     *
     * @param requester the JID of the user requesting to execute this command.
     * @return true if the requester is allowed to execute this command.
     */
    public boolean hasPermission(JID requester) {
        return AdminManager.getInstance().isUserAdmin(requester, false);
    }

    /**
     * Returns the unique identifier for this command for the containing JID. The code will
     * be used as the node in the disco#items or the node when executing the command.
     *
     * @return the unique identifier for this command for the containing JID.
     */
    public abstract String getCode();

    /**
     * Returns the default label used for describing this commmand. This information is usually
     * used when returning commands as disco#items. Admins can later use {@link #setLabel(String)}
     * to set a new label and reset to the default value at any time.
     *
     * @return the default label used for describing this commmand.
     */
    public abstract String getDefaultLabel();

    /**
     * Returns the max number of stages for this command. The number of stages may vary according
     * to the collected data in previous stages. Therefore, a SessionData object is passed as a
     * parameter. When the max number of stages has been reached then the command is ready to
     * be executed.
     *
     * @param data the gathered data through the command stages or <tt>null</tt> if the
     *        command does not have stages or the requester is requesting the execution for the
     *        first time.
     * @return the max number of stages for this command.
     */
    public abstract int getMaxStages(SessionData data);

    /**
     * Executes the command with the specified session data.
     *
     * @param data the gathered data through the command stages or <tt>null</tt> if the
     *        command does not have stages.
     * @param command the command element to be sent to the command requester with a reported
     *        data result or note element with the answer of the execution.
     */
    public abstract void execute(SessionData data, Element command);

    /**
     * Adds to the command element the data form or notes required by the current stage. The
     * current stage is specified in the SessionData. This method will never be invoked for
     * commands that have no stages.
     *
     * @param data the gathered data through the command stages or <tt>null</tt> if the
     *        command does not have stages or the requester is requesting the execution for the
     *        first time.
     * @param command the command element to be sent to the command requester.
     */
    protected abstract void addStageInformation(SessionData data, Element command);

    /**
     * Returns a collection with the allowed actions based on the current stage as defined
     * in the SessionData. Possible actions are: <tt>prev</tt>, <tt>next</tt> and <tt>complete</tt>.
     * This method will never be invoked for commands that have no stages.
     *
     * @param data the gathered data through the command stages or <tt>null</tt> if the
     *        command does not have stages or the requester is requesting the execution for the
     *        first time.
     * @return a collection with the allowed actions based on the current stage as defined
     *         in the SessionData.
     */
    protected abstract List<Action> getActions(SessionData data);

    /**
     * Returns which of the actions available for the current stage is considered the equivalent
     * to "execute". When the requester sends his reply, if no action was defined in the command
     * then the action will be assumed "execute" thus assuming the action returned by this
     * method. This method will never be invoked for commands that have no stages.
     *
     * @param data the gathered data through the command stages or <tt>null</tt> if the
     *        command does not have stages or the requester is requesting the execution for the
     *        first time.
     * @return which of the actions available for the current stage is considered the equivalent
     *         to "execute".
     */
    protected abstract Action getExecuteAction(SessionData data);

    /**
     * Increments the stage number by one and adds to the command element the new data form and
     * new allowed actions that the user might perform.
     *
     * @param data the gathered data through the command stages.
     * @param command the command element to be sent to the command requester.
     */
    public void addNextStageInformation(SessionData data, Element command) {
        // Increment the stage number to the next stage
        data.setStage(data.getStage() + 1);
        // Return the data form of the current stage to the command requester. The
        // requester will need to specify the action to follow (e.g. execute, prev,
        // cancel, etc.) and complete the form is going "forward"
        addStageInformation(data, command);
        // Include the available actions at this stage
        addStageActions(data, command);
    }

    /**
     * Decrements the stage number by one and adds to the command the data form and allowed
     * actions that the user might perform of the previous stage.
     *
     * @param data the gathered data through the command stages.
     * @param command the command element to be sent to the command requester.
     */
    public void addPreviousStageInformation(SessionData data, Element command) {
        // Decrement the stage number to the previous stage
        data.setStage(data.getStage() - 1);
        // Return the data form of the current stage to the command requester. The
        // requester will need to specify the action to follow (e.g. execute, prev,
        // cancel, etc.) and complete the form is going "forward"
        addStageInformation(data, command);
        // Include the available actions at this stage
        addStageActions(data, command);
    }

    /**
     * Adds the allowed actions to follow from the current stage. Possible actions are:
     * <tt>prev</tt>, <tt>next</tt> and <tt>complete</tt>.
     *
     * @param data the gathered data through the command stages or <tt>null</tt> if the
     *        command does not have stages or the requester is requesting the execution for the
     *        first time.
     * @param command the command element to be sent to the command requester.
     */
    protected void addStageActions(SessionData data, Element command) {
        // Add allowed actions to the response
        Element actions = command.addElement("actions");
        List<Action> validActions = getActions(data);
        for (AdHocCommand.Action action : validActions) {
            actions.addElement(action.name());
        }
        Action executeAction = getExecuteAction(data);
        // Add default execute action to the response
        actions.addAttribute("execute", executeAction.name());

        // Store the allowed actions that the user can follow from this stage
        data.setAllowedActions(validActions);
        // Store the default execute action to follow if the user does not specify an
        // action in his command
        data.setExecuteAction(executeAction);
    }

    protected String get(Map<String, List<String>> data, String key, int value) {
        List<String> list = data.get(key);
        if (list == null) {
            return null;
        } else {
            return list.get(value);
        }
    }


    public enum Status {

        /**
         * The command is being executed.
         */
        executing,

        /**
         * The command has completed. The command session has ended.
         */
        completed,

        /**
         * The command has been canceled. The command session has ended.
         */
        canceled
    }

    public enum Action {

        /**
         * The command should be executed or continue to be executed. This is the default value.
         */
        execute,

        /**
         * The command should be canceled.
         */
        cancel,

        /**
         * The command should be digress to the previous stage of execution.
         */
        prev,

        /**
         * The command should progress to the next stage of execution.
         */
        next,

        /**
         * The command should be completed (if possible).
         */
        complete
    }
}
