/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    FlexibleDecimalFormat.java
 *    Copyright (C) 2002-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.core.matrix;

import java.text.DecimalFormat;
import java.text.FieldPosition;

import weka.core.RevisionHandler;
import weka.core.RevisionUtils;

/**
 * @author Yong Wang
 * @version $Revision: 8034 $
 */
public class FlexibleDecimalFormat
  extends DecimalFormat
  implements RevisionHandler {

  /** for serialization */
  private static final long serialVersionUID = 110912192794064140L;

  private DecimalFormat nf = null;
  private int      digits = 7;
  private boolean  exp = false; 
  private int      intDigits = 1;
  private int      decimalDigits = 0;
  private int      expIntDigits = 1;          // ??? 
  private int      expDecimalDigits = 0;      // ???
  private int      power = 2;
  private boolean  trailing = false;
  private boolean  grouping = false;
  private boolean  sign = false;
    
  public FlexibleDecimalFormat ( ) {
    this( 5 );
  }

  public FlexibleDecimalFormat ( int digits ) {
    if( digits < 1 ) 
      throw new IllegalArgumentException("digits < 1");
    this.digits = digits;
    intDigits = 1;
  }

  public FlexibleDecimalFormat ( int digits, boolean trailing ) {
    this( digits );
    this.trailing = trailing;
  }

  public FlexibleDecimalFormat ( int digits, boolean exp, boolean trailing, 
				 boolean grouping ) {
    this.trailing = trailing;
    this.exp = exp;
    this.digits = digits;
    this.grouping = grouping;
    if( exp ) {
      this.intDigits = 1;
      this.decimalDigits = digits - intDigits;
    }
    else {
      this.decimalDigits = decimalDigits;
      this.intDigits = Math.max( 1, digits - decimalDigits );
    }
  }

  public FlexibleDecimalFormat ( double d ) {
    newFormat( d );
  }

  private void newFormat ( double d ) {
    if( needExponentialFormat( d ) ) {
      exp = true;
      intDigits = 1;
      expDecimalDigits = decimalDigits( d, true );
      if( d < 0) sign = true;
      else sign = false;
    }
    else {
      exp = false;
      intDigits = Math.max(1, intDigits( d ));
      decimalDigits = decimalDigits( d, false );
      if( d < 0.0 ) sign = true;
      else sign = false;
    }
  }

  public void update ( double d ) {
    if( Math.abs( intDigits(d) -1 ) > 99 ) power = 3;
    expIntDigits = 1;
    expDecimalDigits = Math.max( expDecimalDigits, 
				 decimalDigits( d, true ));
    if( d < 0) sign = true;
    if( needExponentialFormat( d ) || exp ) {
      exp = true;
    }
    else {
      intDigits = Math.max(intDigits, intDigits( d ));
      decimalDigits = Math.max(decimalDigits, decimalDigits( d, false ));
      if( d < 0) sign = true;
    }
  }

  private static int intDigits ( double d ) {
    return (int) Math.floor( Math.log( Math.abs( d * (1 + 1e-14) ) ) / 
			     Math.log ( 10 ) ) + 1;
  }
    
  private int decimalDigits ( double d, boolean expo ) {
    if( d == 0.0 ) return 0;
    d = Math.abs( d );
    int e = intDigits( d );
    if( expo ) {
      d /= Math.pow(10, e-1);
      e = 1;
    }
    if( e >= digits ) return 0;
    int iD = Math.max(1, e);  
    int dD = digits - e;
    if( !trailing && dD > 0 ) {  // to get rid of trailing zeros
      FloatingPointFormat f = new 
	FloatingPointFormat( iD + 1 + dD, dD, true);
      String dString = f.format( d );
      while( dD > 0 ) {
	if( dString.charAt(iD+1+dD-1) == '0' ) {
	  dD--;
	}
	else break;
      }
    }
    return dD;
  }
    
  public boolean  needExponentialFormat ( double d ) {
    if( d == 0.0 ) return false;
    int e = intDigits( d );
    if( e > digits + 5 || e < -3 ) return true;
    else return false;
  }
    
  public void grouping ( boolean grouping ) {
    this.grouping = grouping;
  }
    
  private static void println ( Object obj ){
    System.out.println( obj );
  }

  private void setFormat ( ) {
    int dot = 1;
    if( decimalDigits == 0) dot = 0;
    if( exp ) 
      nf = new ExponentialFormat( 1 + expDecimalDigits, power, sign, 
				  grouping || trailing );
    else { 
      int s = sign ? 1 : 0;
      nf = new FloatingPointFormat( s +intDigits +dot +decimalDigits, 
				    decimalDigits, grouping || trailing);
    }
  }
    
  private void setFormat ( double d ) {
    newFormat( d );
    setFormat();
  }

  public StringBuffer format (double number, StringBuffer toAppendTo,
			      FieldPosition pos) {
    if( grouping ) {
      if( nf == null ) {
	setFormat();
      }
    }
    else setFormat( number );
	
    return toAppendTo.append( nf.format(number) );
  }

  public int width () {
	
    if( !trailing && !grouping ) 
      throw new RuntimeException( "flexible width" );
	
    return format(0.).length();
  }
    
  public StringBuffer formatString ( String str ) {
    int w = width();
    int h = ( w - str.length() ) / 2;
    StringBuffer text = new StringBuffer();
    for(int i = 0; i < h; i++ ){
      text.append( ' ' );
    }
    text.append( str );
    for(int i = 0; i < w - h - str.length(); i++ ){
      text.append( ' ' );
    }
    return text;
  }
  
  /**
   * Returns the revision string.
   * 
   * @return		the revision
   */
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 8034 $");
  }
}


