require 'active_support'
require 'eventmachine'
require 'amqp'
require 'mq'
require 'rack'
require 'rack/urlmap'
require 'sinatra/base'

require 'powcloud/insurance/logger'
require 'powcloud/insurance/http_service'
require 'powcloud/insurance/agent_container_http_service'

module Powcloud
  module Insurance
    class AgentContainer
      include Logger

      attr_reader :rack_map

      def initialize(agents, opts = {})
        @options = {:host => '0.0.0.0', :port => 4567, :log_level => 'WARN'}.merge(opts)

        log_level = Log4r::LNAMES.include?(opts[:log_level]) ? Log4r::LNAMES.index(opts[:log_level]) : Log4r::WARN
        Log4r::Logger.root.level = log_level
        init_logger log_level

        @agents = ActiveSupport::OrderedHash.new
        @rack_map = {}

        load_agents agents
      end

      def load_agents(agents)
        agents.each do |agent_name|
          next if @agents.include? agent_name
          
          begin
            require agent_name
            agent_class = agent_name.classify.constantize
            logger.warn "Loaded #{agent_class.to_s}"
            @agents[agent_name] = agent = agent_class.new
            agent.init_logger(logger.level, agent_name) if agent.respond_to? :init_logger
          rescue Exception => ex
            log_exception ex
            exit 1 # Hard exit
          end
        end
      end


      def run!
        EM.run do

          begin

            @agents.each_pair do |agent_name, agent|
              logger.warn "Starting aggent #{agent_name}" if logger
              agent.start_no_block # Must be non-blocking
            end

            rack_up

            make_traps
          rescue Exception => ex
            log_exception ex
            stop_and_exit 1
          end
        end
      end

      protected

      def rack_up
        @rack_map = gen_rack_map

        if rack_map.length > 0
          @rack_map['/'] = create_http_service

          sinatra_logger = Log4r::Logger.new("#{logger.fullname}::sinatra")
          class << sinatra_logger
            def write(str)
              str.strip!
              warn str 
            end
          end

          builder = Rack::Builder.new
          builder.use Rack::CommonLogger, sinatra_logger
          builder.run Rack::URLMap.new @rack_map
          Rack::Handler::Thin.run builder.to_app, :Host => @options[:host], :Port => @options[:port] do |@thin_server|# Run on sinatra port.
            @thin_server.timeout = 10.minutes # Do we need longer timeout?
          end
        end
      end

      def gen_rack_map
        map = {}
        @agents.each_pair do |name, agent|
          if agent.kind_of? Powcloud::Insurance::HttpService
            prefix = urlize(name, agent)
            map[prefix] = agent.create_http_service
            logger.warn "Mapping agent http service to #{prefix}"
          end
        end
        map
      end

      def urlize(name, agent)
        # TODO: add agent based suffix support.
        '/' + name.gsub(/[^\w\d\/._-]/, '')
      end

      def make_traps
        # NOTE: sinatra also install traps and it try to stop EM by itself.
        Signal.trap('INT') { stop_with_thin }
        Signal.trap('TERM'){ stop_with_thin }
      end

      def stop_and_exit(status)
        stop_with_thin do
          exit status
        end
      end

      def stop_with_thin
        logger.warn "Stopping..." if logger
        stopped = false

        stop_proc = proc do
          EM.stop
          @thin_server.stop! if @thin_server
          stopped = true
          yield if block_given?
        end

        Powcloud::Insurance::Channel.close_connections &stop_proc

        EM.add_timer 5 do # Wait 5 seconds and force close. 
          logger.warn "Forced stop" if logger
          stop_proc.call unless stopped
        end
      end
    end
  end
end