/**
 * 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.parser;

import static com.github.anba.es6draft.parser.Characters.digit;
import static com.github.anba.es6draft.parser.Characters.hexDigit;

import java.math.BigInteger;

import org.mozilla.javascript.StringToNumber;

/**
 * Utility class for parsing number literals
 */
public final class NumberParser {
    private NumberParser() {
    }

    /**
     * Parse a decimal integer literal.
     * 
     * @param cbuf
     *            the characters to parse
     * @param length
     *            the length of the characters
     * @return the parsed integer
     */
    static double parseInteger(char[] cbuf, int length) {
        if (length < 10) {
            // integer [0, 9999_99999]
            int num = 0;
            for (int i = 0; i < length; ++i) {
                num = (num * 10) + digit(cbuf[i]);
            }
            return num;
        } else if (length < 19) {
            // integer [0, 999_99999_99999_99999]
            long num = 0;
            for (int i = 0; i < length; ++i) {
                num = (num * 10) + digit(cbuf[i]);
            }
            return num;
        } else {
            // integer ]999_99999_99999_99999, ...]
            return parseDecimal(cbuf, length);
        }
    }

    /**
     * Parse a decimal integer literal.
     * 
     * @param cbuf
     *            the characters to parse
     * @param length
     *            the length of the characters
     * @return the parsed integer
     */
    static double parseSignedInteger(char[] cbuf, int length) {
        final char sign = cbuf[0];
        final int start = (sign == '-' || sign == '+') ? 1 : 0;
        if (length - start < 10) {
            // integer [0, 9999_99999]
            int num = 0;
            for (int i = start; i < length; ++i) {
                num = (num * 10) + digit(cbuf[i]);
            }
            return (sign == '-') ? -num : num;
        } else if (length - start < 19) {
            // integer [0, 999_99999_99999_99999]
            long num = 0;
            for (int i = start; i < length; ++i) {
                num = (num * 10) + digit(cbuf[i]);
            }
            return (sign == '-') ? -num : num;
        } else {
            // integer ]999_99999_99999_99999, ...]
            return parseDecimal(cbuf, length);
        }
    }

    /**
     * Parse a decimal integer literal.
     * 
     * @param s
     *            the string to parse
     * @return the parsed integer
     */
    public static double parseInteger(String s) {
        final char sign = s.charAt(0);
        final int start = (sign == '-' || sign == '+') ? 1 : 0;
        final int length = s.length();
        if (length - start < 10) {
            // integer [0, 9999_99999]
            int num = 0;
            for (int i = start; i < length; ++i) {
                num = (num * 10) + digit(s.charAt(i));
            }
            return (sign == '-') ? -(double) num : num;
        } else if (length - start < 19) {
            // integer [0, 999_99999_99999_99999]
            long num = 0;
            for (int i = start; i < length; ++i) {
                num = (num * 10) + digit(s.charAt(i));
            }
            return (sign == '-') ? -(double) num : num;
        } else {
            // integer ]999_99999_99999_99999, ...]
            return Double.parseDouble(s);
        }
    }

    /**
     * Parse a decimal integer literal.
     * 
     * @param s
     *            the string to parse
     * @param end
     *            the end index
     * @return the parsed integer
     */
    public static double parseInteger(String s, final int end) {
        final char sign = s.charAt(0);
        final int start = (sign == '-' || sign == '+') ? 1 : 0;
        final int length = s.length();
        if (end <= start || end >= length) {
            throw new IndexOutOfBoundsException();
        }
        if (end - start < 10) {
            // integer [0, 9999_99999]
            int num = 0;
            for (int i = start; i < end; ++i) {
                num = (num * 10) + digit(s.charAt(i));
            }
            return (sign == '-') ? -(double) num : num;
        } else if (end - start < 19) {
            // integer [0, 999_99999_99999_99999]
            long num = 0;
            for (int i = start; i < end; ++i) {
                num = (num * 10) + digit(s.charAt(i));
            }
            return (sign == '-') ? -(double) num : num;
        } else {
            // integer ]999_99999_99999_99999, ...]
            return Double.parseDouble(s.substring(0, end));
        }
    }

    /**
     * Parse a decimal number literal.
     * 
     * @param cbuf
     *            the characters to parse
     * @param length
     *            the length of the characters
     * @return the parsed decimal
     */
    static double parseDecimal(char[] cbuf, int length) {
        String string = new String(cbuf, 0, length);
        return Double.parseDouble(string);
    }

    /**
     * Parse a decimal number literal.
     * 
     * @param s
     *            the string to parse
     * @return the parsed decimal
     */
    public static double parseDecimal(String s) {
        return Double.parseDouble(s);
    }

    /**
     * Parse a decimal number literal.
     * 
     * @param s
     *            the string to parse
     * @param end
     *            the end index
     * @return the parsed decimal
     */
    public static double parseDecimal(String s, int end) {
        return Double.parseDouble(s.substring(0, end));
    }

    static BigInteger parseBigInt(char[] cbuf, int length) {
        String string = new String(cbuf, 0, length);
        return new BigInteger(string);
    }

    /**
     * Parse a binary integer literal.
     * 
     * @param cbuf
     *            the characters to parse
     * @param length
     *            the length of the characters
     * @return the binary integer
     */
    static double parseBinary(char[] cbuf, int length) {
        if (length < 32) {
            // integer [0, 7FFFFFFF]
            int num = 0;
            for (int i = 0; i < length; ++i) {
                num = (num << 1) | digit(cbuf[i]);
            }
            return num;
        } else if (length < 64) {
            // integer [0, 7FFFFFFFFFFFFFFF]
            long num = 0;
            for (int i = 0; i < length; ++i) {
                num = (num << 1) | digit(cbuf[i]);
            }
            return num;
        } else {
            // integer ]7FFFFFFFFFFFFFFF, ...]
            String string = new String(cbuf, 0, length);
            return StringToNumber.stringToNumber(string, 0, 2);
        }
    }

    static BigInteger parseBigIntBinary(char[] cbuf, int length) {
        String string = new String(cbuf, 0, length);
        return new BigInteger(string, 2);
    }

    /**
     * Parse a binary integer literal.
     * 
     * @param s
     *            the string to parse
     * @return the binary integer
     */
    public static double parseBinary(String s) {
        final int start = 2; // "0b" prefix
        final int length = s.length();
        if (length - start < 32) {
            // integer [0, 7FFFFFFF]
            int num = 0;
            for (int i = start; i < length; ++i) {
                num = (num << 1) | digit(s.charAt(i));
            }
            return num;
        } else if (length - start < 64) {
            // integer [0, 7FFFFFFFFFFFFFFF]
            long num = 0;
            for (int i = start; i < length; ++i) {
                num = (num << 1) | digit(s.charAt(i));
            }
            return num;
        } else {
            // integer ]7FFFFFFFFFFFFFFF, ...]
            return StringToNumber.stringToNumber(s, start, 2);
        }
    }

    /**
     * Parse an octal integer literal.
     * 
     * @param cbuf
     *            the characters to parse
     * @param length
     *            the length of the characters
     * @return the octal integer
     */
    static double parseOctal(char[] cbuf, int length) {
        if (length <= 10) {
            // integer [0, 07777777777]
            int num = 0;
            for (int i = 0; i < length; ++i) {
                num = (num << 3) | digit(cbuf[i]);
            }
            return num;
        } else if (length <= 21) {
            // integer [0, 0777777777777777777777]
            long num = 0;
            for (int i = 0; i < length; ++i) {
                num = (num << 3) | digit(cbuf[i]);
            }
            return num;
        } else {
            // integer ]0777777777777777777777, ...]
            String string = new String(cbuf, 0, length);
            return StringToNumber.stringToNumber(string, 0, 8);
        }
    }

    static BigInteger parseBigIntOctal(char[] cbuf, int length) {
        String string = new String(cbuf, 0, length);
        return new BigInteger(string, 8);
    }

    /**
     * Parse an octal integer literal.
     * 
     * @param s
     *            the string to parse
     * @return the octal integer
     */
    public static double parseOctal(String s) {
        final int start = 2; // "0o" prefix
        final int length = s.length();
        if (length - start <= 10) {
            // integer [0, 07777777777]
            int num = 0;
            for (int i = start; i < length; ++i) {
                num = (num << 3) | digit(s.charAt(i));
            }
            return num;
        } else if (length - start <= 21) {
            // integer [0, 0777777777777777777777]
            long num = 0;
            for (int i = start; i < length; ++i) {
                num = (num << 3) | digit(s.charAt(i));
            }
            return num;
        } else {
            // integer ]0777777777777777777777, ...]
            return StringToNumber.stringToNumber(s, start, 8);
        }
    }

    /**
     * Parse a hexadecimal integer literal.
     * 
     * @param cbuf
     *            the characters to parse
     * @param length
     *            the length of the characters
     * @return the hexadecimal integer
     */
    static double parseHex(char[] cbuf, int length) {
        if (length < 8) {
            // integer [0, FFFFFFF]
            int num = 0;
            for (int i = 0; i < length; ++i) {
                num = (num << 4) | hexDigit(cbuf[i]);
            }
            return num;
        } else if (length < 16) {
            // integer [0, FFFFFFFFFFFFFFF]
            long num = 0;
            for (int i = 0; i < length; ++i) {
                num = (num << 4) | hexDigit(cbuf[i]);
            }
            return num;
        } else {
            // integer ]FFFFFFFFFFFFFFF, ...]
            String string = new String(cbuf, 0, length);
            return StringToNumber.stringToNumber(string, 0, 16);
        }
    }

    static BigInteger parseBigIntHex(char[] cbuf, int length) {
        String string = new String(cbuf, 0, length);
        return new BigInteger(string, 16);
    }

    /**
     * Parse a hexadecimal integer literal.
     * 
     * @param s
     *            the characters to parse
     * @return the hexadecimal integer
     */
    public static double parseHex(String s) {
        final int start = 2; // "0x" prefix
        final int length = s.length();
        if (length - start < 8) {
            // integer [0, FFFFFFF]
            int num = 0;
            for (int i = start; i < length; ++i) {
                num = (num << 4) | hexDigit(s.charAt(i));
            }
            return num;
        } else if (length - start < 16) {
            // integer [0, FFFFFFFFFFFFFFF]
            long num = 0;
            for (int i = start; i < length; ++i) {
                num = (num << 4) | hexDigit(s.charAt(i));
            }
            return num;
        } else {
            // integer ]FFFFFFFFFFFFFFF, ...]
            return StringToNumber.stringToNumber(s, start, 16);
        }
    }
}
