/*
* Copyright (c) 1997 - 2016
* Actelion Pharmaceuticals Ltd.
* Gewerbestrasse 16
* CH-4123 Allschwil, Switzerland
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
* 3. Neither the name of the the copyright holder nor the
*    names of its contributors may be used to endorse or promote products
*    derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

package com.actelion.research.util;

public class DoubleFormat {
	public static final String[] ROUNDING_MODE_TEXT = {"Non Rounded", "Significant Digits", "Multiple Of"};
	public static final String[] ROUNDING_MODE_CODE = {"none", "digits", "multiple"};
	public static final int ROUNDING_MODE_NONE = 0;
	public static final int ROUNDING_MODE_SIGNIFICANT_DIGITS = 1;
	public static final int ROUNDING_MODE_MULTIPLE_OF = 2;

	private static String[] ZEROS = {"","0","00","000","0000","00000","000000","0000000","00000000"};
	/**
	 * Converts a double value into a String representation in scientific format
	 * rounded to 5 significant digits, e.g. 1.2345e-6.
	 * Double values that are effectively integers are turned into the scientific
	 * format only, if they consist of more than 8 digits.
	 * @param theDouble
	 * @return
	 */
	public static String toString(double theDouble) {
		return toString(theDouble, 5, true);
		}

	/**
	 * Converts a double value into a String representation in scientific format
	 * rounded to a definable number of digits, e.g. 1.2345e-6.
	 * Double values that are effectively integers are turned into the scientific
	 * format only, if they consist of more than significantDigits+3 digits.
	 * @param value
	 * @param significantDigits
	 * @return
	 */
	public static String toString(double value, int significantDigits) {
		return toString(value, significantDigits, true);
		}

	/**
	 * Converts a double value into a String representation in scientific format rounded to
	 * a multiple of roundingValue (if mode==ROUNDING_MODE_MULTIPLE_OF) or rounded to a
	 * definable number of digits, e.g. 1.2345e-6 (if mode==ROUNDING_MODE_SIGNIFICANT_DIGITS).
	 * Double values that are effectively integers are turned into the scientific
	 * format only, if they consist of more than significantDigits+3 digits.
	 * @param value numerical value to be converted into String representation
	 * @param roundingMode one of ROUNDING_MODE_...
	 * @param roundingValue roundingMode specific value for rounding
	 * @return
	 */
	public static String toString(double value, int roundingMode, double roundingValue) {
		return roundingMode == ROUNDING_MODE_NONE ? Double.toString(value)
				: roundingMode == ROUNDING_MODE_SIGNIFICANT_DIGITS ? toString(value, (int)roundingValue, true)
				: DoubleFormat.toString(Math.round(value / roundingValue) * roundingValue, 14);
		// Even for ROUNDING_MODE_MULTIPLE_OF we use an additional DoubleFormat rounding with 14 digits
		// to avoid rounding errors: 6 * 0.1 -> 0.6000000000000001 !!!
	}

	/**
	 * Converts a double value into a String representation in scientific format
	 * rounded to a definable number of digits, e.g. 1.2345e-6.
	 * Double values that are effectively integers are turned into the scientific
	 * format only, if they consist of more than significantDigits+3 digits.
	 * If skipTrailingZeros is true, then integer output is not rounded: 7173 instead of 7200.
	 * @param value
	 * @param significantDigits
	 * @param skipTrailingZeros if true then trailing zeros after a decimal point are not shown and integers are not rounded
	 * @return
	 */
	public static String toString(double value, int significantDigits, boolean skipTrailingZeros) {
		if (Double.isNaN(value))
			return "NaN";
		if (Double.isInfinite(value))
			return "Infinity";

		if (value == 0.0)
			return (skipTrailingZeros || significantDigits==1) ? "0" : "0."+zeros(significantDigits-1);

		// Determine cipher count of integer fraction in excess of significant digits.
		// For those cases, where the number is not expressed in scientific notation,
		// the increase significant digits to prevent trailing zeros through rounding.
		//		int cipherExcess = Long.toString(Math.round(value)).length() - significantDigits;
		//		if (cipherExcess > 0 && cipherExcess <= 4)
		//			significantDigits += cipherExcess;
		// Returned to original behaviour with consistent rounding no matter whether we see trailing zeros
		// in integer part that pretend precision with digits (zeros). TLS 21Jun2020

		double limit1 = 1;
		for (int i=1; i<significantDigits; i++)
			limit1 *=10;
		double limit2 = limit1 * 10;

		int exponent = 0;
		while (Math.abs(value) + 0.5 < limit1) {
			value *= 10;
			exponent--;
			}
		while (Math.abs(value) + 0.5 >= limit2) {
			value /= 10.0;
			exponent++;
			}

		return toString((long)(value+(value < 0 ? -0.5 : 0.5)), exponent, significantDigits, skipTrailingZeros);
		}

	/**
	 * Converts the value with the given exponent into a short string representation
	 * using the scientific notation if it is more compact. Trailing zeros are not shown.
	 * @param value
	 * @param exponent
	 * @return
	 */
	public static String toShortString(long value, int exponent) {
		return toString(value, exponent, 16, true);
		}

	private static String toString(long value, int exponent, int significantDigits, boolean skipTrailingZeros) {
		int noOfCiphers = 1;

		if (value == 0)
			return (skipTrailingZeros || significantDigits==1) ? "0" : "0."+zeros(significantDigits-1);

		if (value != 0) {
			while (value % 10 == 0) {
				value /= 10;
				exponent++;
				}

			noOfCiphers = 0;
			for (long temp=value; temp!=0; temp/=10)
				noOfCiphers++;
			}

		assert(significantDigits >= noOfCiphers);

		StringBuilder label = new StringBuilder();
		if (value < 0)
			label.append('-');

		int trailingZeros = Math.max(0, significantDigits-noOfCiphers);

		String cipherString = Long.toString(Math.abs(value));
		if (exponent == 0) {
			label.append(cipherString);
			if (!skipTrailingZeros && trailingZeros != 0) {
				label.append(".");
				label.append(zeros(trailingZeros));
				}
			}
		else if (exponent > 0) {
			boolean useScientificNotation = skipTrailingZeros ? (exponent > 4) : (exponent-trailingZeros > 4);
			if (useScientificNotation) {	// scientific notation for numbers above/equal 0.001
				if (noOfCiphers == 1) {
					label.append(cipherString);
					if (significantDigits > 1) {
						label.append(".");
						if (skipTrailingZeros)
							label.append("0");
						else
							label.append(zeros(trailingZeros));
						}
					}
				else {
					label.append(cipherString.substring(0, 1));
					label.append(".");
					label.append(cipherString.substring(1));
					if (!skipTrailingZeros && trailingZeros != 0)
						label.append(zeros(trailingZeros));
					}
				label.append("e");
				label.append(Integer.toString(exponent+noOfCiphers-1));
				}
			else {
				label.append(cipherString);
				label.append(zeros(exponent));
				if (!skipTrailingZeros && trailingZeros > exponent) {
					label.append(".");
					label.append(zeros(trailingZeros-exponent));
					}
				}
			}
		else {  // exponent < 0
			if (-exponent < noOfCiphers) {
				label.append(cipherString.substring(0, noOfCiphers+exponent));
				label.append(".");
				label.append(cipherString.substring(noOfCiphers+exponent));
				if (!skipTrailingZeros && trailingZeros != 0)
					label.append(zeros(trailingZeros));
				}
			else if (-exponent == noOfCiphers) {
				label.append("0.");
				label.append(cipherString);
				if (!skipTrailingZeros && trailingZeros != 0)
					label.append(zeros(trailingZeros));
				}
			else {
				if (exponent+noOfCiphers < -2) {	// scientific notation for numbers below 0.001
					if (noOfCiphers == 1) {
						label.append(cipherString);
						if (significantDigits > 1) {
							label.append(".");
							if (skipTrailingZeros)
								label.append("0");
							else
								label.append(zeros(trailingZeros));
							}
						}
					else {
						label.append(cipherString.charAt(0));
						label.append(".");
						label.append(cipherString.substring(1));
						if (!skipTrailingZeros && trailingZeros != 0)
							label.append(zeros(trailingZeros));
						}
					label.append("e-");
					label.append(Integer.toString(1-exponent-noOfCiphers));
					}
				else {
					label.append("0.");
					label.append(zeros(-exponent-noOfCiphers));
					label.append(cipherString);
					if (!skipTrailingZeros && trailingZeros != 0)
						label.append(zeros(trailingZeros));
					}
				}
			}
//System.out.println("int:"+theInt+" exp:"+exponent+" label:"+label);
		return label.toString();
		}

	private static String zeros(int i) {
		if (i<ZEROS.length)
			return ZEROS[i];
		StringBuilder b = new StringBuilder();
		while (i >= ZEROS.length) {
			b.append(ZEROS[ZEROS.length-1]);
			i -= ZEROS.length-1;
			}
		b.append(ZEROS[i]);
		return b.toString();
		}
	}