/**
 * Copyright (c) André Bargull
 * Alle Rechte vorbehalten / All Rights Reserved.  Use is subject to license terms.
 *
 * <https://github.com/anba/es6draft>
 */
package com.github.anba.es6draft.runtime.internal;

import static com.github.anba.es6draft.parser.Characters.isWhitespaceOrLineTerminator;

import java.util.Arrays;

/**
 * Operations on strings
 */
public final class Strings {
    private Strings() {
    }

    /**
     * Returns the string representation of a single code point.
     * 
     * @param codePoint
     *            the code point
     * @return the result string
     */
    public static String fromCodePoint(int codePoint) {
        if (Character.isBmpCodePoint(codePoint)) {
            return String.valueOf((char) codePoint);
        }
        return String.valueOf(Character.toChars(codePoint));
    }

    /**
     * Removes leading whitespace.
     * 
     * @param s
     *            the string
     * @return the string with leading whitespace removed
     */
    public static String trimLeft(String s) {
        for (int start = 0, end = s.length(); start < end; ++start) {
            char c = s.charAt(start);
            if (!isWhitespaceOrLineTerminator(c)) {
                return s.substring(start, end);
            }
        }
        return "";
    }

    /**
     * Removes trailing whitespace.
     * 
     * @param s
     *            the string
     * @return the string with trailing whitespace removed
     */
    public static String trimRight(String s) {
        for (int end = s.length(); end > 0; --end) {
            char c = s.charAt(end - 1);
            if (!isWhitespaceOrLineTerminator(c)) {
                return s.substring(0, end);
            }
        }
        return "";
    }

    /**
     * Removes leading and trailing whitespace.
     * 
     * @param s
     *            the string
     * @return the string with leading and trailing whitespace removed
     */
    public static String trim(String s) {
        int start = 0, end = s.length();
        for (; start < end; ++start) {
            char c = s.charAt(start);
            if (!isWhitespaceOrLineTerminator(c)) {
                break;
            }
        }
        for (; end > start; --end) {
            char c = s.charAt(end - 1);
            if (!isWhitespaceOrLineTerminator(c)) {
                break;
            }
        }
        assert start <= end;
        if (start == end) {
            return "";
        }
        return s.substring(start, end);
    }

    /**
     * Concatenates the input strings, adjacent strings are separated by the given separator char.
     * 
     * @param separator
     *            the separator character
     * @param strings
     *            the input strings
     * @return the concatenated string
     */
    public static String concatWith(char separator, String... strings) {
        if (strings.length == 0) {
            return "";
        } else if (strings.length == 1) {
            return strings[0];
        } else {
            StringBuilder sb = new StringBuilder();
            for (String string : strings) {
                sb.append(string).append(separator);
            }
            sb.setLength(sb.length() - 1);
            return sb.toString();
        }
    }

    /**
     * Concatenates two strings.
     * 
     * @param s1
     *            the first string
     * @param s2
     *            the second string
     * @return the concatenated string
     */
    public static String concat(String s1, String s2) {
        char[] ca = new char[s1.length() + s2.length()];
        s1.getChars(0, s1.length(), ca, 0);
        s2.getChars(0, s2.length(), ca, s1.length());
        return new String(ca);
    }

    /**
     * Repeat {@code c} {@code n}-times.
     * 
     * @param c
     *            the character to repeat
     * @param n
     *            the repetition count
     * @return the result string
     */
    public static String repeat(char c, int n) {
        assert n >= 0;
        if (n == 0) {
            return "";
        }
        if (n == 1) {
            return String.valueOf(c);
        }
        char[] value = new char[n];
        Arrays.fill(value, c);
        return new String(value);
    }

    private static final char[] HEXDIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
            'f' };

    /**
     * Quotes the input string.
     * 
     * @param s
     *            the string
     * @return the quoted string
     */
    public static String quote(String s) {
        for (int i = 0, length = s.length(); i < length; ++i) {
            char c = s.charAt(i);
            switch (c) {
            case '"':
            case '\\':
            case '\b':
            case '\f':
            case '\n':
            case '\r':
            case '\t':
                break;
            default:
                if (c < ' ' || c > 0xff) {
                    break;
                }
                continue;
            }
            return quote(s, i);
        }
        return new StringBuilder(s.length() + 2).append('"').append(s).append('"').toString();
    }

    private static String quote(String s, int start) {
        StringBuilder sb = new StringBuilder(s.length() + 5);
        sb.append('"').append(s.substring(0, start));
        for (int i = start, length = s.length(); i < length; ++i) {
            char c = s.charAt(i);
            switch (c) {
            case '"':
            case '\\':
                sb.append('\\').append(c);
                break;
            case '\b':
                sb.append('\\').append('b');
                break;
            case '\f':
                sb.append('\\').append('f');
                break;
            case '\n':
                sb.append('\\').append('n');
                break;
            case '\r':
                sb.append('\\').append('r');
                break;
            case '\t':
                sb.append('\\').append('t');
                break;
            default:
                if (c < ' ' || c > 0xff) {
                    /* @formatter:off */
                    sb.append('\\').append('u')
                      .append(HEXDIGITS[(c >> 12) & 0xf])
                      .append(HEXDIGITS[(c >> 8) & 0xf])
                      .append(HEXDIGITS[(c >> 4) & 0xf])
                      .append(HEXDIGITS[(c >> 0) & 0xf]);
                    /* @formatter:on */
                } else {
                    sb.append(c);
                }
            }
        }
        sb.append('"');
        return sb.toString();
    }

    /**
     * If {@code s} is an argument index less than {@code 2}<span><sup>{@code 31}</sup></span> {@code -1} (
     * {@code 0x7FFFFFF}), its integer value is returned. Otherwise {@code -1} is returned.
     * 
     * @param s
     *            the property key
     * @return the integer index or {@code -1}
     */
    public static int toArgumentIndex(String s) {
        // "2147483647".length == 10
        return (int) toIndex(s, 10, 0x7FFF_FFFFL);
    }

    /**
     * If {@code s} is a string index less than {@code 2}<span><sup>{@code 31}</sup></span> {@code -1} (
     * {@code 0x7FFFFFF}), its integer value is returned. Otherwise {@code -1} is returned.
     * 
     * @param s
     *            the property key
     * @return the integer index or {@code -1}
     */
    public static int toStringIndex(String s) {
        // "2147483647".length == 10
        return (int) toIndex(s, 10, 0x7FFF_FFFFL);
    }

    /**
     * If {@code s} is an array integer indexed property key less than {@code 2}<span><sup>{@code 32} </sup></span>
     * {@code -1} ({@code 0xFFFFFFF}), its integer value is returned. Otherwise {@code -1} is returned.
     * 
     * @param s
     *            the property key
     * @return the array index or {@code -1}
     */
    public static long toArrayIndex(String s) {
        // "4294967295".length == 10
        return toIndex(s, 10, 0xFFFF_FFFFL);
    }

    /**
     * If {@code s} is an integer indexed property key less than {@code 2}<span><sup>{@code 53} </sup></span>{@code -1},
     * its integer value is returned. Otherwise {@code -1} is returned.
     * 
     * @param propertyKey
     *            the property key
     * @return the integer index or {@code -1}
     */
    static long toIndex(String propertyKey) {
        // "9007199254740991".length == 16
        return toIndex(propertyKey, 16, 0x1F_FFFF_FFFF_FFFFL);
    }

    private static long toIndex(String s, int maxLength, long limit) {
        int length = s.length();
        if (length == 0 || length > maxLength) {
            // empty string or definitely greater than maximum value
            return -1;
        }
        char c = s.charAt(0);
        if (c < '0' || c > '9') {
            // not an integer number
            return -1;
        }
        if (length == 1) {
            return c - '0';
        }
        if (c == '0') {
            // leading zeroes not allowed in indexed strings
            return -1;
        }
        return toIndex(s, (long) (c - '0'), limit);
    }

    private static long toIndex(String s, long index, long limit) {
        int length = s.length();
        for (int i = 1; i < length; ++i) {
            char c = s.charAt(i);
            if (!(c >= '0' && c <= '9')) {
                return -1;
            }
            index = index * 10 + (c - '0');
        }
        return index < limit ? index : -1;
    }
}
