require 'fiber'

class Actor < Fiber
class << self
def spawn(&routine)
actor = new &routine
actor.instance_eval { @mailbox = Mailbox.new }

Scheduler << actor
actor
end

def receive(&filter)
raise "receive must be called in the context of an Actor" unless current.is_a?(Actor)
current.instance_eval { @mailbox.receive &filter }
end
end

def <<(message)
@mailbox << message
Scheduler << self
message
end

class Scheduler
@@queue = []
@@running = false

class << self
def <<(actor)
@@queue << actor
run unless @@running
end

def run
return if @@running

@@running = true
while not @@queue.empty?
run_queue = @@queue
@@queue = []
run_queue.each { |actor| actor.resume }
end
@@running = false
end
end
end

class Mailbox
def initialize
@queue = []
end

def <<(message)
@queue << message
end

def receive
raise ArgumentError, "No filter block given" unless block_given?

filter = Filter.new
yield filter
raise ArgumentError, "Empty filter" if filter.empty?

matched_index = matched_message = action = nil

while action.nil?
@queue.each_with_index do |message, index|
next unless (action = filter.match message)
matched_index = index
matched_message = message

break
end

Fiber.yield unless action
end

@queue.delete_at matched_index
return action.call(matched_message)
end

class Filter
def initialize
@ruleset = []
end

def when(pattern, &action)
raise ArgumentError, "No block given" unless action
@ruleset << [pattern, action]
end

def match(message)
_, action = @ruleset.find do |pattern, _|
case pattern
when Proc then pattern.call message
else pattern === message
end
end

action
end

def empty?
@ruleset.empty?
end
end
end
end