/*
 *    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 2 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, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/*
 *    FlexibleDecimalFormat.java
 *    Copyright (C) 2002 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: 1.4 $
 */
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 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;
    }
    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.nf.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();
  }

  @Override
  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
   */
  @Override
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 1.4 $");
  }
}
