package com.atlassian.stash.internal.scm.git;

import org.apache.commons.codec.net.URLCodec;

import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.util.BitSet;

//TODO: Remove after moving to the bitbucket-util module
public class UriEncodingUtils {
    /** Performs the same encoding as the Javascript encodeURIComponents function */
    static final BitSet ALPHANUMERIC_CHARS = new BitSet(256);
    static {
        for (int i = '0'; i <= '9'; i++) {
            ALPHANUMERIC_CHARS.set(i);
        }
        for (int i = 'a'; i <= 'z'; i++) {
            ALPHANUMERIC_CHARS.set(i);
        }
        for (int i = 'A'; i <= 'Z'; i++) {
            ALPHANUMERIC_CHARS.set(i);
        }
    }

    /** Performs the same encoding as the Javascript encodeURIComponents function */
    static final BitSet ENCODE_URI_COMPONENTS_WHITELIST = new BitSet(256);
    static {
        ENCODE_URI_COMPONENTS_WHITELIST.or(ALPHANUMERIC_CHARS);
        ENCODE_URI_COMPONENTS_WHITELIST.set('-');
        ENCODE_URI_COMPONENTS_WHITELIST.set('_');
        ENCODE_URI_COMPONENTS_WHITELIST.set('.');
        ENCODE_URI_COMPONENTS_WHITELIST.set('!');
        ENCODE_URI_COMPONENTS_WHITELIST.set('~');
        ENCODE_URI_COMPONENTS_WHITELIST.set('*');
        ENCODE_URI_COMPONENTS_WHITELIST.set('\'');
        ENCODE_URI_COMPONENTS_WHITELIST.set('(');
        ENCODE_URI_COMPONENTS_WHITELIST.set(')');
    }

    static final BitSet ENCODE_URI_COMPONENTS_SHELL_WHITELIST = new BitSet(256);
    static {
        ENCODE_URI_COMPONENTS_SHELL_WHITELIST.or(ALPHANUMERIC_CHARS);
        ENCODE_URI_COMPONENTS_SHELL_WHITELIST.set('-');
        ENCODE_URI_COMPONENTS_SHELL_WHITELIST.set('_');
        ENCODE_URI_COMPONENTS_SHELL_WHITELIST.set('.');
        ENCODE_URI_COMPONENTS_SHELL_WHITELIST.set('~');
    }

    /** Allowed characters for user info according to http://tools.ietf.org/html/rfc3986 */
    static final BitSet ENCODE_USER_INFO_WHITELISTS = new BitSet(256);
    static {
        ENCODE_USER_INFO_WHITELISTS.or(ALPHANUMERIC_CHARS);
        ENCODE_USER_INFO_WHITELISTS.set('-');
        ENCODE_USER_INFO_WHITELISTS.set('_');
        ENCODE_USER_INFO_WHITELISTS.set('.');
        ENCODE_USER_INFO_WHITELISTS.set('!');
        ENCODE_USER_INFO_WHITELISTS.set('~');
        ENCODE_USER_INFO_WHITELISTS.set('*');
        ENCODE_USER_INFO_WHITELISTS.set('\'');
        ENCODE_USER_INFO_WHITELISTS.set('(');
        ENCODE_USER_INFO_WHITELISTS.set(')');
        ENCODE_USER_INFO_WHITELISTS.set('&');
        ENCODE_USER_INFO_WHITELISTS.set('$');
        ENCODE_USER_INFO_WHITELISTS.set('+');
        ENCODE_USER_INFO_WHITELISTS.set(',');
        ENCODE_USER_INFO_WHITELISTS.set(';');
        ENCODE_USER_INFO_WHITELISTS.set('=');
        ENCODE_USER_INFO_WHITELISTS.set(':');
    }

    private UriEncodingUtils() {
        throw new UnsupportedOperationException(getClass().getName() +
            " is a utility class and should not be instantiated");
    }

    private static String encode(String unescaped, BitSet allowed, String charset) throws URISyntaxException {
        byte[] rawData;
        try {
            rawData = unescaped.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            rawData = unescaped.getBytes();
        }
        try {
            byte[] encoded = URLCodec.encodeUrl(allowed, rawData);
            return new String(encoded, 0, encoded.length, "US-ASCII");
        } catch (UnsupportedEncodingException e) {
            throw new URISyntaxException(unescaped, "Unable to encode URI");
        }
    }

    /**
     * URL encodes all characters except the following: alphanumeric, '-', '_', '.', '~'.
     *
     * Useful when outputting a URL that is intended to be copied and pasted into a shell because the returned string
     * will only expose a minimum of non-alphanumeric characters and all will be within the ISO-8859-1 range.
     *
     * @param component the URI component to encode
     * @param encoding the charset encoding to use
     * @return the encoded component
     */
    public static String encodeComponentForShell(String component, String encoding) throws URISyntaxException {
        return encode(component, ENCODE_URI_COMPONENTS_SHELL_WHITELIST, encoding);
    }

    /**
     * URL encodes all characters except the following: alphanumeric, '-', '_', '.', '!', '~', '*', "'", '(' and ')'.
     *
     * An implementation of the Javascript function encodeURIComponent so that clients may use decodeURIComponent
     * on the output of this function.
     *
     * @param component the URI component to encode
     * @param encoding the charset encoding to use
     * @return the encoded component
     */
    public static String encodeURIComponent(String component, String encoding) throws URISyntaxException {
        return encode(component, ENCODE_URI_COMPONENTS_WHITELIST, encoding);
    }

    /**
     * Encodes the given URI user info with the given encoding. All characters except the following are URL encoded:
     * alphanumeric, '-', '_', '.', '!', '~', '*', "'", '(', ')', '&', '$', '+', ',', ';', '=' and ':'.
     *
     * @param userInfo the user info to encode
     * @param encoding hte charset encoding to use
     * @return the encoded user info
     * @throws URISyntaxException
     */
    public static String encodeUserInfo(String userInfo, String encoding) throws URISyntaxException {
        return encode(userInfo, ENCODE_USER_INFO_WHITELISTS, encoding);
    }
}
