/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.struts2.components;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.StrutsException;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.portlet.context.PortletActionContext;
import org.apache.struts2.portlet.util.PortletUrlHelper;
import org.apache.struts2.portlet.util.PortletUrlHelperJSR286;
import org.apache.struts2.url.QueryStringParser;
import org.apache.struts2.views.util.UrlHelper;

import javax.portlet.PortletMode;
import java.io.IOException;
import java.io.Writer;

/**
 * Implementation of the {@link UrlRenderer} interface that renders URLs for portlet environments.
 *
 * @see UrlRenderer
 */
public class PortletUrlRenderer implements UrlRenderer {

    /**
     * The servlet renderer used when not executing in a portlet context.
     */
    private ServletUrlRenderer servletRenderer = null;
    private PortletUrlHelper portletUrlHelper = null;

    public PortletUrlRenderer() {
        this.servletRenderer = new ServletUrlRenderer();

        if (PortletActionContext.isJSR268Supported()) {
            this.portletUrlHelper = new PortletUrlHelperJSR286();
        } else {
            this.portletUrlHelper = new PortletUrlHelper();
        }
    }

    @Inject
    public void setActionMapper(ActionMapper actionMapper) {
        servletRenderer.setActionMapper(actionMapper);
    }

    @Inject
    public void setUrlHelper(UrlHelper urlHelper) {
        servletRenderer.setUrlHelper(urlHelper);
    }

    @Inject
    public void setQueryStringParser(QueryStringParser queryStringParser) {
        this.servletRenderer.setQueryStringParser(queryStringParser);
    }

    /**
     * {@inheritDoc}
     */
    public void renderUrl(Writer writer, UrlProvider urlComponent) {
        if (PortletActionContext.getPortletContext() == null) {
            servletRenderer.renderUrl(writer, urlComponent);
            return;
        }
        String result;
        if (isPortletModeChange(urlComponent, PortletActionContext.getRequest().getPortletMode())
            && StringUtils.isEmpty(urlComponent.getNamespace())) {
            String mode = urlComponent.getPortletMode();
            PortletMode portletMode = new PortletMode(mode);
            String action = urlComponent.getAction();
            if (StringUtils.isEmpty(action)) {
                action = PortletActionContext.getModeActionMap().get(portletMode).getName();
            }
            String modeNamespace = PortletActionContext.getModeNamespaceMap().get(portletMode);
            result = portletUrlHelper.buildUrl(action, modeNamespace, urlComponent.getMethod(),
                urlComponent.getParameters(), urlComponent.getPortletUrlType(), mode, urlComponent.getWindowState());

        } else {
            String namespace = urlComponent.determineNamespace(urlComponent.getNamespace(), urlComponent.getStack(), urlComponent.getHttpServletRequest());
            urlComponent.setNamespace(namespace);
            if (onlyActionSpecified(urlComponent)) {
                if (StringUtils.isNotEmpty(urlComponent.getAction())) {
                    String action = urlComponent.findString(urlComponent.getAction());
                    result = portletUrlHelper.buildUrl(action, urlComponent.getNamespace(), urlComponent.getMethod(),
                        urlComponent.getParameters(), urlComponent.getPortletUrlType(), urlComponent.getPortletMode(), urlComponent.getWindowState());
                } else {
                    result = portletUrlHelper.buildUrl(urlComponent.getAction(), urlComponent.getNamespace(), urlComponent.getMethod(),
                        urlComponent.getParameters(), urlComponent.getPortletUrlType(), urlComponent.getPortletMode(), urlComponent.getWindowState());
                }
            } else if (onlyValueSpecified(urlComponent)) {
                result = portletUrlHelper.buildResourceUrl(urlComponent.getValue(), urlComponent.getParameters());
            } else {
                result = createDefaultUrl(urlComponent);
            }
        }
        String anchor = urlComponent.getAnchor();
        if (StringUtils.isNotEmpty(anchor)) {
            result += '#' + urlComponent.findString(anchor);
        }

        String var = urlComponent.getVar();

        if (var != null) {
            urlComponent.putInContext(result);

            // add to the request and page scopes as well
            urlComponent.getHttpServletRequest().setAttribute(var, result);
        } else {
            try {
                writer.write(result);
            } catch (IOException e) {
                throw new StrutsException("IOError: " + e.getMessage(), e);
            }
        }
    }

    boolean isPortletModeChange(UrlProvider urlComponent, PortletMode currentMode) {
        if (StringUtils.isNotEmpty(urlComponent.getPortletMode())) {
            PortletMode newPortletMode = new PortletMode(urlComponent.getPortletMode());
            return !(newPortletMode.equals(currentMode));
        }
        return false;
    }

    private String createDefaultUrl(UrlProvider urlComponent) {
        ActionInvocation ai = urlComponent.getStack().getActionContext().getActionInvocation();
        String action = ai.getProxy().getActionName();
        return portletUrlHelper.buildUrl(action, urlComponent.getNamespace(), urlComponent.getMethod(), urlComponent.getParameters(),
            urlComponent.getPortletUrlType(), urlComponent.getPortletMode(), urlComponent.getWindowState());
    }

    private boolean onlyValueSpecified(UrlProvider urlComponent) {
        return urlComponent.getValue() != null && urlComponent.getAction() == null;
    }

    private boolean onlyActionSpecified(UrlProvider urlComponent) {
        return urlComponent.getValue() == null && urlComponent.getAction() != null;
    }

    /**
     * {@inheritDoc}
     */
    public void renderFormUrl(Form formComponent) {
        if (PortletActionContext.getPortletContext() == null) {
            servletRenderer.renderFormUrl(formComponent);
            return;
        }
        String namespace = formComponent.determineNamespace(formComponent.namespace, formComponent.getStack(),
            formComponent.request);
        String action;
        if (formComponent.action != null) {
            action = formComponent.findString(formComponent.action);
        } else {
            ActionInvocation ai = formComponent.getStack().getActionContext().getActionInvocation();
            action = ai.getProxy().getActionName();
        }

        String type = "action";
        if (StringUtils.isNotEmpty(formComponent.method)) {
            if ("GET".equalsIgnoreCase(formComponent.method.trim())) {
                type = "render";
            }
        }
        if (action != null) {
            String result = portletUrlHelper.buildUrl(action, namespace, null,
                formComponent.getParameters(), type, formComponent.portletMode, formComponent.windowState);
            formComponent.addParameter("action", result);


            // name/id: cut out anything between / and . should be the id and
            // name
            String id = formComponent.getId();
            if (id == null) {
                int slash = action.lastIndexOf('/');
                int dot = action.indexOf('.', slash);
                if (dot != -1) {
                    id = action.substring(slash + 1, dot);
                } else {
                    id = action.substring(slash + 1);
                }
                String escapedId = formComponent.escape(id);
                formComponent.addParameter("id", escapedId);
                formComponent.addParameter("escapedId", escapedId);
            }
        }
    }

    public void beforeRenderUrl(UrlProvider urlComponent) {
        if (PortletActionContext.getPortletContext() == null) {
            servletRenderer.beforeRenderUrl(urlComponent);
        }
    }

}
