package uk.me.nxg.unity;

/**
 * An abstract class which writes out unit expressions in the style
 * appropriate for a given syntax.
 *
 * <p>Implementations of this class (that is, implementations for a
 * specific syntax) should aim not to write out any syntactically
 * invalid string.  There are a few cases, however, where doing so is
 * unavoidable.  For example, "Kibyte" is a valid VOUNITS string (it
 * recognises 'byte', and permits binary prefixes), but we oughtn't to
 * write this in FITS as "Kibyte" (because FITS recognises
 * 'byte' but doesn't permit binary prefixes), nor as "1024 byte"
 * (because FITS doesn't permit prefixes which aren't a power of
 * ten).  The best thing to do in this case is possibly to write it
 * out as "Kibyte", which is a syntactically valid FITS string, even
 * though it refers to the unknown unit "Kibyte", rather than to 1024*byte.
 *
 * <p>Instances of this class are created by {@link SyntaxFactory},
 * and are used via {@link UnitExpr#toString}.
 */
abstract class UnitWriter {
    protected UnitExpr ue;

    private java.text.NumberFormat nf;

    UnitWriter(UnitExpr ue) {
        this.ue = ue;
        // This is horribly crude, but this appears to be the only
        // control we have over the number of significant figures
        // being output.  I hate java.text.NumberFormat.
        nf = new java.text.DecimalFormat("#.######",
                                         new java.text.DecimalFormatSymbols(java.util.Locale.US));
        // nf = java.text.NumberFormat.getInstance(java.util.Locale.US);
        // nf.setGroupingUsed(false);

        //nf.setMaximumFractionDigits(100);
        //df = new java.text.DecimalFormat("#.0E0");
    }

    /**
     * Write a number to a StringBuilder, avoiding any fractional parts if they're zero.
     * @param sb	receives the result
     * @param d		the number to be written
     * @param bracketsFloat 	true to force brackets to surround the number
     * @param bracketsNegative	true if brackets should go round the number if it is negative
     * @param bracketsPositive	true if brackets should go round the number if it is positive
     * @param formatLogAsSci	if true, then {@code d} is taken to be a base-10 log
     */
    protected void writeNumber(StringBuilder sb,
                               double d,
                               boolean bracketsFloat,
                               boolean bracketsNegative,
                               boolean bracketsPositive,
                               boolean formatLogAsSci) {
        String numToWrite;
        boolean isRound;

        if (formatLogAsSci) {
            // Java NumberFormat is so bloody hard to use that this
            // mess appears to be the only way to do this sanely.
            // double low = Math.floor(d);
            // double logmantissa = d - low;
            //sb.append(nf.format(Math.pow(10,logmantissa)));
            //sb.append('e');
            //sb.append(nf.format(low));
            sb.append(String.format("%g", Math.pow(10,d)));
        } else {
            if (d == Math.floor(d)) { // no fractional part
                numToWrite = nf.format(Math.round(d));
                isRound = true;
            } else {
                numToWrite = nf.format(d);
                //numToWrite = String.format("%g", d);
                isRound = false;
            }
            boolean withBrackets = (bracketsFloat && !isRound) || (bracketsNegative && d<0.0) || (bracketsPositive && d>=0.0);
            if (withBrackets) {
                sb.append('(').append(numToWrite).append(')');
            } else {
                sb.append(numToWrite);
            }
        }
        return;
    }

    /**
     * Set the locale to be used for formatting numbers.
     * @param newLocale the replacement locale
     */
    public void setLocale(java.util.Locale newLocale) {
        nf = java.text.NumberFormat.getInstance(newLocale);
    }

    abstract String write() throws UnwritableExpression;
}
