// Fmt - some simple single-arg sprintf-like routines
//
// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/

package Acme;

/// Some simple single-arg sprintf-like routines.
// <P>
// It is apparently impossible to declare a Java method that accepts
// variable numbers of any type of argument.  You can declare it to take
// Objects, but numeric variables and constants are not in fact Objects.
// <P>
// However, using the built-in string concatenation, it's almost as
// convenient to make a series of single-argument formatting routines.
// <P>
// Fmt can format the following types:
// <BLOCKQUOTE><CODE>
// byte short int long float double char String Object
// </CODE></BLOCKQUOTE>
// For each type there is a set of overloaded methods, each returning
// a formatted String.  There's the plain formatting version:
// <BLOCKQUOTE><PRE>
// Fmt.fmt( x )
// </PRE></BLOCKQUOTE>
// There's a version specifying a minimum field width:
// <BLOCKQUOTE><PRE>
// Fmt.fmt( x, minWidth )
// </PRE></BLOCKQUOTE>
// And there's a version that takes flags:
// <BLOCKQUOTE><PRE>
// Fmt.fmt( x, minWidth, flags )
// </PRE></BLOCKQUOTE>
// Currently available flags are:
// <BLOCKQUOTE><PRE>
// Fmt.ZF - zero-fill
// Fmt.LJ - left justify
// Fmt.HX - hexadecimal
// Fmt.OC - octal
// </PRE></BLOCKQUOTE>
// The HX and OC flags imply unsigned output.
// <P>
// For doubles and floats, there's a significant-figures parameter before
// the flags:
// <BLOCKQUOTE><PRE>
// Fmt.fmt( d )
// Fmt.fmt( d, minWidth )
// Fmt.fmt( d, minWidth, sigFigs )
// Fmt.fmt( d, minWidth, sigFigs, flags )
// </PRE></BLOCKQUOTE>
// <P>
// <A HREF="/resources/classes/Acme/Fmt.java">Fetch the software.</A><BR>
// <A HREF="/resources/classes/Acme.tar.Z">Fetch the entire Acme package.</A>
// <HR>
// Similar classes:
// <UL>
// <LI> Andrew Scherpbier's <A HREF="http://www.sdsu.edu/doc/java-SDSU/sdsu.FormatString.html">FormatString</A>
// Tries to allow variable numbers of arguments by
// supplying overloaded routines with different combinations of parameters,
// but doesn't actually supply that many.  The floating point conversion
// is described as "very incomplete".
// <LI> Core Java's <A HREF="http://www.apl.jhu.edu/~hall/java/CoreJava-Format.html">Format</A>.
// The design seems a little weird.  They want you to create an instance,
// passing the format string to the constructor, and then call an instance
// method with your data to do the actual formatting.  The extra steps are
// pointless; better to just use static methods.
// </UL>

public class Fmt
    {

    // Flags.
    /// Zero-fill.
    public static final int ZF = 1;
    /// Left justify.
    public static final int LJ = 2;
    /// Hexadecimal.
    public static final int HX = 4;
    /// Octal.
    public static final int OC = 8;
    // Was a number - internal use.
    private static final int WN = 16;

    // byte
    public static String fmt( byte b )
	{
	return fmt( b, 0, 0 );
	}
    public static String fmt( byte b, int minWidth )
	{
	return fmt( b, minWidth, 0 );
	}
    public static String fmt( byte b, int minWidth, int flags )
	{
	boolean hexadecimal = ( ( flags & HX ) != 0 );
	boolean octal = ( ( flags & OC ) != 0 );
	if ( hexadecimal )
	    return fmt( Integer.toString( b & 0xff, 16 ), minWidth, flags|WN );
	else if ( octal )
	    return fmt( Integer.toString( b & 0xff, 8 ), minWidth, flags|WN );
	else
	    return fmt( Integer.toString( b & 0xff ), minWidth, flags|WN );
	}

    // short
    public static String fmt( short s )
	{
	return fmt( s, 0, 0 );
	}
    public static String fmt( short s, int minWidth )
	{
	return fmt( s, minWidth, 0 );
	}
    public static String fmt( short s, int minWidth, int flags )
	{
	boolean hexadecimal = ( ( flags & HX ) != 0 );
	boolean octal = ( ( flags & OC ) != 0 );
	if ( hexadecimal )
	    return fmt(
		Integer.toString( s & 0xffff, 16 ), minWidth, flags|WN );
	else if ( octal )
	    return fmt(
		Integer.toString( s & 0xffff, 8 ), minWidth, flags|WN );
	else
	    return fmt( Integer.toString( s ), minWidth, flags|WN );
	}

    // int
    public static String fmt( int i )
	{
	return fmt( i, 0, 0 );
	}
    public static String fmt( int i, int minWidth )
	{
	return fmt( i, minWidth, 0 );
	}
    public static String fmt( int i, int minWidth, int flags )
	{
	boolean hexadecimal = ( ( flags & HX ) != 0 );
	boolean octal = ( ( flags & OC ) != 0 );
	if ( hexadecimal )
	    return fmt(
		Long.toString( i & 0xffffffffL, 16 ), minWidth, flags|WN );
	else if ( octal )
	    return fmt(
		Long.toString( i & 0xffffffffL, 8 ), minWidth, flags|WN );
	else
	    return fmt( Integer.toString( i ), minWidth, flags|WN );
	}

    // long
    public static String fmt( long l )
	{
	return fmt( l, 0, 0 );
	}
    public static String fmt( long l, int minWidth )
	{
	return fmt( l, minWidth, 0 );
	}
    public static String fmt( long l, int minWidth, int flags )
	{
	boolean hexadecimal = ( ( flags & HX ) != 0 );
	boolean octal = ( ( flags & OC ) != 0 );
	if ( hexadecimal )
	    {
	    if ( ( l & 0xf000000000000000L ) != 0 )
		return fmt(
		    Long.toString( l >>> 60, 16 ) +
		    fmt( l & 0x0fffffffffffffffL, 15, HX|ZF ),
		    minWidth, flags|WN );
	    else
		return fmt( Long.toString( l, 16 ), minWidth, flags|WN );
	    }
	else if ( octal )
	    {
	    if ( ( l & 0x8000000000000000L ) != 0 )
		return fmt(
		    Long.toString( l >>> 63, 8 ) +
		    fmt( l & 0x7fffffffffffffffL, 21, OC|ZF ),
		    minWidth, flags|WN );
	    else
		return fmt( Long.toString( l, 8 ), minWidth, flags|WN );
	    }
	else
	    return fmt( Long.toString( l ), minWidth, flags|WN );
	}

    // float
    public static String fmt( float f )
	{
	return fmt( f, 0, 0, 0 );
	}
    public static String fmt( float f, int minWidth )
	{
	return fmt( f, minWidth, 0, 0 );
	}
    public static String fmt( float f, int minWidth, int sigFigs )
	{
	return fmt( f, minWidth, sigFigs, 0 );
	}
    public static String fmt( float f, int minWidth, int sigFigs, int flags )
	{
	if ( sigFigs != 0 )
	    return fmt(
		sigFigFix( Float.toString( f ), sigFigs ), minWidth,
		flags|WN );
	else
	    return fmt( Float.toString( f ), minWidth, flags|WN );
	}

    // double
    public static String fmt( double d )
	{
	return fmt( d, 0, 0, 0 );
	}
    public static String fmt( double d, int minWidth )
	{
	return fmt( d, minWidth, 0, 0 );
	}
    public static String fmt( double d, int minWidth, int sigFigs )
	{
	return fmt( d, minWidth, sigFigs, 0 );
	}
    public static String fmt( double d, int minWidth, int sigFigs, int flags )
	{
	if ( sigFigs != 0 )
	    return fmt(
		sigFigFix( doubleToString( d ), sigFigs ), minWidth,
		flags|WN );
	else
	    return fmt( doubleToString( d ), minWidth, flags|WN );
	}

    // char
    public static String fmt( char c )
	{
	return fmt( c, 0, 0 );
	}
    public static String fmt( char c, int minWidth )
	{
	return fmt( c, minWidth, 0 );
	}
    public static String fmt( char c, int minWidth, int flags )
	{
	// return fmt( Character.toString( c ), minWidth, flags );
	// Character currently lacks a static toString method.  Workaround
	// is to make a temporary instance and use the instance toString.
	return fmt( new Character( c ).toString(), minWidth, flags );
	}

    // Object
    public static String fmt( Object o )
	{
	return fmt( o, 0, 0 );
	}
    public static String fmt( Object o, int minWidth )
	{
	return fmt( o, minWidth, 0 );
	}
    public static String fmt( Object o, int minWidth, int flags )
	{
	return fmt( o.toString(), minWidth, flags );
	}

    // String
    public static String fmt( String s )
	{
	return fmt( s, 0, 0 );
	}
    public static String fmt( String s, int minWidth )
	{
	return fmt( s, minWidth, 0 );
	}
    public static String fmt( String s, int minWidth, int flags )
	{
	int len = s.length();
	boolean zeroFill = ( ( flags & ZF ) != 0 );
	boolean leftJustify = ( ( flags & LJ ) != 0 );
	boolean hexadecimal = ( ( flags & HX ) != 0 );
	boolean octal = ( ( flags & OC ) != 0 );
	boolean wasNumber = ( ( flags & WN ) != 0 );
	if ( ( hexadecimal || octal || zeroFill ) && ! wasNumber )
	    throw new InternalError( "Acme.Fmt: number flag on a non-number" );
	if ( zeroFill && leftJustify )
	    throw new InternalError( "Acme.Fmt: zero-fill left-justify is silly" );
	if ( hexadecimal && octal )
	    throw new InternalError( "Acme.Fmt: can't do both hex and octal" );
	if ( len >= minWidth )
	    return s;
	int fillWidth = minWidth - len;
	StringBuffer fill = new StringBuffer( fillWidth );
	for ( int i = 0; i < fillWidth; ++i )
	    if ( zeroFill )
		fill.append( '0' );
	    else
		fill.append( ' ' );
	if ( leftJustify )
	    return s + fill;
	else if ( zeroFill && s.startsWith( "-" ) )
	    return "-" + fill + s.substring( 1 );
	else
	    return fill + s;
	}


    // Internal routines.

    private static String sigFigFix( String s, int sigFigs )
	{
	// First dissect the floating-point number string into sign,
	// integer part, fraction part, and exponent.
	String sign;
	String unsigned;
	if ( s.startsWith( "-" ) || s.startsWith( "+" ) )
	    {
	    sign = s.substring( 0, 1 );
	    unsigned = s.substring( 1 );
	    }
	else
	    {
	    sign = "";
	    unsigned = s;
	    }
	String mantissa;
	String exponent;
	int eInd = unsigned.indexOf( 'e' );
	if ( eInd == -1 )	// it may be 'e' or 'E'
	    eInd = unsigned.indexOf( 'E' );
	if ( eInd == -1 )
	    {
	    mantissa = unsigned;
	    exponent = "";
	    }
	else
	    {
	    mantissa = unsigned.substring( 0, eInd );
	    exponent = unsigned.substring( eInd );
	    }
	StringBuffer number, fraction;
	int dotInd = mantissa.indexOf( '.' );
	if ( dotInd == -1 )
	    {
	    number = new StringBuffer( mantissa );
	    fraction = new StringBuffer( "" );
	    }
	else
	    {
	    number = new StringBuffer( mantissa.substring( 0, dotInd ) );
	    fraction = new StringBuffer( mantissa.substring( dotInd + 1 ) );
	    }

	int numFigs = number.length();
	int fracFigs = fraction.length();
	if ( ( numFigs == 0 || number.equals( "0" ) ) && fracFigs > 0 )
	    {
	    // Don't count leading zeros in the fraction.
	    numFigs = 0;
	    for ( int i = 0; i < fraction.length(); ++i )
		{
		if ( fraction.charAt( i ) != '0' )
		    break;
		--fracFigs;
		}
	    }
	int mantFigs = numFigs + fracFigs;
	if ( sigFigs > mantFigs )
	    {
	    // We want more figures; just append zeros to the fraction.
	    for ( int i = mantFigs; i < sigFigs; ++i )
		fraction.append( '0' );
	    }
	else if ( sigFigs < mantFigs && sigFigs >= numFigs )
	    {
	    // Want fewer figures in the fraction; chop.
	    fraction.setLength(
		fraction.length() - ( fracFigs - ( sigFigs - numFigs ) ) );
	    // Round?
	    }
	else if ( sigFigs < numFigs )
	    {
	    // Want fewer figures in the number; turn them to zeros.
	    fraction.setLength( 0 );	// should already be zero, but make sure
	    for ( int i = sigFigs; i < numFigs; ++i )
		number.setCharAt( i, '0' );
	    // Round?
	    }
	// Else sigFigs == mantFigs, which is fine.

	if ( fraction.length() == 0 )
	    return sign + number + exponent;
	else
	    return sign + number + "." + fraction + exponent;
	}


    /// Improved version of Double.toString(), returns more decimal places.
    // <P>
    // The JDK 1.0.2 version of Double.toString() returns only six decimal
    // places on some systems.  In JDK 1.1 full precision is returned on
    // all platforms.
    // @deprecated
    // @see java.lang.Double#toString
    public static String doubleToString( double d )
	{
	// Handle special numbers first, to avoid complications.
	if ( Double.isNaN( d ) )
	    return "NaN";
	if ( d == Double.NEGATIVE_INFINITY )
	    return "-Inf";
	if ( d == Double.POSITIVE_INFINITY )
	    return "Inf";

	// Grab the sign, and then make the number positive for simplicity.
	boolean negative = false;
	if ( d < 0.0D )
	    {
	    negative = true;
	    d = -d;
	    }

	// Get the native version of the unsigned value, as a template.
	String unsStr = Double.toString( d );

	// Dissect out the exponent.
	String mantStr, expStr;
	int exp;
	int eInd = unsStr.indexOf( 'e' );
	if ( eInd == -1 )	// it may be 'e' or 'E'
	    eInd = unsStr.indexOf( 'E' );
	if ( eInd == -1 )
	    {
	    mantStr = unsStr;
	    expStr = "";
	    exp = 0;
	    }
	else
	    {
	    mantStr = unsStr.substring( 0, eInd );
	    expStr = unsStr.substring( eInd + 1 );
	    if ( expStr.startsWith( "+" ) )
		exp = Integer.parseInt( expStr.substring( 1 ) );
	    else
		exp = Integer.parseInt( expStr );
	    }

	// Dissect out the number part.
	String numStr;
	int dotInd = mantStr.indexOf( '.' );
	if ( dotInd == -1 )
	    numStr = mantStr;
	else
	    numStr = mantStr.substring( 0, dotInd );
	long num;
	if ( numStr.length() == 0 )
	    num = 0;
	else
	    num = Integer.parseInt( numStr );

	// Build the new mantissa.
	StringBuffer newMantBuf = new StringBuffer( numStr + "." );
	double p = Math.pow( 10, exp );
	double frac = d - num * p;
	String digits = "0123456789";
	int nDigits = 16 - numStr.length();	// about 16 digits in a double
	for ( int i = 0; i < nDigits; ++i )
	    {
	    p /= 10.0D;
	    int dig = (int) ( frac / p );
	    if ( dig < 0 ) dig = 0;
	    if ( dig > 9 ) dig = 9;
	    newMantBuf.append( digits.charAt( dig ) );
	    frac -= dig * p;
	    }

	if ( (int) ( frac / p + 0.5D ) == 1 )
	    {
	    // Round up.
	    boolean roundMore = true;
	    for ( int i = newMantBuf.length() - 1; i >= 0; --i )
		{
		int dig = digits.indexOf( newMantBuf.charAt( i ) );
		if ( dig == -1 )
		    continue;
		++dig;
		if ( dig == 10 )
		    {
		    newMantBuf.setCharAt( i, '0' );
		    continue;
		    }
		newMantBuf.setCharAt( i, digits.charAt( dig ) );
		roundMore = false;
		break;
		}
	    if ( roundMore )
		{
		// If this happens, we need to prepend a 1.  But I haven't
		// found a test case yet, so I'm leaving it out for now.
		// But if you get this message, please let me know!
		newMantBuf.append( "ROUNDMORE" );
		}
	    }

	// Chop any trailing zeros.
	int len = newMantBuf.length();
	while ( newMantBuf.charAt( len - 1 ) == '0' )
	    newMantBuf.setLength( --len );
	// And chop a trailing dot, if any.
	if ( newMantBuf.charAt( len - 1 ) == '.' )
	    newMantBuf.setLength( --len );

	// Done.
	return ( negative ? "-" : "" ) +
	       newMantBuf +
	       ( expStr.length() != 0 ? ( "e" + expStr ) : "" );
	}


/******************************************************************************
    /// Test program.
    public static void main( String[] args )
	{
	System.out.println( "Starting tests." );
	show( Fmt.fmt( "Hello there." ) );
	show( Fmt.fmt( 123 ) );
	show( Fmt.fmt( 123, 10 ) );
	show( Fmt.fmt( 123, 10, Fmt.ZF ) );
	show( Fmt.fmt( 123, 10, Fmt.LJ ) );
	show( Fmt.fmt( -123 ) );
	show( Fmt.fmt( -123, 10 ) );
	show( Fmt.fmt( -123, 10, Fmt.ZF ) );
	show( Fmt.fmt( -123, 10, Fmt.LJ ) );
	show( Fmt.fmt( (byte) 0xbe, 22, Fmt.OC ) );
	show( Fmt.fmt( (short) 0xbabe, 22, Fmt.OC ) );
	show( Fmt.fmt( 0xcafebabe, 22, Fmt.OC ) );
	show( Fmt.fmt( 0xdeadbeefcafebabeL, 22, Fmt.OC ) );
	show( Fmt.fmt( 0x8000000000000000L, 22, Fmt.OC ) );
	show( Fmt.fmt( (byte) 0xbe, 16, Fmt.HX ) );
	show( Fmt.fmt( (short) 0xbabe, 16, Fmt.HX ) );
	show( Fmt.fmt( 0xcafebabe, 16, Fmt.HX ) );
	show( Fmt.fmt( 0xdeadbeefcafebabeL, 16, Fmt.HX ) );
	show( Fmt.fmt( 0x8000000000000000L, 16, Fmt.HX ) );
	show( Fmt.fmt( 'c' ) );
	show( Fmt.fmt( new java.util.Date() ) );
	show( Fmt.fmt( 123.456F ) );
	show( Fmt.fmt( 123456000000000000.0F ) );
	show( Fmt.fmt( 123.456F, 0, 8 ) );
	show( Fmt.fmt( 123.456F, 0, 7 ) );
	show( Fmt.fmt( 123.456F, 0, 6 ) );
	show( Fmt.fmt( 123.456F, 0, 5 ) );
	show( Fmt.fmt( 123.456F, 0, 4 ) );
	show( Fmt.fmt( 123.456F, 0, 3 ) );
	show( Fmt.fmt( 123.456F, 0, 2 ) );
	show( Fmt.fmt( 123.456F, 0, 1 ) );
	show( Fmt.fmt( 123456000000000000.0F, 0, 4 ) );
	show( Fmt.fmt( -123.456F, 0, 4 ) );
	show( Fmt.fmt( -123456000000000000.0F, 0, 4 ) );
	show( Fmt.fmt( 123.0F ) );
	show( Fmt.fmt( 123.0D ) );
	show( Fmt.fmt( 1.234567890123456789F ) );
	show( Fmt.fmt( 1.234567890123456789D ) );
	show( Fmt.fmt( 1234567890123456789F ) );
	show( Fmt.fmt( 1234567890123456789D ) );
	show( Fmt.fmt( 0.000000000000000000001234567890123456789F ) );
	show( Fmt.fmt( 0.000000000000000000001234567890123456789D ) );
	show( Fmt.fmt( 12300.0F ) );
	show( Fmt.fmt( 12300.0D ) );
	show( Fmt.fmt( 123000.0F ) );
	show( Fmt.fmt( 123000.0D ) );
	show( Fmt.fmt( 1230000.0F ) );
	show( Fmt.fmt( 1230000.0D ) );
	show( Fmt.fmt( 12300000.0F ) );
	show( Fmt.fmt( 12300000.0D ) );
	show( Fmt.fmt( Float.NaN ) );
	show( Fmt.fmt( Float.POSITIVE_INFINITY ) );
	show( Fmt.fmt( Float.NEGATIVE_INFINITY ) );
	show( Fmt.fmt( Double.NaN ) );
	show( Fmt.fmt( Double.POSITIVE_INFINITY ) );
	show( Fmt.fmt( Double.NEGATIVE_INFINITY ) );
	show( Fmt.fmt( 1.0F / 8.0F ) );
	show( Fmt.fmt( 1.0D / 8.0D ) );
	System.out.println( "Done with tests." );
	}

    private static void show( String str )
	{
	System.out.println( "#" + str + "#" );
	}
******************************************************************************/

    }
