module Monkeyshines
  module Monitor
    #
    # Accepts a lightweight call every iteration.
    #
    # Once either a time or an iteration criterion is met, executes the block
    # and resets the timer until next execution.
    #
    # Note that the +time_interval+ is measured *excution to execution* and not
    # in multiples of iter_interval. Say I set a time_interval of 300s, and
    # happen to iterate at 297s and 310s after start.  Then the monitor will
    # execute at 310s, and the next execution will happen on or after 610s.
    #
    # Also note that when *either* criterion is met, *both* criteria are
    # reset. Say I set a time interval of 300s and an +iter_interval+ of 10_000;
    # and that at 250s I reach iteration 10_000.  Then the monitor will execute
    # on or after 20_000 iteration or 550s, whichever happens first.
    #
    class PeriodicMonitor
      attr_accessor :time_interval, :iter_interval
      attr_accessor :last_time, :iter, :started_at

      def initialize options={}
        self.started_at    = Time.now.utc.to_f
        self.last_time     = started_at
        self.iter          = 0
        self.time_interval = options[:time]
        self.iter_interval = options[:iters]
      end

      # True if more than +iter_interval+ has elapsed since last execution.
      def enough_iterations?
        iter % iter_interval == 0 if iter_interval
      end

      # True if more than +time_interval+ has elapsed since last execution.
      def enough_time? now
        (now - last_time) > time_interval if time_interval
      end

      # Time since monitor was created
      def since
        Time.now.utc.to_f - started_at
      end
      # Iterations per second
      def rate
        iter.to_f / since.to_f
      end

      #
      # if the interval conditions are met, executes block; otherwise just does
      # bookkeeping and returns.
      #
      def periodically &block
        self.iter += 1
        now       = Time.now.utc.to_f
        if enough_iterations? || enough_time?(now)
          block.call(iter, (now-last_time))
          self.last_time = now
        end
      end
    end

  end
end