/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.enterprise.gui.legacy.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;

import org.rhq.enterprise.gui.legacy.AttrConstants;
import org.rhq.enterprise.gui.legacy.WebUser;
import org.rhq.enterprise.gui.legacy.beans.ReturnPath;

public class SessionUtils {

    private static final Log log = LogFactory.getLog(SessionUtils.class);

    // Limit the size of the return stack so that it
    // doesn't grow to infinite proportions.
    private static final int RETURN_STACK_MAX_SIZE = 128;

    /**
     * Retrieve the cached <code>WebUser</code> representing the user.
     *
     * @param session the http session
     * @return null if the session or its attribute 'webUser' is invalid, the user otherwise
     */
    public static WebUser getWebUser(HttpSession session) {
        if (session == null) {
            return null;
        }

        Object attr = session.getAttribute(AttrConstants.WEBUSER_SES_ATTR);
        if (attr == null) {
            return null;
        }

        WebUser user = (WebUser) attr;
        log.debug("Loading WebUser");
        /*
        Subject subject = user.getSubject();
        Integer sessionId = subject.getSessionId();
        subject = LookupUtil.getSubjectManager().loadUserConfiguration(subject.getId());
        subject.setSessionId(sessionId); // put the transient data back into our new subject
        user = new WebUser(subject); // reset
        */
        return user;
    }

    public static void setWebUser(HttpSession session, WebUser user) {
        if (user == null) {
            session.removeAttribute(AttrConstants.WEBUSER_SES_ATTR);
        } else {
            session.setAttribute(AttrConstants.WEBUSER_SES_ATTR, user);
        }
    }

    /**
     * Retrieve the array of pending ids stored under the named session attribute.
     *
     * @param session the http session
     * @param attr    the name of the session attribute
     */
    public static Integer[] getList(HttpSession session, String attr) {
        Object list = session.getAttribute(attr);
        Integer[] pendingIds = null;
        try {
            pendingIds = (Integer[]) list;
        } catch (ClassCastException e) {
            List pendingIdsList = (List) list;
            pendingIds = (Integer[]) pendingIdsList.toArray(new Integer[pendingIdsList.size()]);
        }

        if (pendingIds == null) {
            pendingIds = new Integer[0];
        }

        return pendingIds;
    }

    /**
     * Retrieve the <code>List</code> of pending ids stored under the named session attribute.
     *
     * @param session the http session
     * @param attr    the name of the session attribute
     */
    public static List getListAsList(HttpSession session, String attr) {
        Object list = session.getAttribute(attr);
        List pendingIds = null;
        try {
            pendingIds = (List) list;
        } catch (ClassCastException e) {
            Integer[] pendingIdsArray = (Integer[]) list;
            pendingIds = Arrays.asList(pendingIdsArray);
        }

        if (pendingIds == null) {
            pendingIds = new ArrayList();
        }

        return pendingIds;
    }

    /**
     * Retrieve the <code>List</code> of pending ids stored under the named session attribute.
     *
     * @param session the http session
     * @param attr    the name of the session attribute
     */
    @SuppressWarnings("unchecked")
    public static List<String> getListAsListStr(HttpSession session, String attr) {
        Object list = session.getAttribute(attr);
        List<String> pendingIds = null;

        try {
            pendingIds = (List<String>) list;
        } catch (ClassCastException e) {
            String[] pendingIdsArray = (String[]) list;
            pendingIds = Arrays.asList(pendingIdsArray);
        }

        if (pendingIds == null) {
            pendingIds = new ArrayList<String>();
        }

        return pendingIds;
    }

    /**
     * Merge the given ids into the array of pending ids stored under the named session attribute.
     *
     * @param session the http session
     * @param attr    the name of the session attribute
     * @param ids     the ids to be added
     */
    public static void addToList(HttpSession session, String attr, List ids) {
        /* copy the selected pending ids over to the available
         * ids. those that were copied will not be included in the final list of available users generated by theform
         * prepare action. */
        List pendingIds = (List) session.getAttribute(attr);
        if (pendingIds == null) {
            pendingIds = new ArrayList();
        }

        session.setAttribute(attr, mergeIds(pendingIds, ids));
    }

    public static void addToList(HttpSession session, String attr, Integer[] ids) {
        addToList(session, attr, Arrays.asList(ids));
    }

    /**
     * Merge the given ids into the array of pending ids stored under the named session attribute.
     *
     * @param session the http session
     * @param attr    the name of the session attribute
     * @param ids     the ids to be added
     */
    public static void addToListStr(HttpSession session, String attr, List ids) {
        /* copy the selected pending ids over to the available
         * ids. those that were copied will not be included in the final list of available users generated by theform
         * prepare action. */
        List pendingIds = (List) session.getAttribute(attr);
        if (pendingIds == null) {
            pendingIds = new ArrayList();
        }

        session.setAttribute(attr, mergeStrIds(pendingIds, ids));
    }

    public static void addToList(HttpSession session, String attr, String[] ids) {
        addToListStr(session, attr, Arrays.asList(ids));
    }

    /**
     * Remove the given ids from the array of pending ids stored under the named session attribute.
     *
     * @param session the http session
     * @param attr    the name of the session attribute
     * @param ids     the ids to be removed
     */
    public static void removeFromList(HttpSession session, String attr, List ids) {
        /* remove the selected pending ids from the current list
         *of pending ids. */
        List pendingIds = (List) session.getAttribute(attr);
        if (pendingIds == null) {
            pendingIds = new ArrayList();
        }

        session.setAttribute(attr, grepIds(pendingIds, ids));
    }

    public static void removeFromList(HttpSession session, String attr, Integer[] ids) {
        removeFromList(session, attr, Arrays.asList(ids));
    }

    /**
     * Remove the given ids from the array of pending ids stored under the named session attribute.
     *
     * @param session the http session
     * @param attr    the name of the session attribute
     * @param ids     the ids to be removed
     */
    public static void removeFromListStr(HttpSession session, String attr, List ids) {
        /* remove the selected pending ids from the current list
         *of pending ids. */
        List pendingIds = (List) session.getAttribute(attr);
        if (pendingIds == null) {
            pendingIds = new ArrayList();
        }

        session.setAttribute(attr, grepStrIds(pendingIds, ids));
    }

    public static void removeFromList(HttpSession session, String attr, String[] ids) {
        removeFromListStr(session, attr, Arrays.asList(ids));
    }

    /**
     * Remove the array of pending ids stored under the named session attribute.
     *
     * @param session the http session
     * @param attr    the name of the session attribute
     */
    public static void removeList(HttpSession session, String attr) {
        session.removeAttribute(attr);
    }

    /**
     * Retrieve the "return path" that locates the point at which a subflow was included into a primary workflow.
     *
     * @param session the http session
     */
    public static String getReturnPath(HttpSession session) {
        LinkedList stack = (LinkedList) session.getAttribute(AttrConstants.RETURN_LOC_SES_ATTR);

        if ((stack == null) || stack.isEmpty()) {
            return null;
        }

        ReturnPath returnPath = (ReturnPath) stack.getFirst();

        return returnPath.getPath();
    }

    /**
     * Set the "returnPath", the possible point of origin for a workflow.
     *
     * @param session the http session
     * @param path    the return path
     */
    public static void setReturnPath(HttpSession session, String path) {
        setReturnPath(session, path, Boolean.FALSE);
    }

    /**
     * Set the "returnPath," the possible point of origin for a worklow
     *
     * @param session The http session.
     * @param path    The return path url represented as a String.
     * @param ignore
     */
    public static void setReturnPath(HttpSession session, String path, Boolean ignore) {
        LinkedList stack = (LinkedList) session.getAttribute(AttrConstants.RETURN_LOC_SES_ATTR);

        ReturnPath returnPath = new ReturnPath();

        if (stack == null) {
            stack = new LinkedList();
        }

        returnPath.setPath(path);
        returnPath.setIgnore(ignore);

        stack.addFirst(returnPath);

        // don't let it grow too large.
        if (stack.size() > RETURN_STACK_MAX_SIZE) {
            stack.removeLast();
        }

        session.setAttribute(AttrConstants.RETURN_LOC_SES_ATTR, stack);
    }

    /**
     * clear out the return path stack.
     */
    public static void resetReturnPath(HttpSession session) {
        LinkedList stack = new LinkedList();

        session.setAttribute(AttrConstants.RETURN_LOC_SES_ATTR, stack);
    }

    /**
     * Unset the "return path" that locates the point at which a subflow was included into a primary workflow.
     *
     * @param session the http session
     */
    public static void unsetReturnPath(HttpSession session) {
        LinkedList retstack = (LinkedList) session.getAttribute(AttrConstants.RETURN_LOC_SES_ATTR);

        if ((retstack != null) && (retstack.size() >= 1)) {
            retstack.removeFirst();
        }
    }

    /**
     * Retrieve wether the "return path" should be paid attention to for new. should default to false, which means that
     * it is not ignored.
     *
     * @param  session the http session
     *
     * @return whether or not to ignore the return path
     */
    public static Boolean getReturnPathIgnoredForOk(HttpSession session) {
        LinkedList stack = (LinkedList) session.getAttribute(AttrConstants.RETURN_LOC_SES_ATTR);

        if ((stack == null) || stack.isEmpty()) {
            return Boolean.FALSE;
        }

        ReturnPath returnPath = (ReturnPath) stack.getFirst();

        if (returnPath == null) {
            return Boolean.FALSE;
        }

        return returnPath.getIgnore();
    }

    /**
     * Unset the "return path" that locates the point at which a subflow was included into a primary workflow.
     *
     * @param session the http session
     */
    public static void unsetReturnPathIgnoredForOk(HttpSession session) {
        LinkedList stack = (LinkedList) session.getAttribute(AttrConstants.RETURN_LOC_SES_ATTR);
        ReturnPath returnPath = (ReturnPath) stack.getFirst();
        returnPath.setIgnore(Boolean.TRUE);
    }

    /**
     * Remove any old workflows
     *
     * @param session
     * @param workflowName
     */
    public static void clearWorkflow(HttpSession session, String workflowName) {
        HashMap workflows = (HashMap) session.getAttribute(AttrConstants.WORKFLOW_SES_ATTR);

        if (workflows != null) {
            workflows.remove(workflowName);
        }
    }

    /**
     * Takes the current returnPath and pushes it onto the workflow's stack.
     *
     * @param session      The HttpSesion to get and save the workflow to/from
     * @param mapping      Use the input attribute from this mapping if a returnPath is not defined in the current
     *                     session.
     * @param workflowName The name of the workflow scope to save the input under.
     */
    public static void pushWorkflow(HttpSession session, ActionMapping mapping, String workflowName) {
        HashMap workflows = (HashMap) session.getAttribute(AttrConstants.WORKFLOW_SES_ATTR);
        if (workflows == null) {
            workflows = new HashMap();
        }

        String returnPath = getReturnPath(session);
        if (returnPath == null) {
            returnPath = mapping.getInput();
        }

        LinkedList urlStack = (LinkedList) workflows.get(workflowName);

        // whacking getIsFirst.. for some reason, even if i set the
        // property to false in struts-config.xml, it's still getting
        // set back to true. i can't have this, so i'm whacking the
        // check altogether. doesn't seem to harm anything. -bcm 7/9/03
        // if (urlStack == null || mapping.getIsFirst().booleanValue()) {
        if (urlStack == null) {
            urlStack = new LinkedList();
        }

        urlStack.addLast(returnPath);
        workflows.put(workflowName, urlStack);

        session.setAttribute(AttrConstants.WORKFLOW_SES_ATTR, workflows);
    }

    /**
     * Takes the current returnPath and pops it off of the workflow's stack.
     *
     * @param session      The HttpSesion to get and save the workflow to/from
     * @param workflowName The name of the workflow scope to save the input under.
     * @return return path or null if it can't be retrieved
     */
    public static String popWorkflow(HttpSession session, String workflowName) {
        HashMap workflows = (HashMap) session.getAttribute(AttrConstants.WORKFLOW_SES_ATTR);
        if (workflows == null) {
            return null;
        }

        LinkedList urlStack = (LinkedList) workflows.get(workflowName);
        if ((urlStack == null) || urlStack.isEmpty()) {
            return null;
        }

        String returnUrl = (String) urlStack.removeLast();
        workflows.put(workflowName, urlStack);
        session.setAttribute(AttrConstants.WORKFLOW_SES_ATTR, workflows);

        return returnUrl;
    }

    /**
     * Returns the size of a workflow's stack.
     *
     * @param session      The HttpSesion to get and save the workflow to/from
     * @param workflowName The name of the workflow scope to save the input under.
     */
    public static int countWorkflow(HttpSession session, String workflowName) {
        HashMap workflows = (HashMap) session.getAttribute(AttrConstants.WORKFLOW_SES_ATTR);
        if (workflows == null) {
            return 0;
        }

        LinkedList urlStack = (LinkedList) workflows.get(workflowName);
        if ((urlStack == null) || urlStack.isEmpty()) {
            return 0;
        }

        return urlStack.size();
    }

    private static List grepIds(List all, List matches) {
        if ((all == null) || (matches == null)) {
            return new ArrayList();
        }

        // build an index of matches
        HashSet index = new HashSet();
        Iterator mi = matches.iterator();
        while (mi.hasNext()) {
            index.add(mi.next());
        }

        // find everything that's not a match
        ArrayList ids = new ArrayList();
        Iterator ai = all.iterator();
        while (ai.hasNext()) {
            Integer id = (Integer) ai.next();
            if (!index.contains(id)) {
                ids.add(id);
            }
        }

        return ids;
    }

    /**
     * searches for strings which does not match
     */
    private static List grepStrIds(List pending, List newItems) {
        if ((pending == null) || (newItems == null)) {
            return new ArrayList();
        }

        // build an index of matches
        HashSet index = new HashSet();
        Iterator mi = newItems.iterator();
        while (mi.hasNext()) {
            index.add(mi.next());
        }

        // find everything that's not a match
        ArrayList ids = new ArrayList();
        Iterator ai = pending.iterator();
        while (ai.hasNext()) {
            String id = (String) ai.next();
            if (!index.contains(id)) {
                ids.add(id);
            }
        }

        return ids;
    }

    private static List mergeIds(List orig, List added) {
        if ((orig == null) && (added == null)) {
            return new ArrayList();
        }

        if (orig == null) {
            return added;
        }

        if (added == null) {
            return orig;
        }

        // build an index of orig and add originals
        ArrayList ids = new ArrayList();
        HashSet index = new HashSet();
        Iterator oi = orig.iterator();
        while (oi.hasNext()) {
            Object next = oi.next();
            index.add(next);
            ids.add(next);
        }

        // add anything that's not in the index
        Iterator ai = added.iterator();
        while (ai.hasNext()) {
            Integer id = (Integer) ai.next();
            if (!index.contains(id)) {
                ids.add(id);
            }
        }

        return ids;
    }

    /**
     * same as the mergeIds except using strings IDs of form: .
     */
    private static List mergeStrIds(List orig, List added) {
        if ((orig == null) && (added == null)) {
            return new ArrayList();
        }

        if (orig == null) {
            return added;
        }

        if (added == null) {
            return orig;
        }

        // build an index of orig and add originals
        ArrayList ids = new ArrayList();
        HashSet index = new HashSet();
        Iterator oi = orig.iterator();
        while (oi.hasNext()) {
            Object next = oi.next();
            index.add(next);
            ids.add(next);
        }

        // add anything that's not in the index
        Iterator ai = added.iterator();
        while (ai.hasNext()) {
            String id = (String) ai.next();
            if (!index.contains(id)) {
                ids.add(id);
            }
        }

        return ids;
    }

    /**
     * Set a confirmation message upon completion of a user action.
     *
     * @param session The servlet session object
     * @param key     the message resource key
     */
    public static void setConfirmation(HttpSession session, String key) {
        ActionMessage msg = new ActionMessage(key);
        ActionMessages msgs = new ActionMessages();
        msgs.add(ActionMessages.GLOBAL_MESSAGE, msg);
        if (session != null) {
            session.setAttribute(Globals.MESSAGE_KEY, msgs);
        }
    }

    /**
     * Set a confirmation message with a replacement value upon completion of a user action.
     *
     * @param session The servlet session object
     * @param key     the message resource key
     * @param value0  the replacement value
     */
    public static void setConfirmation(HttpSession session, String key, Object value0) {
        ActionMessage msg = new ActionMessage(key, value0);
        ActionMessages msgs = new ActionMessages();
        msgs.add(ActionMessages.GLOBAL_MESSAGE, msg);
        if (session != null) {
            session.setAttribute(Globals.MESSAGE_KEY, msgs);
        }
    }

    public static void unsetConfirmation(HttpSession session, String key) {
        session.removeAttribute(Globals.MESSAGE_KEY);
    }

    /**
     * Set an error message when a user action fails with a user-level error.
     *
     * @param session The servlet session object
     * @param key     the message resource key
     */
    public static void setError(HttpSession session, String key) {
        setError(session, key, ActionMessages.GLOBAL_MESSAGE);
    }

    /**
     * Set an error message when a user action fails with a user-level error.
     *
     * @param session  The servlet session object
     * @param key      the message resource key
     * @param property the form property for which the error occurred
     */
    public static void setError(HttpSession session, String key, String property) {
        ActionMessage err = new ActionMessage(key);
        ActionErrors errs = new ActionErrors();
        errs.add(property, err);
        if (session != null) {
            session.setAttribute(Globals.ERROR_KEY, errs);
        }
    }

    /**
     * Set an error message with a replacement value when a user action fails with a user-level error.
     *
     * @param session The servlet session object
     * @param key     the message resource key
     * @param value0  the replacement value
     */
    public static void setError(HttpSession session, String key, Object value0) {
        setError(session, key, value0, ActionMessages.GLOBAL_MESSAGE);
    }

    /**
     * Set an error message with a replacement value when a user action fails with a user-level error.
     *
     * @param session  The servlet session object
     * @param key      the message resource key
     * @param value0   the replacement value
     * @param property the form property for which the error occurred
     */
    public static void setError(HttpSession session, String key, Object value0, String property) {
        ActionMessage err = new ActionMessage(key, value0);
        ActionMessages errs = new ActionErrors();
        errs.add(property, err);
        if (session != null) {
            session.setAttribute(Globals.ERROR_KEY, errs);
        }
    }

    public static void unsetError(HttpSession session) {
        if (session != null) {
            session.removeAttribute(Globals.ERROR_KEY);
        }
    }

    /**
     * Move the attribute from the session to request scope.
     *
     * @param request The request to move the attribute to.
     * @param key     Key indicating the attribute to move scope.
     */
    public static void moveAttribute(HttpServletRequest request, String key) {
        HttpSession session = request.getSession(false /* dont' create new one */);
        if (session != null) {
            request.setAttribute(key, session.getAttribute(key));
            session.removeAttribute(key);
        }
    }
}