/*
 * @(#) IntegerConverter.java
 *
 * Copyright 2002 - 2003 JIDE Software. All rights reserved.
 */
package com.jidesoft.converter;

import com.jidesoft.utils.SystemInfo;

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Currency;
import java.util.Locale;

/**
 * Converter which converts Number to String and converts it back. You can pass in a NumberFormat as UserObject of
 * ConverterContext if you want to control the format of the number such as maximum decimal point etc.
 */
abstract public class NumberConverter implements ObjectConverter {
    private NumberFormat _numberFormat;
    private boolean _avoidNegativeZero;

    public static final ConverterContext CONTEXT_FRACTION_NUMBER = new ConverterContext("Fraction Number");

    /**
     * Creates a number converter with no NumberFormat.
     */
    public NumberConverter() {
    }

    /**
     * Creates the number converter with a specified NumberFormat.
     *
     * @param format the number format.
     */
    public NumberConverter(NumberFormat format) {
        _numberFormat = format;
        if (_numberFormat != null) {
            _numberFormat.setGroupingUsed(isGroupingUsed()); // apply the grouingUsed flag.
        }
    }

    private String trimNegativeSign(String text) {
        if (text.indexOf('-') != 0) {
            return text;
        }
        for (char ch = '1'; ch <= '9'; ch++) {
            if (text.indexOf(ch) >= 0) {
                return text;
            }
        }
        for (char ch = 'a'; ch <= 'f'; ch++) {
            if (text.indexOf(ch) >= 0) {
                return text;
            }
        }
        for (char ch = 'A'; ch <= 'F'; ch++) {
            if (text.indexOf(ch) >= 0) {
                return text;
            }
        }
        if (text.indexOf('0') < 0) {
            return text;
        }
        return text.substring(1);
    }

    public String toString(Object object, ConverterContext context) {
        // format on userObject has a higher priority.
        try {
            if (context == null || context.getUserObject() == null || !(context.getUserObject() instanceof NumberFormat)) {
                if (object instanceof Number && ((Number) object).doubleValue() == Double.NaN) {
                    return "";
                }
                else {
                    String text = getNumberFormat().format(object);
                    if (isAvoidNegativeZero()) {
                        text = trimNegativeSign(text);
                    }
                    return text;
                }
            }
            else {
                NumberFormat format = (NumberFormat) context.getUserObject();
                String text = format.format(object);
                if (isAvoidNegativeZero()) {
                    text = trimNegativeSign(text);
                }
                return text;
            }
        }
        catch (IllegalArgumentException e) {
            return "";
        }
    }

    public boolean supportToString(Object object, ConverterContext context) {
        return true;
    }

    public void setNumberFormat(NumberFormat numberFormat) {
        _numberFormat = numberFormat;
    }

    /**
     * Gets the NumberFormat. Of setNumberFormat is never called, it will return DecimalFormat.getInstance().
     *
     * @return the NumberFormat.
     */
    protected NumberFormat getNumberFormat() {
        if (_numberFormat == null) {
            _numberFormat = DecimalFormat.getInstance();
            _numberFormat.setGroupingUsed(isGroupingUsed());
        }
        return _numberFormat;
    }

    /**
     * Gets the NumberFormat for Locale.US.
     *
     * @return the NumberFormat for Locale.US.
     */
    protected NumberFormat getDefaultNumberFormat() {
        NumberFormat format = DecimalFormat.getInstance(Locale.US);
        format.setGroupingUsed(isGroupingUsed());
        return format;
    }

    /**
     * Parse the string as number. It will try using getNumberFormat first then try getDefaultNumberFormat which is the
     * US locale number format.
     *
     * @param string the string
     * @return the Number. Null if the string is not a number.
     */
    protected Number parseNumber(String string) {
        Number number;
        try {
            number = getNumberFormat().parse(string);
        }
        catch (ParseException e) {
            try {
                number = getDefaultNumberFormat().parse(string);
            }
            catch (ParseException e1) {
                number = null;
            }
        }
        return number;
    }

    private static boolean _groupingUsed = true;

    /**
     * Gets flag if the grouping is used for the format.
     *
     * @return if the grouping is used.
     */
    public static boolean isGroupingUsed() {
        return _groupingUsed;
    }

    /**
     * Sets if the grouping will be used for the NumberFormat. We will call NumberFormat#setGroupingUsed when we create
     * the NumberFormat. Default is true. If you want to call this method, please make sure calling it before
     * ObjectConverterManager is used.
     *
     * @param groupingUsed true or false.
     */
    public static void setGroupingUsed(boolean groupingUsed) {
        _groupingUsed = groupingUsed;
    }

    /**
     * Set the fraction digits of this converter.
     *
     * @param minDigits minimum fraction digits
     * @param maxDigits maximum fraction digits
     */
    public void setFractionDigits(int minDigits, int maxDigits) {
        NumberFormat numberFormat = getNumberFormat();
        numberFormat.setMinimumFractionDigits(minDigits);
        numberFormat.setMaximumFractionDigits(maxDigits);
    }

    /**
     * Set the currency of this converter.
     *
     * @param currency currency
     */
    public void setCurrency(Currency currency) {
        NumberFormat numberFormat = getNumberFormat();
        numberFormat.setCurrency(currency);
    }

    /**
     * Set the integer digits of this converter.
     *
     * @param minDigits minimum integer digits
     * @param maxDigits maximum integer digits
     */
    public void setIntegerDigits(int minDigits, int maxDigits) {
        NumberFormat numberFormat = getNumberFormat();
        numberFormat.setMinimumIntegerDigits(minDigits);
        numberFormat.setMaximumIntegerDigits(maxDigits);
    }

    /**
     * Set the rounding mode of this converter.
     *
     * @param mode rounding mode
     */
    public void setRoundingMode(RoundingMode mode) {
        if (SystemInfo.isJdk6Above()) {
            NumberFormat numberFormat = getNumberFormat();
            numberFormat.setRoundingMode(mode);
        }
    }

    /**
     * Gets the flag indicating if negative zero should be avoided.
     *
     * @return true if negative zero should be avoided. Otherwise false.
     * @see #setAvoidNegativeZero(boolean)
     * @since 3.5.1
     */
    public boolean isAvoidNegativeZero() {
        return _avoidNegativeZero;
    }

    /**
     * Sets the flag indicating if negative zero should be avoided.
     * <p/>
     * By default, the value is false to keep backward compatibility. If you don't like the string like "-0.00", please
     * set this flag to true to remove the negative sign.
     *
     * @param avoidNegativeZero the flag
     * @since 3.5.1
     */
    public void setAvoidNegativeZero(boolean avoidNegativeZero) {
        _avoidNegativeZero = avoidNegativeZero;
    }
}
