/*
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This code 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
 * MA  02111-1307, USA.
 */
package no.geosoft.cc.util;



import java.util.Iterator;



/**
 * Find "nice numbers" within an interval. The interval is given
 * by a min and a max value:
 *
 * <pre>
 * NiceNumbers numbers = new NiceNumbers (min, max, n, false);
 * for (Iterator i = numbers.iterator(); i.hasNext(); ) {
 *   NiceNumber number = (NiceNumber) i.next();
 *   :
 * }
 * </pre>
 *
 * This class is handy for producing quality annotation on graphic
 * displays, for instance along an axis.
 * 
 * @author <a href="mailto:info@geosoft.no">GeoSoft</a>
 */   
public class NiceNumbers implements Iterator
{
  private boolean  isBounded_;
  private double   fromValue_;
  private double   toValue_;
  private double   firstValue_;    // First nice number < from
  private double   lastValue_;     // First nice number > to
  private double   majorStep_;
  private double   minorStep_;
  private double   minorMinorStep_;
  private double   step_;
  private int      nValues_;
  private int      valueNo_;

  
  
  /**
   * Create nice numbers in an interval.
   *
   * @param fromValue       From value.
   * @param toValue         To value.
   * @param nNumbersApprox  Approximate number of major nice numbers to produce.
   * @param isBounded       True if fromValue/toValue should be end points
   *                        and hence reported as nice numbers.
   */
  public NiceNumbers (double fromValue, double toValue, int nNumbersApprox,
                      boolean isBounded)
  {
    fromValue_ = fromValue;
    toValue_   = toValue;
    isBounded_ = isBounded;

    if (nNumbersApprox <= 0) nNumbersApprox = 1;
    double step = (toValue_ - fromValue_) / nNumbersApprox;
    if (step == 0.0) step = 1.0;

    boolean isAscending = step > 0.0;
    
    // Scale abs(step) to interval 1 - 10
    double scaleFactor = 1.0;
    while (Math.abs (step) > 10) {
      step /= 10.0;
      scaleFactor *= 10.0;
    }
    while (Math.abs (step) < 1) {
      step *= 10.0;
      scaleFactor /= 10.0;
    }

    // Find nice major step value
    majorStep_ = Math.abs (step);
    if      (majorStep_ > 7.50) majorStep_ = 10.0;
    else if (majorStep_ > 3.50) majorStep_ =  5.0;
    else if (majorStep_ > 2.25) majorStep_ =  2.5;
    else if (majorStep_ > 1.50) majorStep_ =  2.0;
    else                        majorStep_ =  1.0;

    // Find corresponding minor step value
    if      (majorStep_ == 10.0) minorStep_ = 5.0;
    else if (majorStep_ ==  5.0) minorStep_ = 2.5;
    else if (majorStep_ ==  2.5) minorStep_ = 0.5;
    else if (majorStep_ ==  2.0) minorStep_ = 1.0;
    else                         minorStep_ = 0.1;
    
    // Find corresponding minor minor step value
    if      (minorStep_ ==  5.0) minorMinorStep_ = 1.0;
    else if (minorStep_ ==  2.5) minorMinorStep_ = 0.5;
    else if (minorStep_ ==  1.0) minorMinorStep_ = 0.1;    
    else if (minorStep_ ==  0.5) minorMinorStep_ = 0.1;
    else                         minorMinorStep_ = 0.0;

    if (step < 0) {
      majorStep_      = -majorStep_;
      minorStep_      = -minorStep_;
      minorMinorStep_ = -minorMinorStep_;      
    }
    
    majorStep_      *= scaleFactor;
    minorStep_      *= scaleFactor;
    minorMinorStep_ *= scaleFactor;    

    // Find first nice value before fromValue
    firstValue_ = ((int) (fromValue_ / majorStep_)) * majorStep_;
    
    if      ( isAscending && firstValue_ > fromValue_) firstValue_ -=majorStep_;
    else if (!isAscending && firstValue_ < fromValue_) firstValue_ -=majorStep_;

    // Find last nice value after toValue
    lastValue_ = ((int) (toValue_ / majorStep_)) * majorStep_;
    if      ( isAscending && lastValue_ < toValue_) lastValue_ += majorStep_;
    else if (!isAscending && lastValue_ > toValue_) lastValue_ += majorStep_;

    // Find total number of values
    step_ = minorMinorStep_ != 0.0 ? minorMinorStep_ :
                 minorStep_ != 0.0 ? minorStep_ : majorStep_;

    nValues_ =  (int) Math.round ((lastValue_ - firstValue_) / step_) + 1;
    
    // Move the steps from value space to count space
    majorStep_      = (double) Math.round (majorStep_      / step_);
    minorStep_      = (double) Math.round (minorStep_      / step_);
    minorMinorStep_ = (double) Math.round (minorMinorStep_ / step_);
  }


  
  /**
   * Create nice numbers in an unbound interval.
   * 
   * @param fromValue       From value.
   * @param toValue         To value.
   * @param nNumbersApprox  Approximate number of major nice numbers to produce.
   */
  public NiceNumbers (double fromValue, double toValue, int nNumbersApprox)
  {
    this (fromValue, toValue, nNumbersApprox, false);
  }



  /**
   * Initiate the iteration and return the iterator object
   * 
   * @return  The iterator (which is this).
   */
  public Iterator iterator()
  {
    valueNo_ = 0;
    return this;
  }
  
  
  
  /**
   * Retur true if there are more nice numbers, false otherwise.
   * 
   * @return  True if there are more nice numbers, false otherwise.
   */
  public boolean hasNext()
  {
    return valueNo_ < nValues_;
  }


  
  /**
   * Return the first nice number of the interval.
   * 
   * @return  First nice number of the interval.
   */
  public double getFirstValue()
  {
    return isBounded_ ? fromValue_ : firstValue_;
  }


  
  /**
   * Return the last nice number of the interval.
   * 
   * @return  Last nice number of the interval.
   */
  public double getLastValue()
  {
    return isBounded_ ? toValue_ : lastValue_;
  }


  
  /**
   * Return number of nice values in this interval.
   * 
   * @return  Total number of nice numbers in the interval.
   */
  public int getNValues()
  {
    return nValues_;
  }
  
  

  private boolean equals (double a, double b)
  {
    double limit = a == 0.0 ? 0.001 : Math.abs (a) * 0.001;
    return b > a - limit && b < a + limit;
  }

  
  
  /**
   * Return the next nice number of the sequence.
   * 
   * @return  Next nice number.
   */
  public Object next()
  {
    // Solve the bounded case
    if (isBounded_) {
      if (valueNo_ == 0) {
        double value = firstValue_;

        while ((fromValue_ < toValue_ && value <= fromValue_) ||
               (fromValue_ > toValue_ && value >= fromValue_)) {
          valueNo_++;
          value = firstValue_ + valueNo_ * step_;
        }

        int rank = equals (firstValue_, fromValue_) ? 0 : 1;
        return new NiceNumber (fromValue_, rank, 0.0);
      }
      else {
        double value = firstValue_ + valueNo_ * step_;
        if ((fromValue_ < toValue_ && value >= toValue_) ||
            (fromValue_ > toValue_ && value <= toValue_)) {
          valueNo_ = nValues_;

          int rank = equals (lastValue_, toValue_) ? 0 : 1;
          return new NiceNumber (toValue_, rank, 1.0);
        }
      }
    }
    
    double value = firstValue_ + valueNo_ * step_;

    int rank;
    if      (valueNo_ % (int) majorStep_ == 0.0) rank = 0;
    else if (valueNo_ % (int) minorStep_ == 0.0) rank = 1;
    else                                         rank = 2;

    // Find position
    double first = getFirstValue();
    double last  = getLastValue();
    double position = first == last ? 0.0 : (value - first) / (last - first);
    
    NiceNumber niceNumber = new NiceNumber (value, rank, position);

    valueNo_++;
    
    return niceNumber;
  }



  /**
   * From Iterator. Removing nice numbers are not possible, so this method
   * is left empty.
   */
  public void remove()
  {
    // Not possible
  }


  
  /**
   * Testing this class.
   * 
   * @param args  Not used.
   */
  public static void main (String[] args)
  {
    NiceNumbers numbers = new NiceNumbers (1.01, -1.0, 4, true);

    for (Iterator i = numbers.iterator(); i.hasNext(); ) {
      NiceNumber niceNumber = (NiceNumber) numbers.next();
      if (niceNumber.getRank() < 2)
        System.out.println (niceNumber);
    }
  }
}