# async_sinatra_example.ru
require 'sinatra'

# Normally Sinatra::Base expects that the completion of a request is 
# determined by the block exiting, and returning a value for the body.
#
# In an async environment, we want to tell the webserver that we're not going
# to provide a response now, but some time in the future.
#
# The a* methods provide a method for doing this, by informing the server of
# our asynchronous intent, and then scheduling our action code (your block)
# as the next thing to be invoked by the server.
#
# This code can then do a number of things, including waiting (asynchronously)
# for external servers, services, and whatnot to complete. When ready to send
# the real response, simply setup your environment as you normally would for 
# sinatra (using #content_type, #headers, etc). Finally, you complete your
# response body by calling the #body method. This will push your body into the
# response object, and call out to the server to actually send that data.
module Sinatra::Async
  module ClassMethods
    def aget(path, opts={}, &block)
      conditions = @conditions.dup
      aroute('GET', path, opts, &block)

      @conditions = conditions
      aroute('HEAD', path, opts, &block)
    end

    def aput(path, opts={}, &bk); aroute 'PUT', path, opts, &bk; end
    def apost(path, opts={}, &bk); aroute 'POST', path, opts, &bk; end
    def adelete(path, opts={}, &bk); aroute 'DELETE', path, opts, &bk; end
    def ahead(path, opts={}, &bk); aroute 'HEAD', path, opts, &bk; end

    private
    def aroute(*args, &block)
      self.send :route, *args do |*bargs|
        mc = class << self; self; end
        mc.send :define_method, :__async_callback, &block
        EM.next_tick { send(:__async_callback, *bargs) }
        throw :async      
      end
    end
  end

  def self.included(klass)
    klass.extend ClassMethods
  end

  def body(*args, &blk)
    super
    request.env['async.callback'][[response.status, response.headers, response.body]]
  end
end

class AsyncTest < Sinatra::Base
  include Sinatra::Async

  aget '/' do
    body "hello async"
  end

  aget '/delay/:n' do |n|
    EM.add_timer(n.to_i) { body { "delayed for #{n} seconds" } }
  end

end

run AsyncTest.new