/*******************************************************************************
 * Copyright (c) 2004, 2011 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.compiler.util;

/**
 * Internal utility for declaring with hexadecimal double and float literals.
 *
 * @since 3.1
 */
public class FloatUtil {

	private static final int DOUBLE_FRACTION_WIDTH = 52;

	private static final int DOUBLE_PRECISION = 53;

	private static final int MAX_DOUBLE_EXPONENT = +1023;

	private static final int MIN_NORMALIZED_DOUBLE_EXPONENT = -1022;

	private static final int MIN_UNNORMALIZED_DOUBLE_EXPONENT = MIN_NORMALIZED_DOUBLE_EXPONENT
			- DOUBLE_PRECISION;

	private static final int DOUBLE_EXPONENT_BIAS = +1023;

	private static final int DOUBLE_EXPONENT_SHIFT = 52;

	private static final int SINGLE_FRACTION_WIDTH = 23;

	private static final int SINGLE_PRECISION = 24;

	private static final int MAX_SINGLE_EXPONENT = +127;

	private static final int MIN_NORMALIZED_SINGLE_EXPONENT = -126;

	private static final int MIN_UNNORMALIZED_SINGLE_EXPONENT = MIN_NORMALIZED_SINGLE_EXPONENT
			- SINGLE_PRECISION;

	private static final int SINGLE_EXPONENT_BIAS = +127;

	private static final int SINGLE_EXPONENT_SHIFT = 23;

	/**
	 * Returns the float value corresponding to the given
	 * hexadecimal floating-point single precision literal.
	 * The literal must be syntactically correct, and must be
	 * a float literal (end in a 'f' or 'F'). It must not
	 * include either leading or trailing whitespace or
	 * a sign.
	 * <p>
	 * This method returns the same answer as
	 * Float.parseFloat(new String(source)) does in JDK 1.5,
	 * except that this method returns Floal.NaN if it
	 * would underflow to 0 (parseFloat just returns 0).
	 * The method handles all the tricky cases, including
	 * fraction rounding to 24 bits and gradual underflow.
	 * </p>
	 *
	 * @param source source string containing single precision
	 * hexadecimal floating-point literal
	 * @return the float value, including Float.POSITIVE_INFINITY
	 * if the non-zero value is too large to be represented, and
	 * Float.NaN if the non-zero value is too small to be represented
	 */
	public static float valueOfHexFloatLiteral(char[] source) {
		long bits = convertHexFloatingPointLiteralToBits(source);
		return Float.intBitsToFloat((int) bits);
	}

	/**
	 * Returns the double value corresponding to the given
	 * hexadecimal floating-point double precision literal.
	 * The literal must be syntactially correct, and must be
	 * a double literal (end in an optional 'd' or 'D').
	 * It must not include either leading or trailing whitespace or
	 * a sign.
	 * <p>
	 * This method returns the same answer as
	 * Double.parseDouble(new String(source)) does in JDK 1.5,
	 * except that this method throw NumberFormatException in
	 * the case of overflow to infinity or underflow to 0.
	 * The method handles all the tricky cases, including
	 * fraction rounding to 53 bits and gradual underflow.
	 * </p>
	 *
	 * @param source source string containing double precision
	 * hexadecimal floating-point literal
	 * @return the double value, including Double.POSITIVE_INFINITY
	 * if the non-zero value is too large to be represented, and
	 * Double.NaN if the non-zero value is too small to be represented
	 */
	public static double valueOfHexDoubleLiteral(char[] source) {
		long bits = convertHexFloatingPointLiteralToBits(source);
		return Double.longBitsToDouble(bits);
	}

	/**
	 * Returns the given hexadecimal floating-point literal as
	 * the bits for a single-precision  (float) or a
	 * double-precision (double) IEEE floating point number.
	 * The literal must be syntactically correct.  It must not
	 * include either leading or trailing whitespace or a sign.
	 *
	 * @param source source string containing hexadecimal floating-point literal
	 * @return for double precision literals, bits suitable
	 * for passing to Double.longBitsToDouble; for single precision literals,
	 * bits suitable for passing to Single.intBitsToDouble in the bottom
	 * 32 bits of the result
	 * @throws NumberFormatException if the number cannot be parsed
	 */
	private static long convertHexFloatingPointLiteralToBits(char[] source) {
		int length = source.length;
		long mantissa = 0;

		// Step 1: process the '0x' lead-in
		int next = 0;
		char nextChar = source[next];
		nextChar = source[next];
		if (nextChar == '0') {
			next++;
		} else {
			throw new NumberFormatException();
		}
		nextChar = source[next];
		if (nextChar == 'X' || nextChar == 'x') {
			next++;
		} else {
			throw new NumberFormatException();
		}

		// Step 2: process leading '0's either before or after the '.'
		int binaryPointPosition = -1;
		loop: while (true) {
			nextChar = source[next];
			switch (nextChar) {
			case '0':
				next++;
				continue loop;
			case '.':
				binaryPointPosition = next;
				next++;
				continue loop;
			default:
				break loop;
			}
		}

		// Step 3: process the mantissa
		// leading zeros have been trimmed
		int mantissaBits = 0;
		int leadingDigitPosition = -1;
		loop: while (true) {
			nextChar = source[next];
			int hexdigit;
			switch (nextChar) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				hexdigit = nextChar - '0';
				break;
			case 'a':
			case 'b':
			case 'c':
			case 'd':
			case 'e':
			case 'f':
				hexdigit = (nextChar - 'a') + 10;
				break;
			case 'A':
			case 'B':
			case 'C':
			case 'D':
			case 'E':
			case 'F':
				hexdigit = (nextChar - 'A') + 10;
				break;
			case '.':
				binaryPointPosition = next;
				next++;
				continue loop;
			default:
				if (binaryPointPosition < 0) {
					// record virtual '.' as being to right of all digits
					binaryPointPosition = next;
				}
				break loop;
			}
			if (mantissaBits == 0) {
				// this is the first non-zero hex digit
				// ignore leading binary 0's in hex digit
				leadingDigitPosition = next;
				mantissa = hexdigit;
				mantissaBits = 4;
			} else if (mantissaBits < 60) {
				// middle hex digits
				mantissa <<= 4;
				mantissa |= hexdigit;
				mantissaBits += 4;
			} else {
				// more mantissa bits than we can handle
				// drop this hex digit on the ground
			}
			next++;
			continue loop;
		}

		// Step 4: process the 'P'
		nextChar = source[next];
		if (nextChar == 'P' || nextChar == 'p') {
			next++;
		} else {
			throw new NumberFormatException();
		}

		// Step 5: process the exponent
		int exponent = 0;
		int exponentSign = +1;
		loop: while (next < length) {
			nextChar = source[next];
			switch (nextChar) {
			case '+':
				exponentSign = +1;
				next++;
				continue loop;
			case '-':
				exponentSign = -1;
				next++;
				continue loop;
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				int digit = nextChar - '0';
				exponent = (exponent * 10) + digit;
				next++;
				continue loop;
			default:
				break loop;
			}
		}

		// Step 6: process the optional 'f' or 'd'
		boolean doublePrecision = true;
		if (next < length) {
			nextChar = source[next];
			switch (nextChar) {
			case 'f':
			case 'F':
				doublePrecision = false;
				next++;
				break;
			case 'd':
			case 'D':
				doublePrecision = true;
				next++;
				break;
			default:
				throw new NumberFormatException();
			}
		}

		// at this point, all the parsing is done
		// Step 7: handle mantissa of zero
		if (mantissa == 0) {
			return 0L;
		}

		// Step 8: normalize non-zero mantissa
		// mantissa is in right-hand mantissaBits
		// ensure that top bit (as opposed to hex digit) is 1
		int scaleFactorCompensation = 0;
		long top = (mantissa >>> (mantissaBits - 4));
		if ((top & 0x8) == 0) {
			mantissaBits--;
			scaleFactorCompensation++;
			if ((top & 0x4) == 0) {
				mantissaBits--;
				scaleFactorCompensation++;
				if ((top & 0x2) == 0) {
					mantissaBits--;
					scaleFactorCompensation++;
				}
			}
		}

		// Step 9: convert double literals to IEEE double
		long result = 0L;
		if (doublePrecision) {
			long fraction;
			if (mantissaBits > DOUBLE_PRECISION) {
				// more bits than we can keep
				int extraBits = mantissaBits - DOUBLE_PRECISION;
				// round to DOUBLE_PRECISION bits
				fraction = mantissa >>> (extraBits - 1);
				long lowBit = fraction & 0x1;
				fraction += lowBit;
				fraction = fraction >>> 1;
				if ((fraction & (1L << DOUBLE_PRECISION)) != 0) {
					fraction = fraction >>> 1;
					scaleFactorCompensation -= 1;
				}
			} else {
				// less bits than the faction can hold - pad on right with 0s
				fraction = mantissa << (DOUBLE_PRECISION - mantissaBits);
			}

			int scaleFactor = 0; // how many bits to move '.' to before leading hex digit
			if (mantissaBits > 0) {
				if (leadingDigitPosition < binaryPointPosition) {
					// e.g., 0x80.0p0 has scaleFactor == +8
					scaleFactor = 4 * (binaryPointPosition - leadingDigitPosition);
					// e.g., 0x10.0p0 has scaleFactorCompensation == +3
					scaleFactor -= scaleFactorCompensation;
				} else {
					// e.g., 0x0.08p0 has scaleFactor == -4
					scaleFactor = -4
							* (leadingDigitPosition - binaryPointPosition - 1);
					// e.g., 0x0.01p0 has scaleFactorCompensation == +3
					scaleFactor -= scaleFactorCompensation;
				}
			}

			int e = (exponentSign * exponent) + scaleFactor;
			if (e - 1 > MAX_DOUBLE_EXPONENT) {
				// overflow to +infinity
				result = Double.doubleToLongBits(Double.POSITIVE_INFINITY);
			} else if (e - 1 >= MIN_NORMALIZED_DOUBLE_EXPONENT) {
				// can be represented as a normalized double
				// the left most bit must be discarded (it's always a 1)
				long biasedExponent = e - 1 + DOUBLE_EXPONENT_BIAS;
				result = fraction & ~(1L << DOUBLE_FRACTION_WIDTH);
				result |= (biasedExponent << DOUBLE_EXPONENT_SHIFT);
			} else if (e - 1 > MIN_UNNORMALIZED_DOUBLE_EXPONENT) {
				// can be represented as an unnormalized double
				long biasedExponent = 0;
				result = fraction >>> (MIN_NORMALIZED_DOUBLE_EXPONENT - e + 1);
				result |= (biasedExponent << DOUBLE_EXPONENT_SHIFT);
			} else {
				// underflow - return Double.NaN
				result = Double.doubleToLongBits(Double.NaN);
			}
			return result;
		}

		// Step 10: convert float literals to IEEE single
		long fraction;
		if (mantissaBits > SINGLE_PRECISION) {
			// more bits than we can keep
			int extraBits = mantissaBits - SINGLE_PRECISION;
			// round to DOUBLE_PRECISION bits
			fraction = mantissa >>> (extraBits - 1);
			long lowBit = fraction & 0x1;
			fraction += lowBit;
			fraction = fraction >>> 1;
			if ((fraction & (1L << SINGLE_PRECISION)) != 0) {
				fraction = fraction >>> 1;
				scaleFactorCompensation -= 1;
			}
		} else {
			// less bits than the faction can hold - pad on right with 0s
			fraction = mantissa << (SINGLE_PRECISION - mantissaBits);
		}

		int scaleFactor = 0; // how many bits to move '.' to before leading hex digit
		if (mantissaBits > 0) {
			if (leadingDigitPosition < binaryPointPosition) {
				// e.g., 0x80.0p0 has scaleFactor == +8
				scaleFactor = 4 * (binaryPointPosition - leadingDigitPosition);
				// e.g., 0x10.0p0 has scaleFactorCompensation == +3
				scaleFactor -= scaleFactorCompensation;
			} else {
				// e.g., 0x0.08p0 has scaleFactor == -4
				scaleFactor = -4
						* (leadingDigitPosition - binaryPointPosition - 1);
				// e.g., 0x0.01p0 has scaleFactorCompensation == +3
				scaleFactor -= scaleFactorCompensation;
			}
		}

		int e = (exponentSign * exponent) + scaleFactor;
		if (e - 1 > MAX_SINGLE_EXPONENT) {
			// overflow to +infinity
			result = Float.floatToIntBits(Float.POSITIVE_INFINITY);
		} else if (e - 1 >= MIN_NORMALIZED_SINGLE_EXPONENT) {
			// can be represented as a normalized single
			// the left most bit must be discarded (it's always a 1)
			long biasedExponent = e - 1 + SINGLE_EXPONENT_BIAS;
			result = fraction & ~(1L << SINGLE_FRACTION_WIDTH);
			result |= (biasedExponent << SINGLE_EXPONENT_SHIFT);
		} else if (e - 1 > MIN_UNNORMALIZED_SINGLE_EXPONENT) {
			// can be represented as an unnormalized single
			long biasedExponent = 0;
			result = fraction >>> (MIN_NORMALIZED_SINGLE_EXPONENT - e + 1);
			result |= (biasedExponent << SINGLE_EXPONENT_SHIFT);
		} else {
			// underflow - return Float.NaN
			result = Float.floatToIntBits(Float.NaN);
		}
		return result;
	}
}
