require "eventmachine"
require "amqp/utilities/server_type"

require "thread"

module AMQP
  module Utilities

    # A helper that starts EventMachine reactor the optimal way depending on what Web server
    # (if any) you are running. It should not be considered a 100% safe, general purpose EventMachine
    # reactor "on/off switch" but is very useful in Web applications and some stand-alone applications.
    #
    # This helper was inspired by Qusion project by Dan DeLeo.
    #
    # h2. Key methods
    #
    # * {EventLoopHelper.run}
    # * {EventLoopHelper.server_type}
    class EventLoopHelper

      def self.eventmachine_thread
        @eventmachine_thread
      end # self.eventmachine_thread

      def self.reactor_running?
        EventMachine.reactor_running?
      end # self.reactor_running?

      # Type of server (if any) that is running.
      #
      # @see AMQP::Utilities::ServerType.detect
      def self.server_type
        @server_type ||= ServerType.detect
      end # self.server_type

      # A helper that detects what app server (if any) is running and starts
      # EventMachine reactor in the most optimal way. For event-driven servers like
      # Thin and Goliath, this means relying on them starting the reactor but delaying
      # execution of a block you pass to {EventLoopHelper.run} until reactor is actually running.
      #
      # For Unicorn, Passenger, Mongrel and other servers and standalone apps EventMachine is started
      # in a separate thread.
      #
      # @example Using EventLoopHelper.run to start EventMachine reactor the optimal way without blocking current thread
      #
      #   AMQP::Utilities::EventLoopHelper.run do
      #     # Sets up default connection, accessible via AMQP.connection, and opens a channel
      #     # accessible via AMQP.channel for convenience
      #     AMQP.start
      #
      #     exchange          = AMQP.channel.fanout("amq.fanout")
      #
      #     AMQP.channel.queue("", :auto_delete => true, :exclusive => true).bind(exchange)
      #     AMQP::channel.default_exchange.publish("Started!", :routing_key => AMQP::State.queue.name)
      #   end
      #
      # @return [Thread] A thread EventMachine event loop will be started in (there is no guarantee it is already running).
      #
      #
      # @note This method, unlike EventMachine.run, DOES NOT block current thread.
      def self.run(&block)
        if reactor_running?
          EventMachine.run(&block)

          return
        end

        @eventmachine_thread  ||= begin
                                    case self.server_type
                                    when :thin, :goliath, :evented_mongrel then
                                      EventMachine.next_tick { block.call }
                                      Thread.current
                                    when :unicorn, :passenger, :mongrel, :scgi, :webrick, nil then
                                      t = Thread.new { EventMachine.run(&block) }
                                      # give EventMachine reactor some time to start
                                      sleep(0.25)

                                      t
                                    else
                                      t = Thread.new { EventMachine.run(&block) }
                                      # give EventMachine reactor some time to start
                                      sleep(0.25)

                                      t
                                    end
                                  end

        @eventmachine_thread
      end # self.run


    end # EventLoopHelper
  end # Utilities
end # AMQP