/**
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.portal.webui.application;

import java.io.Serializable;
import java.util.Map;

import javax.portlet.PortletMode;
import javax.portlet.WindowState;

import org.exoplatform.Constants;
import org.exoplatform.commons.utils.Text;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.portal.application.PortalRequestContext;
import org.exoplatform.portal.config.NoSuchDataException;
import org.exoplatform.portal.portlet.PortletExceptionHandleService;
import org.exoplatform.portal.webui.portal.UIPortal;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.portal.webui.workspace.UIPortalApplication;
import org.exoplatform.portal.webui.workspace.UIPortalApplication.EditMode;
import org.exoplatform.resolver.ApplicationResourceResolver;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.portletcontainer.PortletContainerException;
import org.exoplatform.web.application.ApplicationMessage;
import org.exoplatform.webui.application.WebuiApplication;
import org.exoplatform.webui.application.WebuiRequestContext;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.core.lifecycle.Lifecycle;
import org.exoplatform.webui.core.lifecycle.WebuiBindingContext;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.exception.MessageException;

import org.gatein.pc.api.NoSuchPortletException;
import org.gatein.pc.api.invocation.RenderInvocation;
import org.gatein.pc.api.invocation.response.PortletInvocationResponse;

/**
 * Created by The eXo Platform SAS May 8, 2006
 */
public class UIPortletLifecycle<S, C extends Serializable, I> extends Lifecycle<UIPortlet<S, C>> {

    protected static Log log = ExoLogger.getLogger("portal:UIPortletLifecycle");

    /**
     * This processAction method associated with the portlet UI component does the following work:
     *
     * 1) If the current request is one that target the portal than an event targeting a Portal level ActionListener is sent.
     * This case happen when the incoming request contains the parameter PortalRequestContext.UI_COMPONENT_ACTION
     * (portal:action). When the event is broadcasted the methods is over 2) In other cases, the request targets the portlet
     * either to a) change the portlet mode b) change the window state c) make a processAction() or render() call to the portlet
     * container (Portlet API methods here) In those 3 cases, dedicated events are created and broadcasted and the portlet is
     * added in the list of components to update within the AJAX call
     */
    public void processAction(UIPortlet<S, C> uicomponent, WebuiRequestContext context) throws Exception {
        try {
            // The PortletMode and WindowState can change during a portlet invocation, so we need
            // to be able to compare the results before and after invoking the portlet to know if
            // we need to broadcast a change event or not.
            PortletMode currentPortletMode = uicomponent.getCurrentPortletMode();
            WindowState currentWindowState = uicomponent.getCurrentWindowState();

            String action = context.getRequestParameter(PortalRequestContext.UI_COMPONENT_ACTION);
            if (action != null) {
                Event<UIComponent> event = uicomponent.createEvent(action, Event.Phase.PROCESS, context);
                if (event != null)
                    event.broadcast();
                return;
            }

            String portletMode = context.getRequestParameter(org.exoplatform.portal.Constants.PORTAL_PORTLET_MODE);
            if (portletMode != null) {
                Event<UIComponent> event = uicomponent.createEvent("ChangePortletMode", Event.Phase.PROCESS, context);
                if (event != null)
                    event.broadcast();
            }

            String windowState = context.getRequestParameter(org.exoplatform.portal.Constants.PORTAL_WINDOW_STATE);
            if (windowState != null) {
                Event<UIComponent> event = uicomponent.createEvent("ChangeWindowState", Event.Phase.PROCESS, context);
                if (event != null)
                    event.broadcast();
            }

            /*
             * Check the type of the incoming request, can be either an ActionURL or a RenderURL one
             *
             * In case of a RenderURL, the parameter state map must be invalidated and this is done in the associated
             * ActionListener
             *
             * If no action type is specified we assume the default, which is to render
             */
            String portletActionType = context.getRequestParameter(Constants.TYPE_PARAMETER);
            if (portletActionType != null) {
                if (portletActionType.equals(Constants.PORTAL_PROCESS_ACTION)) {
                    Event<UIComponent> event = uicomponent.createEvent("ProcessAction", Event.Phase.PROCESS, context);
                    if (event != null)
                        event.broadcast();
                } else if (portletActionType.equals(Constants.PORTAL_SERVE_RESOURCE)) {
                    Event<UIComponent> event = uicomponent.createEvent("ServeResource", Event.Phase.PROCESS, context);
                    if (event != null)
                        event.broadcast();
                }
            } else {
                Event<UIComponent> event = uicomponent.createEvent("Render", Event.Phase.PROCESS, context);
                if (event != null)
                    event.broadcast();
            }

            // These two checks needs to go after the ProcessAction, ServeResource or Render broadcast events.
            // The mode or state can change during the invocation and we need to be able to broadcast the change
            // event if this occurs.
            if (currentPortletMode != null && !currentPortletMode.equals(uicomponent.getCurrentPortletMode())) {
                context.setAttribute(UIPortletActionListener.CHANGE_PORTLET_MODE_EVENT, uicomponent.getCurrentPortletMode()
                        .toString());
                Event<UIComponent> event = uicomponent.createEvent("ChangePortletMode", Event.Phase.PROCESS, context);
                if (event != null)
                    event.broadcast();
                context.setAttribute(UIPortletActionListener.CHANGE_PORTLET_MODE_EVENT, null);
            }
            if (currentWindowState != null && !currentWindowState.equals(uicomponent.getCurrentWindowState())) {
                context.setAttribute(UIPortletActionListener.CHANGE_WINDOW_STATE_EVENT, uicomponent.getCurrentWindowState()
                        .toString());
                Event<UIComponent> event = uicomponent.createEvent("ChangeWindowState", Event.Phase.PROCESS, context);
                if (event != null)
                    event.broadcast();
                context.setAttribute(UIPortletActionListener.CHANGE_WINDOW_STATE_EVENT, null);
            }

            context.addUIComponentToUpdateByAjax(uicomponent);
        } catch (NoSuchDataException e) {
            UIPortalApplication uiApp = Util.getUIPortalApplication();
            uiApp.refreshCachedUI();
            ApplicationMessage msg = new ApplicationMessage("UIPortlet.message.staleData", null,
                    ApplicationMessage.WARNING);
            uiApp.addMessage(msg);
        } catch (Exception e) {
            String message = e.getLocalizedMessage();
            log.error("Error processing the action: " + message, e);
            Object[] args = { message };
            context.addUIComponentToUpdateByAjax(uicomponent);
            throw new MessageException(new ApplicationMessage("UIPortletLifecycle.msg.process-error", args,
                    ApplicationMessage.ERROR));
        }

    }

    /**
     * This methods of the Lifecycle writes into the output writer the content of the portlet
     *
     * 1) Create a RenderInput object and fill it with all the Request information 2) Call the portletContainer.render() method
     * of the Portlet Container to get the HTML generated fragment 3) Then if the current request is an AJAX one, just write in
     * the buffer the content returned by the portlet container 4) If not AJAX, then merge the content with the UIPortlet.gtmpl
     */
    public void processRender(UIPortlet<S, C> uicomponent, WebuiRequestContext context) throws Exception {
        PortalRequestContext prcontext = (PortalRequestContext) context;
        ExoContainer container = prcontext.getApplication().getApplicationServiceContainer();

        //
        Text markup = null;

        try {
            Map<String, String[]> paramMap = prcontext.getRequest().getParameterMap();
            if (paramMap.containsKey("removePP")) {
                UIPortal uiPortal = Util.getUIPortal();
                for (String publicParamName : paramMap.get("removePP")) {
                    uiPortal.getPublicParameters().remove(publicParamName);
                }
            }

            RenderInvocation renderInvocation = uicomponent.create(RenderInvocation.class, prcontext);

            String appStatus = uicomponent.getProperties().get("appStatus");
            if ("Window".equals(uicomponent.getPortletStyle()) && !("SHOW".equals(appStatus) || "HIDE".equals(appStatus))) {
                markup = Text.create("<span></span>");
            } else {
                // Check mode of portal, portlet and permission for viewable
                if ((Util.getUIPortalApplication().getEditMode() != EditMode.BLOCK || uicomponent.getCurrentPortletMode()
                        .equals(PortletMode.EDIT)) && uicomponent.hasAccessPermission()) {
                    PortletInvocationResponse response = uicomponent.invoke(renderInvocation);
                    markup = uicomponent.generateRenderMarkup(response, prcontext);
                } else {
                    uicomponent.setConfiguredTitle(null);
                }
            }
        } catch (Exception e) {
            PortletContainerException pcException = new PortletContainerException(e);
            PortletExceptionHandleService portletExceptionService = uicomponent
                    .getApplicationComponent(PortletExceptionHandleService.class);
            if (portletExceptionService != null) {
                portletExceptionService.handle(pcException);
            }

            if (e instanceof NoSuchDataException) {
                UIPortalApplication uiApp = Util.getUIPortalApplication();
                uiApp.refreshCachedUI();
                markup = Text.create(context.getApplicationResourceBundle().getString("UIPortlet.message.staleData"));
            } else if (e instanceof NoSuchPortletException) {
                markup = Text.create("");
            } else {
                // Log the error
                log.error("Portlet render threw an exception", pcException);

                //
                markup = Text.create(context.getApplicationResourceBundle().getString("UIPortlet.message.RuntimeError"));
            }
        }

        //
        if (context.useAjax() && !prcontext.getFullRender()) {
            if (markup != null) {
                markup.writeTo(prcontext.getWriter());
            }
        } else {
            WebuiApplication app = (WebuiApplication) prcontext.getApplication();
            ApplicationResourceResolver resolver = app.getResourceResolver();
            WebuiBindingContext bcontext = new WebuiBindingContext(resolver, context.getWriter(), uicomponent, prcontext);
            bcontext.put(UIComponent.UICOMPONENT, uicomponent);
            bcontext.put("portletContent", markup);
            try {
                renderTemplate(uicomponent.getTemplate(), bcontext);
            } catch (Throwable e) {
                log.error(e.getMessage(), e);
            }
        }
    }
}
