/*
 * Copyright (C) 2011 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.url;

import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.exoplatform.commons.utils.I18N;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.portal.application.PortalRequestHandler;
import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.web.ControllerContext;
import org.exoplatform.web.WebAppController;
import org.exoplatform.web.controller.QualifiedName;
import org.exoplatform.web.controller.router.URIWriter;
import org.exoplatform.web.url.PortalURL;
import org.exoplatform.web.url.URLContext;
import org.exoplatform.web.security.csrf.CSRFTokenUtil;
import org.gatein.common.io.UndeclaredIOException;

/**
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 */
public class PortalURLContext implements URLContext {

    private static final char REPLACEMENT_CHAR = '\ufffd';

    private static volatile String  globalSite;

    private static volatile String  defaultSite;

    /** . */
    private final ControllerContext controllerContext;

    /** . */
    private final SiteKey siteKey;

    /** . */
    private URIWriter writer;

    /** . */
    private StringBuilder buffer;

    public PortalURLContext(ControllerContext controllerContext, SiteKey siteKey) {
        if (controllerContext == null) {
            throw new NullPointerException("No null controller context");
        }

        //
        this.controllerContext = controllerContext;
        this.siteKey = siteKey;
        this.writer = null;
        this.buffer = null;
    }

    public <R, U extends PortalURL<R, U>> String render(U url) {
        try {
            return _render(url);
        } catch (IOException e) {
            throw new UndeclaredIOException(e);
        }
    }

    private <R, U extends PortalURL<R, U>> String _render(U url) throws IOException {
        if (url.getResource() == null) {
            throw new IllegalStateException("No resource set on portal URL");
        }

        //
        if (writer == null) {
            writer = new URIWriter(buffer = new StringBuilder());
        } else {
            buffer.setLength(0);
            writer.reset(buffer);
        }

        //
        HttpServletRequest req = controllerContext.getRequest();
        if (url.getSchemeUse()) {
            buffer.append(req.getScheme());
            buffer.append("://");
        }
        if (url.getAuthorityUse()) {
            buffer.append(req.getServerName());
            int port = req.getServerPort();
            if (port != 80) {
                buffer.append(':').append(port);
            }
        }

        //
        writer.setMimeType(url.getMimeType());

        //
        String confirm = url.getConfirm();
        boolean hasConfirm = confirm != null && confirm.length() > 0;

        //
        boolean ajax = url.getAjax() != null && url.getAjax();
        if (ajax) {
            writer.append("javascript:");
            if (hasConfirm) {
                writer.append("if(confirm('");
                writer.append(confirm.replaceAll("'", "\\\\'"));
                writer.append("'))");
            }
            writer.append("ajaxGet('");
        } else {
            if (hasConfirm) {
                writer.append("javascript:");
                writer.append("if(confirm('");
                writer.append(confirm.replaceAll("'", "\\\\'"));
                writer.append("'))");
                writer.append("window.location=\'");
            }
        }

        //
        Map<QualifiedName, String> parameters = new HashMap<QualifiedName, String>();
        parameters.put(WebAppController.HANDLER_PARAM, "portal");
        parameters.put(PortalRequestHandler.REQUEST_SITE_TYPE, siteKey.getTypeName());
        parameters.put(PortalRequestHandler.REQUEST_SITE_NAME, getSiteName(siteKey.getName()));

        //
        String lang = "";
        Locale locale = url.getLocale();
        if (locale != null && locale.getLanguage().length() > 0) {
            lang = I18N.toTagIdentifier(locale);
        }
        parameters.put(PortalRequestHandler.LANG, lang);

        //
        for (QualifiedName parameterName : url.getParameterNames()) {
            String parameterValue = url.getParameterValue(parameterName);
            if (parameterValue != null) {
                // XSS issue PLF-8071
                if (PortalRequestHandler.REQUEST_SITE_NAME.equals(parameterName)) {
                  parameterValue = getSiteName(parameterValue);
                } else {
                  parameterValue = parameterValue.replaceAll("&", "&amp;");
                }
                parameters.put(parameterName, parameterValue);
            }
        }

        int untrustedStart = buffer.length();

        // Render url via controller
        controllerContext.renderURL(parameters, writer);

        checkUntrustedData(untrustedStart);

        // Now append generic query parameters
        Map<String, String[]> queryParameters = url.getQueryParameters();
        if (queryParameters != null) {
            for (Map.Entry<String, String[]> entry : queryParameters.entrySet()) {
                for (String value : entry.getValue()) {
                    writer.appendQueryParameter(entry.getKey(), value);
                }
            }
        }

        // CSRF token
        if (url.isCSRFCheck()) {
            String token;
            try {
                token = CSRFTokenUtil.getToken(req);
                if(token != null){
                    writer.appendQueryParameter(CSRFTokenUtil.CSRF_TOKEN, token);
                }

            } catch (Exception e) {
                throw new IllegalStateException("Can't add csrf token to url", e);
            }
        }

        //
        if (ajax) {
            writer.appendQueryParameter("ajaxRequest", "true");
            writer.append("')");
        } else {
            if (hasConfirm) {
                writer.append("\'");
            }
        }

        //
        return buffer.toString();
    }

    private void checkUntrustedData(int start) {
        int len = buffer.length();
        for (int i = start; i < len; i++) {
            if (buffer.charAt(i) == '\'') {
                buffer.setCharAt(i, REPLACEMENT_CHAR);
            }
        }
    }

    public static String getSiteName(String siteName) {
      if (StringUtils.equals(siteName, getGlobalSite())) {
        return getDefaultSite();
      }
      return siteName;
    }

    public static String getGlobalSite() {
      if (globalSite == null) {
        UserPortalConfigService portalConfigService = ExoContainerContext.getService(UserPortalConfigService.class);
        if (portalConfigService != null) {
          globalSite = portalConfigService.getGlobalPortal();
        }
      }
      return globalSite;
    }

    public static String getDefaultSite() {
      if (defaultSite == null) {
        UserPortalConfigService portalConfigService = ExoContainerContext.getService(UserPortalConfigService.class);
        if (portalConfigService != null) {
          defaultSite = portalConfigService.getDefaultPortal();
        }
      }
      return defaultSite;
    }
}
