# = timer.rb
#
# == Copyright (c) 2004 Thomas Sawyer
#
#   Ruby License
#
#   This module is free software. You may use, modify, and/or redistribute this
#   software under the same terms as Ruby.
#
#   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.
#
# == SPECIAL THANKS
#
# Thanks to Paul Brannan for TimeLimit and Minero Aoki for Timer.
# These two libraries served as models for building this class.
#
# == Author(s)
#
# * Minero Aoki
# * Paul Brannan
# * Thomas Sawyer

# Author::    Thomas Sawyer
# Copyright:: Copyright (c) 2005 Thomas Sawyer
# License::   Ruby License

require 'timeout'  # for TimeoutError

# = Timer
#
# Provides a strightforward means for controlling time critical execution.
# Can be used as a "stop watch" timer or as a "time bomb" timer.
#
# == Usage
#
#   t = Timer.new(10) { raise TimeoutError, "timeout!" }
#   t.start
#     :      # done within 10sec timeout
#   t.stop
#   t.start
#     :
#   if condition then
#     t.reset       #--> restart timer
#   end
#
# A Kernel method is also provided for easily timing the exectuion of a block.
#
#   timed { |timer|
#
#      timer.total_time.round #=> 0
#
#      sleep 1
#      timer.total_time.round #=> 1
#
#      timer.stop
#      timer.total_time.round #=> 1
#
#      sleep 2
#      timer.total_time.round #=> 1
#
#      timer.start
#      timer.total_time.round #=> 1
#
#      sleep 1
#      timer.total_time.round #=> 2
#
#   }
#

class Timer

  attr_reader :start_time, :end_time
  attr_accessor :time_limit

  def initialize( time_limit=nil, &block )
    # standard timer
    @start_time = nil
    @end_time = nil
    @total_time = 0
    @runnning = nil
    # for using time limit
    @time_limit = time_limit
    @on_timeout = block
    @current_thread = nil
    @timer_thread = nil
  end

  def on_timeout( &block )
    if block then
      @on_timeout = block
      true
    else
      false
    end
  end

  # Start the timer.

  def start
    @running = true
    @start_time = Time.now

    limit if @time_limit

    self

    #if block_given? then
    #  begin
    #    yield( self )
    #  ensure
    #    stop
    #  end
    #else
    #  @time_limit
    #end
  end

  # Establish a time limit on execution.

  def limit( time_limit=nil )
    if @time_limit || time_limit
      @current_thread = Thread.current
      @timer_thread = Thread.fork {
        sleep @time_limit
        if @on_timeout then
          @on_timeout.call @time_limit
        else
          @current_thread.raise TimeoutError, "#{@time_limit} seconds past"
        end
      }
    end
  end

  # Kill time limit thread, if any.

  def defuse
    if @timer_thread
      Thread.kill @timer_thread
      @timer_thread = nil
    end
  end

  # Stops timer and returns total time.
  # If timer was not running returns false.

  def stop
    if @running
      defuse
      # record running time
      @end_time = Time.now
      @running = false
      @total_time += (@end_time - @start_time)
    else
      nil
    end
  end

  # Stops and resets the timer. If the timer was running
  # returns the total time. If not returns 0.

  def reset
    if running?
      r = stop
    else
      r = 0
    end
    @total_time = 0
    return r
  end

  # Resets the time limit. Same as:
  #
  #   t.stop
  #   t.start
  #

  def reset_limit
    #stop
    #start
    defuse
    limit
  end

  # Queries whether the timer is still running.

  def running?
    return @running
  end

  # Queries whether the timer is still not running.

  def stopped?
    return !@running
  end

  # Queries total recorded time of timer.

  def total_time
    if running? then
      return @total_time + (Time.now - @start_time)
    else
      return @total_time
    end
  end

  #
  # Timer::Dummy - Dummy Timer (i.e. no real time limit)
  #--
  # NEEDS WORK!
  #++

  class Dummy < self
    def start
      if block_given? then
        yield
      else
        time_limit()
      end
    end

    def stop
      false
    end
  end #class Dummy

end #class Timer


module Kernel

  # Takes a block and returns the total time it took to execute.

  def timed
    yield( timer = Timer.new.start )
    return timer.total_time
  end

end



#  _____         _
# |_   _|__  ___| |_
#   | |/ _ \/ __| __|
#   | |  __/\__ \ |_
#   |_|\___||___/\__|
#

=begin test

require 'test/unit'

class TC_Timer < Test::Unit::TestCase

  def test_timed
    timed { |timer|
      assert_equal 0, timer.total_time.round
      sleep 1
      assert_equal 1, timer.total_time.round
      timer.stop
      assert_equal 1, timer.total_time.round
      sleep 1
      assert_equal 1, timer.total_time.round
      timer.start
      assert_equal 1, timer.total_time.round
      sleep 1
      assert_equal 2, timer.total_time.round
    }
  end

  def test_outoftime
    t = Timer.new(1)
    assert_raises( TimeoutError ) {
      t.start
      sleep 2
      t.stop
    }
  end

  # This has been removed becuase it is too close to call.
  # Sometimes and error is returned sometimes it is not.
  #def test_nickoftime
  #  assert_raises( TimeoutError ) {
  #    @t.start
  #    sleep 2
  #    @t.stop
  #  }
  #end

  def test_intime
    t = Timer.new(2)
    assert_nothing_raised {
      t.start
      sleep 1
      t.stop
    }
  end

end

=end
