// F.java ///////////////////////////////////////////////////////////////////
//
// Based heavily on the Fmt class by Jef Poskanzer <jef@acme.com>
//
// Visit his ACME Labs Java page at http://www.acme.com/java/
//


package com.informagen;

// Doing  fprintf-like formatting in Java
// 
// 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.
// 
// However, using the built-in string concatenation, it's almost as
// convenient to make a series of single-argument formatting routines.
// 
// Class F can format the following datatypes:
// 
//     boolean, byte, char, short, int, long, float, double,
//     Boolean, Byte, Character, Short, Integer, Long, Float,
//     Date, Color, StringBuffer, String, and Object
//
// For each data type there is a set of overloaded methods, each returning
// a formatted String.  There's the plain formatting version:
//
//      F.f(x)
//
// 
// There's a version specifying a field width:
// 
//      F.f(x, width)
//
// 
// There's a version which takes formatting flags:
// 
//      F.f(x, flags)
//
//
// And there's a version that takes both:
// 
//      F.f(x, width, flags)
//
//
// Currently available flags are:
// 
//      F.RJ - right justify (the default)
//      F.LJ - left justify
//      F.CJ - center justify
//      F.ZF - zero fill
//      F.RF - repeat fill
//      F.HX - hexadecimal
//      F.OC - octal
//      F.BN - binary
//      F.LC - lowercase
//      F.UC - uppercase
//      F.XC - OK to exceed field width 
//      F.TR - truncate string to width 
// 
// Right justify is the default for all formatting.
//
// Center justify must be used with a width and will not work with
//   zero or repeat fill or left justify. In these cases it is ignored.
//
// The HX, OC and BN flags produce unsigned output. Hexidecimal representation
//  have an "0x" prefix and octal representation have a "0" prefix.
//
// Repeat fill is useful for making string of the same character
//
// Uppercase and lowecase can be used on numbers which contain letter ie Hexadecimal
//   or exponential.
//
// Use XC when the field width isn't equal to zero ie "DEFAULT" but you don't might if
//  the value exceed its field boundaries.
//
// Use TR with care with numeric fields as the output could be misinterpreted. XC will
//  override TR.
//
//
// For formatting dates there are a set of predefined format strings for convienence.
//   See the documentation for the class "java.text.SimpleDateFormat" for an extensive
//   description of the format used in date templates.
//
//	    F.SYBASEDATE = "MMM dd yyyy hh:mm:ss:SSSaa"  Sybase datetime stamp
//	    F.DATE       = "dd-MMM-yyyy"                 Three character month
//	    F.TIME       = "hh:mmaa"                     AM/PM 12 hour time
//	    F.MILTIME    = "HHmm"                        24 hour time ie Military
//	    F.MONTH      = "MMMM"                        Full month name
//	    F.WEEKDAY    = "EEEE"                        Full weekday name
//
//
// For real numbers, ie doubles and floats, there's a significant-figures parameter.
// 
//      F.f(d, width, decimalDigits)
//
//
// If the value of decimalDigits is negative the value will be treated as the number of
//  significant figures to be printed. Significant figures are used in scientific calculations
//  in order to express intermediate and final calculation with the proper significance
//  from the measured values.
//
// 


public class F {

    // Flags ----------------------------------------------------------------------
    
	public static final int DEFAULT = Integer.MAX_VALUE;	// Use the default width/sigfigs

	public static final short RJ =    1;	// Right justify
	public static final short LJ =    2;	// Left justify
	public static final short CJ =    4;	// Center justify
	
	public static final short ZF =    8;	// Zero-fill
	public static final short RF =   16;	// Repeat-fill
	
	public static final short HX =   32;	// Hexadecimal
	public static final short OC =   64;	// Octal
	public static final short BN =  128;	// Binary
	
	public static final short UC =  256;	// Uppercase
	public static final short LC =  512;	// Lowercase
	
	public static final short XC = 1024;	// OK to exceed field width
	public static final short TR = 2048;	// Truncate string to width


	
	// Convienent date/time templates --------------------------------------------
	// Re: "java.text.SimpleDateFormat" for a guide to using the symbols
	
	public static final String SYBASEDATE = "MMM dd yyyy hh:mm:ss:SSSaa";	// Sybase datetime stamp
	public static final String DATE       = "dd-MMM-yyyy";					// Three character month
	public static final String TIME       = "hh:mmaa";						// AM/PM 12 hour time
	public static final String MILTIME    = "HHmm";							// 24 hour time ie Military
	public static final String MONTH      = "MMMM";							// Full month name
	public static final String WEEKDAY    = "EEEE";							// Full weekday name
	
 
    // boolean --------------------------------------------------------------------
    
	public static String f(boolean b) { return f(b, DEFAULT, RJ); }
	public static String f(boolean b, short flags) { return f(b, DEFAULT, flags); }
	public static String f(boolean b, int width) { return f(b, width, RJ); }
	
	public static String f(boolean b, int width, short flags) {

		if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
			return f((b) ? (byte)1 : (byte)0, width, flags);
		else	
			return f((b) ? "true" : "false", width, flags);
	}


    // byte -----------------------------------------------------------------------

	public static String f(byte b) { return f(b, DEFAULT, RJ); }
	public static String f(byte b, short flags) { return f(b, DEFAULT, flags); }
	public static String f(byte b, int width) { return f(b, width, RJ); }
	
	public static String f(byte b, int width, short flags) {
	
		if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
			return f((b&0xff), width, flags);
		else
			return f(Integer.toString(b&0xff), width, flags);
	}


    // char -----------------------------------------------------------------------

	public static String f(char c) { return f(c, DEFAULT, RJ); }
	public static String f(char c, short flags) { return f(c, DEFAULT, flags); }
	public static String f(char c, int width) { return f(c, width, RJ); }
	
	public static String f(char c, int width, short flags) {
	
		boolean hexadecimal = ((flags & HX) != 0);
		boolean octal       = ((flags & OC) != 0);
		boolean binary      = ((flags & BN) != 0);
		
		if (hexadecimal)
			return f("0x" + f(Integer.toHexString(c & 0xffff), 4, ZF), width, flags);
		else if (octal | binary)
			return f((short)c, width, flags);
		else
			return f(new Character(c).toString(), width, flags);
	}



    // short -----------------------------------------------------------------------

	public static String f(short s) {return f(s, DEFAULT, RJ); }
	public static String f(short s, short flags) {return f(s, DEFAULT, flags); }
	public static String f(short s, int width) { return f(s, width, RJ); }

	public static String f(short s, int width, short flags) {
	
		if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
			return f((s & 0xffff), width, flags);
		else
			return f(Integer.toString(s), width, flags);
	}



    // int -----------------------------------------------------------------------

	public static String f(int i) { return f(i, DEFAULT, RJ); }
	public static String f(int i, short flags) { return f(i, DEFAULT, flags); }
	public static String f(int i, int width) { return f(i, width, RJ); }
	
	public static String f(int i, int width, short flags) {
					
		if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
			return f((i & 0xffffffffL), width, flags );
		else
			return f(Integer.toString(i), width, flags);
	}


    // long -----------------------------------------------------------------------

	public static String f(long l) { return f(l, DEFAULT, RJ); }
	public static String f(long l, short flags) { return f(l, DEFAULT, flags); }
	public static String f(long l, int width) { return f(l, width, RJ ); }
	
	public static String f(long l, int width, short flags) {
	
		boolean hexadecimal = ((flags & HX) != 0 );
		boolean octal = ((flags & OC) != 0 );
		boolean binary = ((flags & BN) != 0 );
		
		if (hexadecimal)
			return f("0x" + Long.toHexString(l), width, flags);
		else if (octal)
			return f("0" + Long.toOctalString(l), width, flags);
		else if (binary)
			return f(Long.toBinaryString(l), width, flags);
		else
			return f(Long.toString(l), width, flags);
	}



    // Float and float -----------------------------------------------------------------------
    
	public static String f(Float f, int width, int decimalDigits, short flags) {
		return ( f == null ) ? f("null", width, flags) : f(f.floatValue(), width, decimalDigits, flags); 
	}

	public static String f(float f) { return f(f, DEFAULT, DEFAULT, RJ); }
	public static String f(float f, short flags) { return f(f, DEFAULT, DEFAULT, flags); }
	public static String f(float f, int width) { return f(f, width, DEFAULT, RJ); }
	public static String f(float f, int width, short flags) { return f(f, width, DEFAULT, flags); }
	public static String f(float f, int width, int decimalDigits) { return f(f, width, decimalDigits, RJ); }
	
	public static String f(float f, int width, int decimalDigits, short flags) {
	

		if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
			return f(Float.floatToIntBits(f), width, flags);


		if ( decimalDigits == DEFAULT )
			return f(Float.toString( f ), width, (short)flags);
		else
			return f(sigFig(Float.toString(f), decimalDigits), width, flags);
	}



    // Double and double -----------------------------------------------------------------------

	public static String f(Double d, int width, int decimalDigits, short flags) { 
		return ( d == null ) ? f("null", width, flags) : f(d.doubleValue(), width, decimalDigits, flags); 
	}

	public static String f(double d) { return f(d, DEFAULT, DEFAULT, RJ); }
	public static String f(double d, short flags) { return f(d, DEFAULT, DEFAULT, flags); }
	public static String f(double d, int width) { return f(d, width, DEFAULT, RJ); }
	public static String f(double d, int width, short flags) { return f(d, width, DEFAULT, flags); }
	public static String f(double d, int width, int decimalDigits) { return f(d, width, decimalDigits, RJ); }
	
	public static String f(double d, int width, int decimalDigits, short flags) {
	
		if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
			return f(Double.doubleToLongBits(d), width, flags);

		if ( decimalDigits == DEFAULT )
			return f(Double.toString(d), width, flags);
		else
			return f(sigFig(Double.toString(d), decimalDigits), width, flags );
	}



    // Date -----------------------------------------------------------------------
    
	public static String f(java.util.Date d, String template) { return f(d, template, RJ); }
	public static String f(java.util.Date d, String template, int width) { return f(d, template, width, RJ); }
	public static String f(java.util.Date d, String template, short flags) { return f(d, template, DEFAULT, flags); }
	
	public static String f(java.util.Date d, String template, int width, short flags) {
		if ( d == null )
			return f("null", width, flags);
		else {
			java.text.SimpleDateFormat dateFormatter = new java.text.SimpleDateFormat(template);
			return f(dateFormatter.format(d), width, flags);
		}
	}
	
	
	
    // Color -----------------------------------------------------------------------
	
	public static String f(java.awt.Color c, int width, short flags) {
	
		if ( c == null)
			return f("null", width, flags);
		else {	
			short binHexFlags = (short)((flags & HX) | (flags & OC) | (flags & BN));
				
			return f(f(c.getRed(), DEFAULT, binHexFlags) + "," + 
			         f(c.getGreen(), DEFAULT, binHexFlags)+ ","  + 
			         f(c.getBlue(), DEFAULT, binHexFlags), width, flags);
		}
		
	}
	


    // Object -----------------------------------------------------------------------
    
	public static String f(Object o) { return f(o, DEFAULT, RJ); }
	public static String f(Object o, int width) { return f(o, width, RJ); }
	public static String f(Object o, short flags) { return f(o, DEFAULT, flags); }
	
	public static String f(Object o, int width, short flags) {
	
		if ( o == null )
			return f("null", width, flags); 
		else {

			if (o instanceof java.lang.String)
				return f((String)o, width, flags);
			else if (o instanceof java.lang.Boolean)
				return f(((Boolean)o).booleanValue(), width, flags);
			else if (o instanceof java.lang.Byte)
				return f(((Byte)o).byteValue(), width, flags);
			else if (o instanceof java.lang.Character)
				return f(((Character)o).charValue(), width, flags);
			else if (o instanceof java.lang.Short)
				return f(((Short)o).shortValue(), width, flags);
			else if (o instanceof java.lang.Integer)
				return f(((Integer)o).intValue(), width, flags);
			else if (o instanceof java.lang.Long)
				return f(((Long)o).longValue(), width, flags);
			else if (o instanceof java.lang.Float)
				return f(((Float)o).floatValue(), width, flags);
			else if (o instanceof java.lang.Double)
				return f(((Double)o).doubleValue(), width, flags);
			else if (o instanceof java.lang.StringBuffer)
				return f((java.lang.StringBuffer)o, width, flags);
			else if (o instanceof java.awt.Color)
				return f((java.awt.Color)o, width, flags);
			else
				return f(o.toString(), width, flags);
		} 
	}

    // StringBuffer ------------------------------------------------------------------
    	
	public static String f(StringBuffer sb, int width, short flags) {
		return ( sb == null) ? f("null", width, flags) : f(sb.toString(), width, flags);
	}

    // String ------------------------------------------------------------------------
    //
	//  String overloaded methods could be dispatched via the Object however
	//  as they are heavily used they are explicitly declared.

		
	public static String f(String s, int width, short flags) {

		width = (width < 0) ? -width : width;

		if ( s == null )
			return f("null", width, flags);


		int len = s.length();
		
		boolean zeroFill      = ((flags & ZF) != 0);
		boolean repeatFill    = ((flags & RF) != 0);
		
		boolean rightJustify  = ((flags & RJ) != 0);
		boolean leftJustify   = ((flags & LJ) != 0);
		boolean centerJustify = ((flags & CJ) != 0);
		
		boolean upperCase     = ((flags & UC) != 0);
		boolean lowerCase     = ((flags & LC) != 0);
		
		boolean okToExceed    = ((flags & XC) != 0);
		boolean truncate      = ((flags & TR) != 0);
		

		// Width is zero and we are not allowed to exceed it
				
		if ( width == 0 && !okToExceed )
			return "";


		// Width is not zero but the string is too big and we are not allowed to 
		//   exceed the width or truncate the output, print a string of asterisks instead.

		if ( (width != DEFAULT) && (width < len) && (okToExceed == false) && (truncate == false) )
			return f("*", width, RF);
						
		
		// No special formatting to be done, this is the usual case so it
		//  is worthwhile taking the time to test here and just returning.		

		if ( (width <= len || width == DEFAULT) &&
			!zeroFill && 
			!repeatFill && 
			!centerJustify && 
			!upperCase && 
			!lowerCase &&
			!truncate)
				return s;
			
			
		// Determine reasonable behavior from conflicting flags
			
		if ( rightJustify && (leftJustify || centerJustify) )
			rightJustify = false;

		if ( centerJustify && (repeatFill || zeroFill || leftJustify) )
			centerJustify = false;

		if ( repeatFill && zeroFill )
			repeatFill = false;

		if ( truncate && (okToExceed || (width == DEFAULT)) )
			truncate = false;


		// Zero and repeat fill, leading spaces
		
		int fillWidth = 0;
		
		if ( width != DEFAULT )
			fillWidth = (repeatFill) ? width : width - len;
		
		fillWidth = (fillWidth < 0) ? 0 : fillWidth;
		
		StringBuffer buffer = new StringBuffer(fillWidth + len);
		
		int i=0;
		while ( i<fillWidth ) {
			if ( zeroFill )
				buffer.append('0');
			else if ( repeatFill )
				buffer.append(s.charAt(i%len));
			else
				buffer.append(' ');
			i++;
		}

		String fill = buffer.toString();


		// Assemble the final string using the fill string created above
		
		buffer.setLength(0);
		
		if ( leftJustify ) {
			  buffer.append(s).append(fill);
		} else if ( centerJustify ) {
			  buffer.append(fill.substring(0,fillWidth/2)).append(s).append(fill.substring(fillWidth/2));
		} else if ( zeroFill && s.startsWith( "-" ) )
			buffer.append("-").append(fill).append(s.substring(1));
		else if ( repeatFill )
			buffer.append(fill);
		else
			buffer.append(fill).append(s);


		// Truncate
		
		if ( truncate && width != DEFAULT )
			buffer.setLength(width);


		// Deal with the case flags

		if ( upperCase )
			return buffer.toString().toUpperCase();
		else if ( lowerCase )
			return buffer.toString().toLowerCase();
		else
			return buffer.toString();


	}


	//-------------------------------------------------------------------------------------------
    // Internal routine, sigFig. 


	private static String sigFig(String s, int decimalDigits) {
	
		// First dissect the floating-point number string into optional sign,
		// integer part, fraction part, and optional exponent.

		// Sign - may be '-' or '+' or nothing

		String sign;
		String unsigned;

		if ( s.startsWith( "-" ) || s.startsWith( "+" )) {
			sign = s.substring(0, 1);
			unsigned = s.substring(1);
		} else {
			sign = "";
			unsigned = s;
		}
	    
	    
		
		// Exponent -  may be 'e' or 'E' format
		
		String mantissa;
		String exponent;
		
		int eIndex = unsigned.indexOf('e');
		
		if ( eIndex == -1 )	
			eIndex = unsigned.indexOf('E');
			
		if ( eIndex == -1) {
			mantissa = unsigned;
			exponent = "";
		} else {
			mantissa = unsigned.substring(0, eIndex);
			exponent = unsigned.substring(eIndex);
		}
		

		// Number and fraction
		
		StringBuffer number, fraction;
		
		int dotIndex = mantissa.indexOf('.');
		
		if ( dotIndex == -1) {
			number = new StringBuffer(mantissa);
			fraction = new StringBuffer("");
		} else {
			number = new StringBuffer(mantissa.substring(0, dotIndex));
			fraction = new StringBuffer(mantissa.substring(dotIndex + 1));
		}
		

		int numFigs = number.length();
		int fracFigs = fraction.length();
		
		// Don't count leading zeros in the fraction.
		if ((numFigs == 0 || number.equals("0")) && fracFigs > 0) {
		
			numFigs = 0;
			
			for ( int i = 0; i < fraction.length(); ++i) {
				if ( fraction.charAt( i ) != '0' )
					break;
				--fracFigs;
			}
		}
		
		
		int mantFigs = numFigs + fracFigs;

		// if decimalDigits is positive return significant figures otherwise treat
		//  decimalDigits a precision.


		if ( decimalDigits < 0 || decimalDigits == DEFAULT) {
		
			int sigFigs = -decimalDigits;
			
			// Special case!!!
			if ( decimalDigits == DEFAULT)
				sigFigs = 0;

		
			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 ) ) );

			} else if ( sigFigs < numFigs) {								// Want fewer figures in the number; turn them to zeros.
			
				fraction.setLength(0);
				for ( int i = sigFigs; i < numFigs; ++i )
					number.setCharAt( i, '0' );
			}


			if ( fraction.length() == 0 )
				return sign + number + exponent;
			else
				return sign + number + "." + fraction + exponent;
				
		} else {
		
			// Treat sigFig as a measure of precision
		
			if ( exponent.length()>0 && exponent.charAt(1) == '-' ) {
			
				number.append(fraction);
				fraction.setLength(0);
				fraction.append(f("0", Integer.parseInt(exponent.substring(2))-1, RF)).append(number);

				fracFigs = fraction.length();
				exponent = "";
				number.setLength(0);
				number.append("0");
			}	
		
		
			for (int i = fracFigs; i < decimalDigits; ++i )
				fraction.append('0');
				
			fraction.setLength(decimalDigits);
		
			return  sign + number + "." + fraction + exponent;
		}
		
		
	}



}