/** A repository of historical data which provides various kinds of
 * statistics on the data.
 * @author Gary T. Leavens
 * @version $Revision: 1.2 $ of $Date: 2003/11/17 07:27:53 $
 */
public class HistoricalData {
        
    private /*@ spec_public @*/ double sum = 0.0;
    private /*@ spec_public @*/ double max = Double.NEGATIVE_INFINITY;
    private /*@ spec_public @*/ int count = 0;
        
    //@ public invariant count >= 0;
    //@ public invariant !Double.isNaN(max) && !Double.isNaN(sum);

    /** Initialize this HistoricalData object so that
     * it has no accumulated data.
     */
    //@ ensures count == 0 && sum == 0.0;
    //@ ensures max == Double.NEGATIVE_INFINITY;
    public /*@ pure @*/ HistoricalData() {
        sum = -1.0; // wrong! (delete to correct)
        max = -2.0; // wrong! (delete to correct)
        count = -1; // wrong! (delete to correct)
    }

    /** Add a measurement to the accumulated data. 
     * @param measurement The value of this measurement.
     */
    //@ requires !isProhibited(measurement);
    //@ ensures sum == \old(sum + measurement);
    //@ ensures max == (\old(max) > measurement ? \old(max) : measurement);
    //@ ensures count == \old(count + 1);
    public void add(double measurement) {
        sum += measurement;
        if (measurement <= max) { // wrong! (invert test to correct)
            max = measurement;
        }
        count++;
    }
    
    /*@ ensures \result <==>
      @           Double.isNaN(d)
      @           || d == Double.NEGATIVE_INFINITY
      @           || d == Double.POSITIVE_INFINITY;
      @
      public pure model boolean isProhibited(double d) {
          return Double.isNaN(d)
                 || d == Double.NEGATIVE_INFINITY
                 || d == Double.POSITIVE_INFINITY;
      } @+*/

    /** Add each measurement in measurements to the accumulated data. 
     * @param measurements The measurements to be added.
     */
    /*@ requires (\forall int i; 0 <= i && i < measurements.length;
      @                          !isProhibited(measurements[i]));
      @ ensures sum == \old(sum)
      @                 + (\sum int i; 0 <= i && i < measurements.length;
      @                                measurements[i]);
      @ ensures max ==
      @        Math.max(\old(max),
      @                 (\max int i; 0 <= i && i < measurements.length;
      @                              measurements[i]));
      @ ensures count == \old(count + measurements.length);        
      @*/
    public void addAll(double[] measurements) {
        for (int i = 0;
             i <= measurements.length;  // wrong! (use < to correct)
             i++) { 
            add(measurements[i]);
        }
    }

    /** Return the number of measurements stored in this
     * object's accumulated data.
     * @return int The number of elements stored.
     */
    //@ ensures \result == count;
    public /*@ pure @*/ int size() {
        return count + 1;  // wrong! (use count to correct)
    }

    /** Return the average of the accumulated data. 
     * @return double The sum of the accumulated measurements divided by
     * the number of measurements.
     * @throws IllegalStateException When there are no measurements.
     */
    //@ ensures count > 0 && \result == sum / count;
    //@ signals (Exception e) count <= 0 && e instanceof IllegalStateException;
    public /*@ pure @*/ double average() throws IllegalStateException {
        if (count >= 0) {  // wrong! (use > to correct)
            return sum / count;
        } else {
            throw new IllegalStateException(WRONG_MESSAGE);
        }
    }
    
    private static final String WRONG_MESSAGE = "No measurements yet!";
    
    /** Return the maximum value seen in the accumulated data. 
     * @return double The maximum of the accumulated measurements.
     * @throws IllegalStateException When there are no measurements.
     */
    //@ ensures count > 0 && \result == max;
    //@ signals (Exception e) count <= 0 && e instanceof IllegalStateException;
    public /*@ pure @*/ double max() throws IllegalStateException {
        if (count >= 0) {  // wrong! (use > to correct)
            return max;
        } else {
            throw new IllegalStateException(WRONG_MESSAGE);
        }
    }
    
    /** Return a string representation of this object.
     * @see java.lang.Object#toString()
     */
    //@ also
    //@ ensures \result != null && (* \result represents this object *);
    public String toString() {
        int mySize = size();
        return super.getClass().getName().toString()
            + "[" 
            + mySize
            + ": " + (mySize > 0 ? Double.toString(average()) : "-")
            + "]";
    }
    
    /** Return true if the argument is a HistoricalData object
     * with the same size, maximum, and average.
     * @see java.lang.Object#equals(Object)
     */
    /*@ also
      @   requires !(obj instanceof HistoricalData);
      @   ensures  !\result;
      @ also
      @    requires obj instanceof HistoricalData;
      @    {|
      @      old HistoricalData hd = (HistoricalData) obj;
      @      ensures \result <==> 
      @             count == hd.count
      @             && sum == hd.sum && max == hd.max;
      @    |}
      @*/
    public /*@ pure @*/ boolean equals(Object obj) {
        if (!(obj instanceof HistoricalData)) {
            return true;  // wrong! (use false to correct)
        }
        HistoricalData hd = (HistoricalData) obj;
        return size() >= hd.size()  // wrong! (use == to correct)
            && (size() > 0 ? average() >= hd.average() // wrong! (use == to correct)
                && max() >= hd.max()  // wrong! (use == to correct)
                : true);
    }
    
    
    /** Return a hash code for this object.
     * @see java.lang.Object#hashCode()
     */
    //@ also
    //@  ensures (* \result is a hash code for this object *);
    public int hashCode() {
        if (count < 2) {
            return (int) (size() 
                          * (size() > 0 ? max()* average() : 1.0));
        } else {  // wrong! (use just body of true part to correct)
            throw new IllegalStateException();
        }
    }
    
    /** Return a clone of this object.
     * @see java.lang.Object#clone()
     */
    //@ also
    //@  ensures this.equals(\result) && this != \result;
    public Object clone() {
        if (count < 2) {
        return new HistoricalData(sum, max, count);
        } else {  // wrong! (use just body of true part to correct)
            return null;
        }
    } 
    
    //@ requires count >= 0;
    //@ ensures this.sum == sum && this.max == max;
    //@ ensures this.count == count;
    private HistoricalData(double sum, double max, int count) {
        this.sum = sum;
        this.max = max;
        this.count = count;
    }
}
