/*
 This file is part of Airsonic.

 Airsonic 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, either version 3 of the License, or
 (at your option) any later version.

 Airsonic 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 Airsonic.  If not, see <http://www.gnu.org/licenses/>.

 Copyright 2016 (C) Airsonic Authors
 Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
 */
package org.airsonic.player.taglib;

import org.airsonic.player.filter.ParameterDecodingFilter;
import org.airsonic.player.util.StringUtil;
import org.apache.commons.lang.CharUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.taglibs.standard.tag.common.core.UrlSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

/**
 * Creates a URL with optional query parameters. Similar to 'c:url', but
 * you may specify which character encoding to use for the URL query
 * parameters. If no encoding is specified, the following steps are performed:
 * <ul>
 * <li>Parameter values are encoded as the hexadecimal representation of the UTF-8 bytes of the original string.</li>
 * <li>Parameter names are prepended with the suffix "Utf8Hex"</li>
 * <li>Note: Nothing is done with the parameter name or value if the value only contains ASCII alphanumeric characters.</li>
 * </ul>
 * <p/>
 * (The problem with c:url is that is uses the same encoding as the http response,
 * but most(?) servlet container assumes that ISO-8859-1 is used.)
 *
 * @author Sindre Mehus
 */
public class UrlTag extends BodyTagSupport {

    private String DEFAULT_ENCODING = "Utf8Hex";
    private static final Logger LOG = LoggerFactory.getLogger(UrlTag.class);

    private String var;
    private String value;
    private String encoding = DEFAULT_ENCODING;
    private List<Pair<String, String>> parameters = new ArrayList<>();

    public int doStartTag() {
        parameters.clear();
        return EVAL_BODY_BUFFERED;
    }

    public int doEndTag() throws JspException {

        // Rewrite and encode the url.
        String result = formatUrl();

        // Store or print the output
        if (var != null)
            pageContext.setAttribute(var, result, PageContext.PAGE_SCOPE);
        else {
            try {
                pageContext.getOut().print(result);
            } catch (IOException x) {
                throw new JspTagException(x);
            }
        }
        return EVAL_PAGE;
    }

    private String formatUrl() throws JspException {
        String baseUrl = UrlSupport.resolveUrl(value, null, pageContext);

        StringBuilder result = new StringBuilder();
        result.append(baseUrl);
        if (!parameters.isEmpty()) {
            result.append('?');

            for (int i = 0; i < parameters.size(); i++) {
                Pair<String, String> parameter = parameters.get(i);
                try {
                    result.append(parameter.getLeft());
                    if (isUtf8Hex() && !isAsciiAlphaNumeric(parameter.getRight())) {
                        result.append(ParameterDecodingFilter.PARAM_SUFFIX);
                    }

                    result.append('=');
                    if (parameter.getRight() != null) {
                        result.append(encode(parameter.getRight()));
                    }
                    if (i < parameters.size() - 1) {
                        result.append("&");
                    }

                } catch (UnsupportedEncodingException x) {
                    throw new JspTagException(x);
                }
            }
        }
        return result.toString();
    }

    private String encode(String s) throws UnsupportedEncodingException {
        if (isUtf8Hex()) {
            if (isAsciiAlphaNumeric(s)) {
                return s;
            }

            try {
                return StringUtil.utf8HexEncode(s);
            } catch (Exception x) {
                LOG.error("Failed to utf8hex-encode the string '" + s + "'.", x);
                return s;
            }
        }

        return URLEncoder.encode(s, encoding);
    }

    private boolean isUtf8Hex() {
        return DEFAULT_ENCODING.equals(encoding);
    }

    static private boolean isAsciiAlphaNumeric(String s) {
        if (s == null) {
            return true;
        }

        for (int i = 0; i < s.length(); i++) {
            if (!CharUtils.isAsciiAlphanumeric(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public void release() {
        var = null;
        value = null;
        encoding = DEFAULT_ENCODING;
        parameters.clear();
        super.release();
    }

    public void addParameter(String name, String value) {
        parameters.add(Pair.of(name, value));
    }

    public String getVar() {
        return var;
    }

    public void setVar(String var) {
        this.var = var;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getEncoding() {
        return encoding;
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }
}
