#--
#            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
#                    Version 2, December 2004
#
#            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
#   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
#
#  0. You just DO WHAT THE FUCK YOU WANT TO.
#++

require 'thread'

# An every runs the given block every given seconds, you can then get the
# value, check if the value is old and you can check how many seconds
# until the next run.
class Thread::Every
	Cancel  = Class.new(Exception)
	Restart = Class.new(Exception)

	# Create an every with the given seconds and block.
	def initialize (every, &block)
		raise ArgumentError, 'no block given' unless block

		@every  = every
		@old    = true
		@mutex  = Mutex.new
		@thread = Thread.new {
			loop do
				begin
					value = block.call

					@mutex.synchronize {
						@at        = Time.now
  					@value     = value
						@old       = false
						@exception = nil
					}
				rescue Restart
					next
				rescue Exception => e
					@mutex.synchronize {
						@at        = Time.now
						@exception = e
					}

					break if e.is_a? Cancel
				end

				cond.broadcast if cond?

				begin
					sleep @every
				rescue Restart
					next
				rescue Cancel => e
					@mutex.synchronize {
						@at        = Time.now
						@exception = e
					}

					break
				end
			end
		}

		ObjectSpace.define_finalizer self, self.class.finalizer(@thread)
	end

	# @private
	def self.finalizer (thread)
		proc {
			thread.raise Cancel.new
		}
	end

	# Change the number of seconds between each call.
	def every (seconds)
		@every = seconds

		restart
	end

	# Cancel the every, {#value} will yield a Cancel exception.
	def cancel
		@mutex.synchronize {
			@thread.raise Cancel.new('every cancelled')
		}

		self
	end

	# Check if the every has been cancelled.
	def cancelled?
		@mutex.synchronize {
			@exception.is_a? Cancel
		}
	end

	# Checks when the every was cancelled.
	def cancelled_at
		if cancelled?
			@mutex.synchronize {
				@at
			}
		end
	end

	# Restart the every.
	def restart
		@mutex.synchronize {
			@thread.raise Restart.new
		}

		self
	end

	# Check if the every is running.
	def running?
		!cancelled?
	end

	# Check if the every is old, after the first #value call it becomes old,
	# until another run of the block is gone)
	def old?
		@mutex.synchronize {
			@old
		}
	end

	# Gets the Time when the block was called.
	def called_at
		@mutex.synchronize {
			@at
		}
	end

	# Gets how many seconds are missing before another call.
	def next_in
		return if cancelled?

		@mutex.synchronize {
			@every - (Time.now - @at)
		}
	end

	# Gets the current every value.
	def value (timeout = nil)
		@mutex.synchronize {
			if @old
				cond.wait(@mutex, *timeout)
			end

			@old = true

			if @exception
				raise @exception
			else
				@value
			end
		}
	end

	alias ~ value

	# Gets the current every value, without blocking and waiting for the next
	# call.
	def value!
		@mutex.synchronize {
			@old = true

			@value unless @exception
		}
	end

private
	def cond?
		instance_variable_defined? :@cond
	end

	def cond
		@cond ||= ConditionVariable.new
	end
end

class Thread
	# Helper to create an every
	def self.every (every, &block)
		Thread::Every.new(every, &block)
	end
end

module Kernel
	# Helper to create an every
	def every (every, &block)
		Thread::Every.new(every, &block)
	end
end
