// ============================================================================
// File:               $File$
//
// Project:
//
// Purpose:
//
// Author:             Rammi
//
// Copyright Notice:   (c) 1999-2006  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date: 2006-02-01 13:48:49 +0100 (Mi, 01 Feb 2006) $
//
// History:	       $Log$
//=============================================================================


package de.caff.util.debug;



/**
 *  Simple class for parsing format strings similar to the C printf functionality.
 *
 *  The places in the format strings where the arguments will be inserted are 
 *  marked with <code>%1</code> to <code>%9</code> thus allowing reordering.
 *  Arguments must be objects, primitive types are not allowed. If needed,
 *  primitive bytes must be boxed by their standard wrappers.
 *
 *  @author Rammi
 */
public class Format 
{
  /** Null string returned for null elements. */
  private static final String NULL_STRING = "<null>";

  /**
   *  Parses a format string with arguments marked by <tt>%1</tt> to <tt>%9</tt>
   *  (max.) and substitutes in the arguments accordingly.
   *  @param  mask    format string
   *  @param  args    further arguments (minimal length needed is as much as the
   *                  highest digit used in the markers)
   *  @return formatted string
   */
  public static String format(Object mask, Object ... args) {
    String format = mask.toString();
    StringBuffer compiled = new StringBuffer();
    int length = format.length();
    boolean nextIsDigit = false;

    for (int index = 0;   index < length;   ++index) {
      char c = format.charAt(index);
      if (nextIsDigit) {
	nextIsDigit = false;
	if ('1' <= c   &&  c <= '9') {
	  // "%[1-9]" will be substituted by the according argument
	  int arg = c-'1';
	  if (arg >= args.length) {
	    compiled.append("<%").append(c).append(" missing>");
	  }
	  else {
	    compiled.append(toString(args[arg]));
	  }
	}
	else if (c == '%') {
	  // "%%" will become "%"
	  compiled.append(c);
	}
	else {
	  // "%[^1-9]" will be kept
	  compiled.append('%').append(c);
	}
      }
      else {
	if (c == '%') {
	  // next character needs special treatment
	  nextIsDigit = true;
	}
	else {
	  // normal char: just append
	  compiled.append(c);
	}
      }
    }

    return compiled.toString();
  }

  
  /**
   *  This is a convenience method same as {@link #format(Object,Object[]) format}
   *  for the case where you need one argument.
   *  @param  format  format string
   *  @param  arg1    fitted for <tt>%1</tt> in the format string
   *  @return formatted string
   */
  public static String format(Object format, Object arg1) 
  {
    return format(format, new Object[] { arg1 });
  }

  /**
   *  This is a convenience method same as {@link #format(Object,Object[]) format}
   *  for the case where you need two arguments.
   *  @param  format  format string
   *  @param  arg1    fitted for <tt>%1</tt> in the format string
   *  @param  arg2    fitted for <tt>%2</tt> in the format string
   *  @return formatted string
   */
  public static String format(Object format, Object arg1, Object arg2) 
  {
    return format(format, new Object[] { arg1, arg2 });
  }

  /**
   *  This is a convenience method same as {@link #format(Object,Object[]) format}
   *  for the case where you need three arguments.
   *  @param  format  format string
   *  @param  arg1    fitted for <tt>%1</tt> in the format string
   *  @param  arg2    fitted for <tt>%2</tt> in the format string
   *  @param  arg3    fitted for <tt>%3</tt> in the format string
   *  @return formatted string
   */
  public static String format(Object format, Object arg1, Object arg2, Object arg3) 
  {
    return format(format, new Object[] { arg1, arg2, arg3 });
  }

  /**
   *  This is a convenience method same as {@link #format(Object,Object[]) format}
   *  for the case where you need four arguments.
   *  @param  format  format string
   *  @param  arg1    fitted for <tt>%1</tt> in the format string
   *  @param  arg2    fitted for <tt>%2</tt> in the format string
   *  @param  arg3    fitted for <tt>%3</tt> in the format string
   *  @param  arg4    fitted for <tt>%4</tt> in the format string
   *  @return formatted string
   */
  public static String format(Object format, Object arg1, Object arg2, Object arg3,
			      Object arg4) 
  {
    return format(format, new Object[] { arg1, arg2, arg3, arg4 });
  }

  /**
   *  This is a convenience method same as {@link #format(Object,Object[]) format}
   *  for the case where you need five arguments.
   *  @param  format  format string
   *  @param  arg1    fitted for <tt>%1</tt> in the format string
   *  @param  arg2    fitted for <tt>%2</tt> in the format string
   *  @param  arg3    fitted for <tt>%3</tt> in the format string
   *  @param  arg4    fitted for <tt>%4</tt> in the format string
   *  @param  arg5    fitted for <tt>%5</tt> in the format string
   *  @return formatted string
   */
  public static String format(Object format, Object arg1, Object arg2, Object arg3,
			      Object arg4, Object arg5) 
  {
    return format(format, new Object[] { arg1, arg2, arg3, arg4, arg5 });
  }

  /**
   *  This is a convenience method same as {@link #format(Object,Object[]) format}
   *  for the case where you need six arguments.
   *  @param  format  format string
   *  @param  arg1    fitted for <tt>%1</tt> in the format string
   *  @param  arg2    fitted for <tt>%2</tt> in the format string
   *  @param  arg3    fitted for <tt>%3</tt> in the format string
   *  @param  arg4    fitted for <tt>%4</tt> in the format string
   *  @param  arg5    fitted for <tt>%5</tt> in the format string
   *  @param  arg6    fitted for <tt>%6</tt> in the format string
   *  @return formatted string
   */
  public static String format(Object format, Object arg1, Object arg2, Object arg3,
			      Object arg4, Object arg5, Object arg6) 
  {
    return format(format, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6 });
  }

  /**
   *  This is a convenience method same as {@link #format(Object,Object[]) format}
   *  for the case where you need seven arguments.
   *  @param  format  format string
   *  @param  arg1    fitted for <tt>%1</tt> in the format string
   *  @param  arg2    fitted for <tt>%2</tt> in the format string
   *  @param  arg3    fitted for <tt>%3</tt> in the format string
   *  @param  arg4    fitted for <tt>%4</tt> in the format string
   *  @param  arg5    fitted for <tt>%5</tt> in the format string
   *  @param  arg6    fitted for <tt>%6</tt> in the format string
   *  @param  arg7    fitted for <tt>%7</tt> in the format string
   *  @return formatted string
   */
  public static String format(Object format, Object arg1, Object arg2, Object arg3,
			      Object arg4, Object arg5, Object arg6,
			      Object arg7) 
  {
    return format(format, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 });
  }

  /**
   *  This is a convenience method same as {@link #format(Object,Object[]) format}
   *  for the case where you need eight arguments.
   *  @param  format  format string
   *  @param  arg1    fitted for <tt>%1</tt> in the format string
   *  @param  arg2    fitted for <tt>%2</tt> in the format string
   *  @param  arg3    fitted for <tt>%3</tt> in the format string
   *  @param  arg4    fitted for <tt>%4</tt> in the format string
   *  @param  arg5    fitted for <tt>%5</tt> in the format string
   *  @param  arg6    fitted for <tt>%6</tt> in the format string
   *  @param  arg7    fitted for <tt>%7</tt> in the format string
   *  @param  arg8    fitted for <tt>%8</tt> in the format string
   *  @return formatted string
   */
  public static String format(Object format, Object arg1, Object arg2, Object arg3,
			      Object arg4, Object arg5, Object arg6,
			      Object arg7, Object arg8) 
  {
    return format(format, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 });
  }

  /**
   *  This is a convenience method same as {@link #format(Object,Object[]) format}
   *  for the case where you need all nine arguments.
   *  @param  format  format string
   *  @param  arg1    fitted for <tt>%1</tt> in the format string
   *  @param  arg2    fitted for <tt>%2</tt> in the format string
   *  @param  arg3    fitted for <tt>%3</tt> in the format string
   *  @param  arg4    fitted for <tt>%4</tt> in the format string
   *  @param  arg5    fitted for <tt>%5</tt> in the format string
   *  @param  arg6    fitted for <tt>%6</tt> in the format string
   *  @param  arg7    fitted for <tt>%7</tt> in the format string
   *  @param  arg8    fitted for <tt>%8</tt> in the format string
   *  @param  arg9    fitted for <tt>%9</tt> in the format string
   *  @return formatted string
   */
  public static String format(Object format, Object arg1, Object arg2, Object arg3,
			      Object arg4, Object arg5, Object arg6,
			      Object arg7, Object arg8, Object arg9) 
  {
    return format(format, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 });
  }

  /**
   *  Make a string from objects. 
   *  <p>
   *  Handles arrays specially.
   *  
   *  @param  obj  object
   *  @return string representation
   */
  public static String toString(Object obj) {
    return toString(obj, "{", ",", "}", "");
  }

  /**
   *  Return a string for object.
   *  Expand arrays by using the given strings a s delimiters.
   *  @param  obj    object (possibly an array)
   *  @param  pre    prefix before outputting an array
   *  @param  inter  intermediate string between array elements
   *  @param  post   postfix after outputting an array
   *  @param  indent indent string recursively used as a prefix everywhere
   *  @return String description of object
   */
  private static String toString(Object obj, String pre, String inter, String post, String indent) {
    StringBuffer buffer = new StringBuffer();

    if (obj == null) {
      return NULL_STRING;
    }
    else {
      Class cl = obj.getClass();

      if (cl.isArray()) {
	buffer.append(pre);
	if (cl.getComponentType().isPrimitive()) {
	  Class prim = cl.getComponentType();

	  if (prim == Integer.TYPE) {
	    int[] arr = (int[])obj;

	    for (int a = 0;  a < arr.length;   ++a) {
	      buffer.append(arr[a]);
	      if (a != arr.length-1) {
		buffer.append(inter);
	      }
	    }
	  }
	  else if (prim == Short.TYPE) {
	    short[] arr = (short[])obj;

	    for (int a = 0;  a < arr.length;   ++a) {
	      buffer.append(arr[a]);
	      if (a != arr.length-1) {
		buffer.append(inter);
	      }
	    }
	  }
	  else if (prim == Byte.TYPE) {
	    byte[] arr = (byte[])obj;

	    for (int a = 0;  a < arr.length;   ++a) {
	      buffer.append(arr[a]);
	      if (a != arr.length-1) {
		buffer.append(inter);
	      }
	    }
	  }
	  else if (prim == Long.TYPE) {
	    long[] arr = (long[])obj;

	    for (int a = 0;  a < arr.length;   ++a) {
	      buffer.append(arr[a]);
	      if (a != arr.length-1) {
		buffer.append(inter);
	      }
	    }
	  }
	  else if (prim == Character.TYPE) {
	    char[] arr = (char[])obj;

	    for (int a = 0;  a < arr.length;   ++a) {
	      buffer.append(arr[a]);
	      if (a != arr.length-1) {
		buffer.append(inter);
	      }
	    }
	  }
	  else if (prim == Boolean.TYPE) {
	    boolean[] arr = (boolean[])obj;

	    for (int a = 0;  a < arr.length;   ++a) {
	      buffer.append(arr[a]);
	      if (a != arr.length-1) {
		buffer.append(inter);
	      }
	    }
	  }
	  else if (prim == Float.TYPE) {
	    float[] arr = (float[])obj;

	    for (int a = 0;  a < arr.length;   ++a) {
	      buffer.append(arr[a]);
	      if (a != arr.length-1) {
		buffer.append(inter);
	      }
	    }
	  }
	  else if (prim == Double.TYPE) {
	    double[] arr = (double[])obj;

	    for (int a = 0;  a < arr.length;   ++a) {
	      buffer.append(arr[a]);
	      if (a != arr.length-1) {
		buffer.append(inter);
	      }
	    }
	  }
	}
	else {
	  Object[] arr = (Object[])obj;
	  for (int a = 0;  a < arr.length;   ++a) {
	    buffer.append(toString(arr[a], indent+pre, indent+inter, indent+post, indent));

	    if (a != arr.length-1) {
	      buffer.append(inter);
	    }
	  }
	}
	buffer.append(post);
	return buffer.toString();
      }
      else {
	return obj.toString();
      }
    }
  }

  /**
   *  Get the length of the longest string in a set of strings.
   *  @param strings the set of strings as an array
   *  @return the maximum length
   */
  public static int getMaxStringLength(String [] strings)
  {
    int len = 0;
    for (int i = 0; i< strings.length;++i)
      if (strings[i].length() > len)
	len = strings[i].length();

    return len;
  }

  /**
   *  Right pads the given string with the fill until <code>length</code>
   *  is reached. 
   *  @param str the string to be right padded
   *  @param length the length when the padding stops
   *  @param fill the string with which <code>str</code> is padded
   *  @return the padded string cut to <code>length</code> chars
   */
  public static String rpad(String str,
			    int length,
			    String fill)
  {
    if (fill.length() > 0)
      while(str.length() < length)
	str += fill;
    
    if(str.length() > length) {
      str = str.substring(0,length);
    }
    return str;
  }

  /**
   *  Left pads the given string with the fill until <code>length</code>
   *  characters are reached. 
   *  @param str the string to be left padded
   *  @param length the length when the padding stops
   *  @param fill the string with which <code>str</code> is padded
   *  @return the padded string cut to <code>length</code> chars
   */
  public static String lpad(String str,
			    int length,
			    String fill)
  {
    if (fill.length() > 0)
      while(str.length() < length)
	str = fill + str;
    
    if(str.length() > length) {
      str = str.substring(str.length() - length, length + 1);
    }
    return str;
  }

  /**
   *  Produce a string where <code>str</code> is centered. 
   *  <code>str</code> is left padded with fill. 
   *  @param str the string to be centered
   *  @param length the length where <code>str</code> is centered
   *  @param fill the string with which <code>str</code> is padded
   *  @return the centered string cut to <code>length</code> chars
   */
  public static String center(String str,
			      int length,
			      String fill)
  {
    int leftMargin = (length - str.length()) / 2;
    return lpad(str, str.length() + leftMargin, fill);
  }

  /**
   *  Replaces all occurencies of <code>str2</code> in <code>str1</code> by <code>str3</code>
   *  @param str1  in this string substrings will be replaced, must be != null
   *  @param str2  string to be replaced, must be != null and != ""
   *  @param str3  string to replace, must be != null
   *  @return the resulting string
   */
  public static String replace(String str1, String str2, String str3)
  {
    String result = "";

    if (str1 != null && str2 != null && str3 != null && str2 != "") {
      String[] pieces = new String[str1.length()];
      int i = 0;
      while (str1.indexOf(str2) >= 0) {
	pieces[i++] = str1.substring(0, str1.indexOf(str2));
	str1 = str1.substring (str1.indexOf(str2) + str2.length());
      }

      for (int j=0;j<i;++j)
	result += pieces[j] + str3;

      result += str1;
    }

    return result;
  }

  /**
   *  Trims the left side of a string. I.e. takes away all white space and
   *  control characters from the left.
   *  @param str  string to trim
   *  @return trimmed string
   */
  public static String trimLeft(String str)
  {
    if (str == null) {
      return null;
    }
    int length = str.length();
    int pos    = 0;
    
    while (pos < length) {
      char ch = str.charAt(pos);
      if (!Character.isWhitespace(ch)  &&  !Character.isISOControl(ch)) {
	break;
      }
      ++pos;
    }

    return str.substring(pos, length);
  }



  /**
   *  Trims the right side of a string. I.e. takes away all white space and
   *  control characters from the right.
   *  @param str  string to trim
   *  @return trimmed string
   */
  public static String trimRight(String str)
  {
    if (str == null) {
      return null;
    }
    int length = str.length();
    int pos    = length-1;
    
    while (pos >= 0) {
      char ch = str.charAt(pos);
      if (!Character.isWhitespace(ch)  &&  !Character.isISOControl(ch)) {
	break;
      }
      --pos;
    }

    return str.substring(0, pos+1);
  }



  /**
   *  Test routines.
   *  @param  args   unused
   */
  public static void main(String[] args) {
    int[] iarr = new int[] {1,2,3};
    String[][] strarr = new String[][] {
      { "eins1", "eins2", "eins3" },
      { "zwei1", "zwei2", "zwei3" },
      { "drei1", "drei2", "drei3" },
      null
    };
      

    System.out.println(format("Integer array: %1", iarr));
    System.out.println(toString(strarr, "{\n  ", ",\n  ", "\n}", "  "));


    if (!"abcxx".equals(rpad("abc", 5, "x"))) System.out.println("rpad has an error (1)");
    if (!"abcxx".equals(rpad("abc", 5, "xx"))) System.out.println("rpad has an error (2)");
    if (!"abcxx".equals(rpad("abc", 5, "xxxx"))) System.out.println("rpad has an error (3)");
    if (!"ab".equals(rpad("abc", 2, "x"))) System.out.println("rpad has an error (4)");

    if (!"xxabc".equals(lpad("abc", 5, "x"))) System.out.println("lpad has an error (1) "+lpad("abc", 5, "x"));
    if (!"xxabc".equals(lpad("abc", 5, "xx"))) System.out.println("lpad has an error (2) "+lpad("abc", 5, "xx"));
    if (!"xxabc".equals(lpad("abc", 5, "xxx"))) System.out.println("lpad has an error (3) "+lpad("abc", 5, "xxx"));
    if (!"bc".equals(lpad("abc", 2, "x"))) System.out.println("lpad has an error (4) "+lpad("abc", 2, "x"));

    if (!"xabc".equals(center("abc", 5, "x"))) System.out.println("center has an error (1) "+center("abc", 5, "x"));
    if (!"xabc".equals(center("abc", 6, "xx"))) System.out.println("center has an error (2) "+center("abc", 6, "xx"));
    if (!"xxabc".equals(center("abc", 7, "x"))) System.out.println("center has an error (3) "+center("abc", 7, "x"));

    if (!"xxx".equals(replace("xxx", "y", "z"))) System.out.println("replace error (1) :"+ replace("xxx", "y", "z"));
    if (!"zzz".equals(replace("xxx", "x", "z"))) System.out.println("replace error (2) :"+ replace("xxx", "x", "z"));
    if (!"zx".equals(replace("xyx", "xy", "z"))) System.out.println("replace error (3) :"+replace("xyx", "xy", "z") );
    if (!"zz".equals(replace("xyxy", "xy", "z"))) System.out.println("replace error (4) :"+ replace("xyxy", "xy", "z"));
    if (!"z234".equals(replace("1234", "1", "z"))) System.out.println("replace error (5) :"+ replace("1234", "1", "z"));
    if (!"123z".equals(replace("1234", "4", "z"))) System.out.println("replace error (6) :"+ replace("1234", "4", "z"));
  }

}










