/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.luni.util;

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

/**
 * Used to parse a string and return either a single or double precision
 * floating point number.
 */
public final class FloatingPointParser {

    private static final class StringExponentPair {
        String s;

        int e;

        boolean negative;

        StringExponentPair(String s, int e, boolean negative) {
            this.s = s;
            this.e = e;
            this.negative = negative;
        }
    }

    /**
     * Takes a String and an integer exponent. The String should hold a positive
     * integer value (or zero). The exponent will be used to calculate the
     * floating point number by taking the positive integer the String
     * represents and multiplying by 10 raised to the power of the of the
     * exponent. Returns the closest double value to the real number
     * 
     * @param s
     *            the String that will be parsed to a floating point
     * @param e
     *            an int represent the 10 to part
     * @return the double closest to the real number
     * 
     * @exception NumberFormatException
     *                if the String doesn't represent a positive integer value
     */
    private static native double parseDblImpl(String s, int e);

    /**
     * Takes a String and an integer exponent. The String should hold a positive
     * integer value (or zero). The exponent will be used to calculate the
     * floating point number by taking the positive integer the String
     * represents and multiplying by 10 raised to the power of the of the
     * exponent. Returns the closest float value to the real number
     * 
     * @param s
     *            the String that will be parsed to a floating point
     * @param e
     *            an int represent the 10 to part
     * @return the float closest to the real number
     * 
     * @exception NumberFormatException
     *                if the String doesn't represent a positive integer value
     */
    private static native float parseFltImpl(String s, int e);

    /**
     * Takes a String and does some initial parsing. Should return a
     * StringExponentPair containing a String with no leading or trailing white
     * space and trailing zeroes eliminated. The exponent of the
     * StringExponentPair will be used to calculate the floating point number by
     * taking the positive integer the String represents and multiplying by 10
     * raised to the power of the of the exponent.
     * 
     * @param s
     *            the String that will be parsed to a floating point
     * @param length
     *            the length of s
     * @return a StringExponentPair with necessary values
     * 
     * @exception NumberFormatException
     *                if the String doesn't pass basic tests
     */
    private static StringExponentPair initialParse(String s, int length) {
        boolean negative = false;
        char c;
        int start, end, decimal;
        int e = 0;

        start = 0;
        if (length == 0)
            throw new NumberFormatException(s);

        c = s.charAt(length - 1);
        if (c == 'D' || c == 'd' || c == 'F' || c == 'f') {
            length--;
            if (length == 0)
                throw new NumberFormatException(s);
        }

        end = Math.max(s.indexOf('E'), s.indexOf('e'));
        if (end > -1) {
            if (end + 1 == length)
                throw new NumberFormatException(s);

                        int exponent_offset = end + 1;
                        if (s.charAt(exponent_offset) == '+') {
                                if (s.charAt(exponent_offset + 1) == '-') {
                                        throw new NumberFormatException(s);
                                }
                                exponent_offset++; // skip the plus sign
                        }
            try {
                e = Integer.parseInt(s.substring(exponent_offset,
                                                                 length));
                        } catch (NumberFormatException ex) {
                                // ex contains the exponent substring
                                // only so throw a new exception with
                                // the correct string
                throw new NumberFormatException(s);
                        }                            
                                    
        } else {
            end = length;
        }
        if (length == 0)
            throw new NumberFormatException(s);

        c = s.charAt(start);
        if (c == '-') {
            ++start;
            --length;
            negative = true;
        } else if (c == '+') {
            ++start;
            --length;
        }
        if (length == 0)
            throw new NumberFormatException(s);

        decimal = s.indexOf('.');
        if (decimal > -1) {
            e -= end - decimal - 1;
            s = s.substring(start, decimal) + s.substring(decimal + 1, end);
        } else {
            s = s.substring(start, end);
        }

        if ((length = s.length()) == 0)
            throw new NumberFormatException();

        end = length;
        while (end > 1 && s.charAt(end - 1) == '0')
            --end;

        start = 0;
        while (start < end - 1 && s.charAt(start) == '0')
            start++;

        if (end != length || start != 0) {
            e += length - end;
            s = s.substring(start, end);
        }

        return new StringExponentPair(s, e, negative);
    }

    /*
     * Assumes the string is trimmed.
     */
    private static double parseDblName(String namedDouble, int length) {
        // Valid strings are only +Nan, NaN, -Nan, +Infinity, Infinity,
        // -Infinity.
        if ((length != 3) && (length != 4) && (length != 8) && (length != 9)) {
            throw new NumberFormatException();
        }

        boolean negative = false;
        int cmpstart = 0;
        switch (namedDouble.charAt(0)) {
        case '-':
            negative = true; // fall through
        case '+':
            cmpstart = 1;
        default:
        }

        if (namedDouble.regionMatches(false, cmpstart, "Infinity", 0, 8)) {
            return negative ? Double.NEGATIVE_INFINITY
                    : Float.POSITIVE_INFINITY;
        }

        if (namedDouble.regionMatches(false, cmpstart, "NaN", 0, 3)) {
            return Double.NaN;
        }

        throw new NumberFormatException();
    }

    /*
     * Assumes the string is trimmed.
     */
    private static float parseFltName(String namedFloat, int length) {
        // Valid strings are only +Nan, NaN, -Nan, +Infinity, Infinity,
        // -Infinity.
        if ((length != 3) && (length != 4) && (length != 8) && (length != 9)) {
            throw new NumberFormatException();
        }

        boolean negative = false;
        int cmpstart = 0;
        switch (namedFloat.charAt(0)) {
        case '-':
            negative = true; // fall through
        case '+':
            cmpstart = 1;
        default:
        }

        if (namedFloat.regionMatches(false, cmpstart, "Infinity", 0, 8)) {
            return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
        }

        if (namedFloat.regionMatches(false, cmpstart, "NaN", 0, 3)) {
            return Float.NaN;
        }

        throw new NumberFormatException();
    }

    /**
     * Returns the closest double value to the real number in the string.
     * 
     * @param s
     *            the String that will be parsed to a floating point
     * @return the double closest to the real number
     * 
     * @exception NumberFormatException
     *                if the String doesn't represent a double
     */
    public static double parseDouble(String s) {
        s = s.trim();
        int length = s.length();

        if (length == 0) {
            throw new NumberFormatException(s);
        }

        // See if this could be a named double
        char last = s.charAt(length - 1);
        if ((last == 'y') || (last == 'N')) {
            return parseDblName(s, length);
        }
        
        // See if it could be a hexadecimal representation
        if (s.toLowerCase().indexOf("0x") != -1) { //$NON-NLS-1$
            return HexStringParser.parseDouble(s);
        }
        
        StringExponentPair info = initialParse(s, length);

        double result = parseDblImpl(info.s, info.e);
        if (info.negative)
            result = -result;

        return result;
    }

    /**
     * Returns the closest float value to the real number in the string.
     * 
     * @param s
     *            the String that will be parsed to a floating point
     * @return the float closest to the real number
     * 
     * @exception NumberFormatException
     *                if the String doesn't represent a float
     */
    public static float parseFloat(String s) {
        s = s.trim();
        int length = s.length();

        if (length == 0) {
            throw new NumberFormatException(s);
        }

        // See if this could be a named float
        char last = s.charAt(length - 1);
        if ((last == 'y') || (last == 'N')) {
            return parseFltName(s, length);
        }
        
        // See if it could be a hexadecimal representation
        if (s.toLowerCase().indexOf("0x") != -1) { //$NON-NLS-1$
            return HexStringParser.parseFloat(s);
        }
        
        StringExponentPair info = initialParse(s, length);

        float result = parseFltImpl(info.s, info.e);
        if (info.negative)
            result = -result;

        return result;
    }
}
