/*
 * Copyright 2011 Vaadin Ltd.
 * 
 * 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 com.vaadin.server;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.MimeResponse;
import javax.portlet.PortletConfig;
import javax.portlet.PortletMode;
import javax.portlet.PortletModeException;
import javax.portlet.PortletResponse;
import javax.portlet.PortletSession;
import javax.portlet.PortletURL;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;
import javax.portlet.StateAwareResponse;
import javax.servlet.http.HttpSessionBindingListener;
import javax.xml.namespace.QName;

import com.vaadin.ui.UI;
import com.vaadin.util.CurrentInstance;

/**
 * TODO Write documentation, fix JavaDoc tags.
 * 
 * This is automatically registered as a {@link HttpSessionBindingListener} when
 * {@link PortletSession#setAttribute()} is called with the context as value.
 * 
 * @author peholmst
 * 
 * @deprecated might be refactored or removed before 7.0.0
 */
@Deprecated
@SuppressWarnings("serial")
public class VaadinPortletSession extends VaadinSession {

    private final Set<PortletListener> portletListeners = new LinkedHashSet<PortletListener>();

    private final Map<String, QName> eventActionDestinationMap = new HashMap<String, QName>();
    private final Map<String, Serializable> eventActionValueMap = new HashMap<String, Serializable>();

    private final Map<String, String> sharedParameterActionNameMap = new HashMap<String, String>();
    private final Map<String, String> sharedParameterActionValueMap = new HashMap<String, String>();

    public PortletSession getPortletSession() {
        WrappedSession wrappedSession = getSession();
        PortletSession session = ((WrappedPortletSession) wrappedSession)
                .getPortletSession();
        return session;
    }

    private PortletResponse getCurrentResponse() {
        VaadinPortletResponse currentResponse = (VaadinPortletResponse) CurrentInstance
                .get(VaadinResponse.class);

        if (currentResponse != null) {
            return currentResponse.getPortletResponse();
        } else {
            return null;
        }
    }

    public PortletConfig getPortletConfig() {
        VaadinPortletResponse response = (VaadinPortletResponse) CurrentInstance
                .get(VaadinResponse.class);
        return response.getService().getPortlet().getPortletConfig();
    }

    public void addPortletListener(PortletListener listener) {
        portletListeners.add(listener);
    }

    public void removePortletListener(PortletListener listener) {
        portletListeners.remove(listener);
    }

    public void firePortletRenderRequest(UI uI, RenderRequest request,
            RenderResponse response) {
        for (PortletListener l : new ArrayList<PortletListener>(
                portletListeners)) {
            l.handleRenderRequest(request, new RestrictedRenderResponse(
                    response), uI);
        }
    }

    public void firePortletActionRequest(UI uI, ActionRequest request,
            ActionResponse response) {
        String key = request.getParameter(ActionRequest.ACTION_NAME);
        if (eventActionDestinationMap.containsKey(key)) {
            // this action request is only to send queued portlet events
            response.setEvent(eventActionDestinationMap.get(key),
                    eventActionValueMap.get(key));
            // cleanup
            eventActionDestinationMap.remove(key);
            eventActionValueMap.remove(key);
        } else if (sharedParameterActionNameMap.containsKey(key)) {
            // this action request is only to set shared render parameters
            response.setRenderParameter(sharedParameterActionNameMap.get(key),
                    sharedParameterActionValueMap.get(key));
            // cleanup
            sharedParameterActionNameMap.remove(key);
            sharedParameterActionValueMap.remove(key);
        } else {
            // normal action request, notify listeners
            for (PortletListener l : new ArrayList<PortletListener>(
                    portletListeners)) {
                l.handleActionRequest(request, response, uI);
            }
        }
    }

    public void firePortletEventRequest(UI uI, EventRequest request,
            EventResponse response) {
        for (PortletListener l : new ArrayList<PortletListener>(
                portletListeners)) {
            l.handleEventRequest(request, response, uI);
        }
    }

    public void firePortletResourceRequest(UI uI, ResourceRequest request,
            ResourceResponse response) {
        for (PortletListener l : new ArrayList<PortletListener>(
                portletListeners)) {
            l.handleResourceRequest(request, response, uI);
        }
    }

    public interface PortletListener extends Serializable {

        public void handleRenderRequest(RenderRequest request,
                RenderResponse response, UI uI);

        public void handleActionRequest(ActionRequest request,
                ActionResponse response, UI uI);

        public void handleEventRequest(EventRequest request,
                EventResponse response, UI uI);

        public void handleResourceRequest(ResourceRequest request,
                ResourceResponse response, UI uI);
    }

    /**
     * Creates a new action URL.
     * 
     * @param action
     * @return action URL or null if called outside a MimeRequest (outside a
     *         UIDL request or similar)
     */
    public PortletURL generateActionURL(String action) {
        PortletURL url = null;
        PortletResponse response = getCurrentResponse();
        if (response instanceof MimeResponse) {
            url = ((MimeResponse) response).createActionURL();
            url.setParameter("javax.portlet.action", action);
        } else {
            return null;
        }
        return url;
    }

    /**
     * Sends a portlet event to the indicated destination.
     * 
     * Internally, an action may be created and opened, as an event cannot be
     * sent directly from all types of requests.
     * 
     * The event destinations and values need to be kept in the context until
     * sent. Any memory leaks if the action fails are limited to the session.
     * 
     * Event names for events sent and received by a portlet need to be declared
     * in portlet.xml .
     * 
     * @param uI
     *            a window in which a temporary action URL can be opened if
     *            necessary
     * @param name
     *            event name
     * @param value
     *            event value object that is Serializable and, if appropriate,
     *            has a valid JAXB annotation
     */
    public void sendPortletEvent(UI uI, QName name, Serializable value)
            throws IllegalStateException {
        PortletResponse response = getCurrentResponse();
        if (response instanceof MimeResponse) {
            String actionKey = "" + System.currentTimeMillis();
            while (eventActionDestinationMap.containsKey(actionKey)) {
                actionKey = actionKey + ".";
            }
            PortletURL actionUrl = generateActionURL(actionKey);
            if (actionUrl != null) {
                eventActionDestinationMap.put(actionKey, name);
                eventActionValueMap.put(actionKey, value);
                uI.getPage().setLocation(actionUrl.toString());
            } else {
                // this should never happen as we already know the response is a
                // MimeResponse
                throw new IllegalStateException(
                        "Portlet events can only be sent from a portlet request");
            }
        } else if (response instanceof StateAwareResponse) {
            ((StateAwareResponse) response).setEvent(name, value);
        } else {
            throw new IllegalStateException(
                    "Portlet events can only be sent from a portlet request");
        }
    }

    /**
     * Sets a shared portlet parameter.
     * 
     * Internally, an action may be created and opened, as shared parameters
     * cannot be set directly from all types of requests.
     * 
     * The parameters and values need to be kept in the context until sent. Any
     * memory leaks if the action fails are limited to the session.
     * 
     * Shared parameters set or read by a portlet need to be declared in
     * portlet.xml .
     * 
     * @param uI
     *            a window in which a temporary action URL can be opened if
     *            necessary
     * @param name
     *            parameter identifier
     * @param value
     *            parameter value
     */
    public void setSharedRenderParameter(UI uI, String name, String value)
            throws IllegalStateException {
        PortletResponse response = getCurrentResponse();
        if (response instanceof MimeResponse) {
            String actionKey = "" + System.currentTimeMillis();
            while (sharedParameterActionNameMap.containsKey(actionKey)) {
                actionKey = actionKey + ".";
            }
            PortletURL actionUrl = generateActionURL(actionKey);
            if (actionUrl != null) {
                sharedParameterActionNameMap.put(actionKey, name);
                sharedParameterActionValueMap.put(actionKey, value);
                uI.getPage().setLocation(actionUrl.toString());
            } else {
                // this should never happen as we already know the response is a
                // MimeResponse
                throw new IllegalStateException(
                        "Shared parameters can only be set from a portlet request");
            }
        } else if (response instanceof StateAwareResponse) {
            ((StateAwareResponse) response).setRenderParameter(name, value);
        } else {
            throw new IllegalStateException(
                    "Shared parameters can only be set from a portlet request");
        }
    }

    /**
     * Sets the portlet mode. This may trigger a new render request.
     * 
     * Portlet modes used by a portlet need to be declared in portlet.xml .
     * 
     * @param uI
     *            a window in which the render URL can be opened if necessary
     * @param portletMode
     *            the portlet mode to switch to
     * @throws PortletModeException
     *             if the portlet mode is not allowed for some reason
     *             (configuration, permissions etc.)
     */
    public void setPortletMode(UI uI, PortletMode portletMode)
            throws IllegalStateException, PortletModeException {
        PortletResponse response = getCurrentResponse();
        if (response instanceof MimeResponse) {
            PortletURL url = ((MimeResponse) response).createRenderURL();
            url.setPortletMode(portletMode);
            throw new RuntimeException("UI.open has not yet been implemented");
            // UI.open(new ExternalResource(url.toString()));
        } else if (response instanceof StateAwareResponse) {
            ((StateAwareResponse) response).setPortletMode(portletMode);
        } else {
            throw new IllegalStateException(
                    "Portlet mode can only be changed from a portlet request");
        }
    }
}
