package nu.validator.checker;


import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Static utilities for working with (X)HTML5 attribute values.
 * 
 * @version $Id$
 * @author hsivonen
 */
public final class AttributeUtil {

    /**
     * An empty string array instance.
     */
    private final static String[] EMPTY_STRING_ARRAY = {};

    /**
     * The pattern for extracting an integer by skipping white space and ignoring 
     * trailing garbage.
     */
    private static Pattern INTEGER_PATTERN = Pattern.compile("^[ \t\n\r]*(-?[0-9]+)");

    /**
     * Private constructor to prevent instantiation.
     */
    private AttributeUtil() {
        super();
    }

    /**
     * Returns the integer represented by <code>attrVal</code> or 
     * <code>Integer.MIN_VALUE</code> on error.
     * 
     * @param attrVal a string representing an integer attribute 
     * value (can be <code>null</code>)
     * @return the integer represented by <code>attrVal</code> or 
     * <code>Integer.MIN_VALUE</code> on error
     */
    public static int parseInteger(String attrVal) {
        if (attrVal == null) {
            return Integer.MIN_VALUE;
        }
        Matcher m = INTEGER_PATTERN.matcher(attrVal);
        if (!m.matches()) {
            return Integer.MIN_VALUE;
        }
        try {
            return Integer.parseInt(m.group(1));
        } catch (NumberFormatException e) {
            return Integer.MIN_VALUE;
        }
    }

    /**
     * Returns the non-negative integer represented by 
     * <code>attrVal</code> or -1 on error.
     * 
     * @param attrVal a string representing a non-negative 
     * integer attribute value (can be <code>null</code>)
     * @return the integer represented by <code>attrVal</code> or 
     * -1 on error
     */
    public static int parseNonNegativeInteger(String attrVal) {
        int rv = parseInteger(attrVal);
        if (rv < 0) {
            return -1;
        } else {
            return rv;
        }
    }

    /**
     * Returns the positive integer represented by 
     * <code>attrVal</code> or -1 on error.
     * 
     * @param attrVal a string representing a positive 
     * integer attribute value (can be <code>null</code>)
     * @return the integer represented by <code>attrVal</code> or 
     * -1 on error
     */
    public static int parsePositiveInteger(String attrVal) {
        int rv = parseInteger(attrVal);
        if (rv < 1) {
            return -1;
        } else {
            return rv;
        }
    }

    /**
     * Splits the argument on white space.
     * 
     * @param value the attribute value
     * @return a string array with zero or more strings none of which 
     * is the empty string and none of which contains white space characters.
     */
    public static String[] split(String value) {
        if (value == null || "".equals(value)) {
            return EMPTY_STRING_ARRAY;
        }
        int len = value.length();
        List<String> list = new LinkedList<>();
        boolean collectingSpace = true;
        int start = 0;
        for (int i = 0; i < len; i++) {
            char c = value.charAt(i);
            if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
                if (!collectingSpace) {
                    list.add(value.substring(start, i));
                    collectingSpace = true;
                }
            } else {
                if (collectingSpace) {
                    start = i;
                    collectingSpace = false;
                }
            }
        }
        if (!collectingSpace) {
            list.add(value.substring(start, len));
        }
        return list.toArray(EMPTY_STRING_ARRAY);
    }

    /**
     * Checks if <code>string</code> matches <code>lowerCaseLiteral</code> when
     * ASCII-lowercased.
     * @param lowerCaseLiteral a lower-case literal
     * @param string potentially mixed-case string or <code>null</code>
     * @return <code>true</code> if <code>string</code> matches 
     * <code>lowerCaseLiteral</code> when ASCII-lowercased
     */
    public static boolean lowerCaseLiteralEqualsIgnoreAsciiCaseString(String lowerCaseLiteral,
            String string) {
        if (string == null) {
            return false;
        }
        if (lowerCaseLiteral.length() != string.length()) {
            return false;
        }
        for (int i = 0; i < lowerCaseLiteral.length(); i++) {
            char c0 = lowerCaseLiteral.charAt(i);
            char c1 = string.charAt(i);
            if (c1 >= 'A' && c1 <= 'Z') {
                c1 += 0x20;
            }
            if (c0 != c1) {
                return false;
            }
        }
        return true;
    }
}
