IMPLEMENTATION MODULE Stats;

IMPORT Math;
FROM Streams IMPORT StdOut;
IMPORT SYSTEM;
FROM Conversions IMPORT IntConversions;

  CLASS StatCollector;
    VAR
      TheObservations, TheIntervals: ARRAY OF INTEGER;
      TheMin,
      TheMax,
      TheCount: INTEGER;
      TheSquareSum,
      TheSum: REAL;

    METHOD IsSorted (a: ARRAY OF INTEGER): BOOLEAN;
      BEGIN
      RESULT := TRUE;
      FOR i := 0 TO a.SIZE - 2 WHILE RESULT DO
        RESULT := RESULT AND (a[i] <= a[i + 1]);
        END;
      END IsSorted;

    REDEFINE METHOD CREATE (Intervals: ARRAY OF INTEGER);
      BEGIN
      ASSERT IsSorted (Intervals);
      TheObservations.CREATE (Intervals.SIZE + 1);
      TheIntervals := Intervals;      
      END CREATE;

    METHOD Intervals: ARRAY OF INTEGER;
      BEGIN
      RESULT := TheIntervals;
      END Intervals;
      
    METHOD Observations: ARRAY OF INTEGER;
      BEGIN
      RESULT := TheObservations;
      END Observations;
      
    METHOD Min: INTEGER;
      BEGIN
      RESULT := TheMin;
      END Min;

    METHOD Max: INTEGER;
      BEGIN
      RESULT := TheMax;
      END Max;
      
    METHOD Count: INTEGER;
      BEGIN
      RESULT := TheCount;
      END Count;
      
    METHOD StdDev: REAL;
      VAR
        x: REAL;
      BEGIN
      x := TheSquareSum / SYSTEM.FLOAT (TheCount) - (Average * Average);
      RESULT := Math.Sqrt (x);
      END StdDev;
      
    METHOD Average: REAL;
      BEGIN
      RESULT := TheSum / SYSTEM.FLOAT (TheCount);
      END Average;

    METHOD FindInterval (Value: INTEGER): INTEGER;
      VAR
         Found: INTEGER;
      BEGIN
      Found := -1;
      FOR i := 0 TO TheIntervals.SIZE - 1 WHILE Found < 0 DO
        IF Value < TheIntervals[i] THEN
	  Found := i;
	  END;
	END;
      IF Found < 0 THEN
	Found := TheIntervals.SIZE;
        END;
      RESULT := Found;
      END FindInterval;
      
    METHOD AddObservation (Value: INTEGER);
      BEGIN
      AddNObservations (Value, 1);
      END AddObservation;
      
    METHOD AddNObservations (Value, n: INTEGER);
      VAR
        i: INTEGER;
      BEGIN
      IF TheCount = 0 THEN
        TheMin := Value;
  	TheMax := Value;
	END;
      IF Value < TheMin THEN
        TheMin := Value;
	END;
      IF Value > TheMax THEN
        TheMax := Value;
	END;
      i := FindInterval (Value);
      TheObservations[i] := TheObservations[i] + n;
      TheCount := TheCount + n;
      TheSum := TheSum + SYSTEM.FLOAT (n) * SYSTEM.FLOAT (Value);
      TheSquareSum := TheSquareSum + SYSTEM.FLOAT (n) * SYSTEM.FLOAT (Value * Value);
      END AddNObservations;

  END StatCollector;

END Stats;
