# Mock server to accept requests and return known responses based on request content 
#
# influenced by: http://dynamicorange.com/2009/02/18/ruby-mock-web-server
require 'openssl' unless defined?(JRUBY_VERSION)
require 'ostruct'
require 'net/http'
require 'net/https'
require 'webrick'
require 'webrick/https'
require 'rack'
require 'thread'

module HTTParrot 

  class Server

    attr_accessor :options

    def initialize(opts={})
      @server_started = @server = nil
      @server_thread = nil
      @secure_started = @secure_server = nil
      @secure_thread = nil

      @parent_thread = Thread.current
      @options = { 
        :Port => 4000, 
        :Host => "127.0.0.1"
      }.merge(HTTParrot::Config.config).merge(opts)

      quiet_options = { 
        :Logger => WEBrick::Log::new("/dev/null", 7),
        :AccessLog => []
      }

      @options.merge!(quiet_options) unless HTTParrot::Config.verbose

      self.clear!
    end

    def clear!
      blank_handler = {
        :get => [], 
        :post => [], 
        :head => [], 
        :delete => []
      }

      # Using Marshal for deep copy purposes
      # slow from an efficiency perspective, but #clear! 
      # is not called frequently and this gem is for testing
      @call_handlers = Marshal.load(Marshal.dump(blank_handler)) 
      @regex_handlers = Marshal.load(Marshal.dump(blank_handler)) 
      @endpoint_handlers = Marshal.load(Marshal.dump(blank_handler)) 
      @complex_handlers = Marshal.load(Marshal.dump(blank_handler))
      self
    end

    def reset_counts
      [@call_handlers, @regex_handlers, @endpoint_handlers, @complex_handlers].each do |handler_type|
        handler_type.each_key do |req_meth|
          handler_type[req_meth].each { |handler| handler.response_count = 0 }
        end
      end
    end

    def call(env)
      req = Rack::Request.new(env)
      response = respond_with(env)

      # TODO move this to WEBrick's builtin logging facilities
      if ENV["PARROT_VERBOSE"] || HTTParrot::Config.verbose
        puts "\n>>>>> REQUEST\n"
        puts req.inspect
        #puts req.body.string

        puts "\n<<<<< RESPONSE\n"
        puts response.inspect
        #response[2].each{ |l| puts l }
      end

      return response

    rescue Exception => e
      # reraise the exception in the parent thread if it Errors out
      @parent_thread.raise e
    end

    def register(http_method, method_key, response, call_with_env = false)
      http_method = http_method.to_s.downcase.to_sym
      raise "http_method in register must be one of [:get, :post, :head, :delete] : #{http_method}" if ![:get, :post, :head, :delete].include?(http_method)

      response_handler = OpenStruct.new({
        :method_key => method_key,
        :response => response,
        :env? => call_with_env,
        :response_count => 0
      })

      case
      when method_key.respond_to?(:call) then
        @call_handlers[http_method] << response_handler      
      when method_key.is_a?(Regexp) then
        @regex_handlers[http_method] << response_handler 
      when method_key.is_a?(String) then
        @endpoint_handlers[http_method] << response_handler 
      when method_key.is_a?(Array) then
        @complex_handlers[http_method] << response_handler        
      else
        raise "method_key (Handler) must be callable, Regexp, Array, or String" 
      end

      return response_handler
    end

    def start(startup_interval = 1)
      start_server
      start_secure_server if options[:ssl]
      sleep startup_interval # Ensure the server has time to startup
      sleep startup_interval if !running? # Give it a little more time if they didn't start
    end

    def running?
      secure_run_running = options[:ssl] ? (!@secure_server.nil? && @secure_started) : true
      @server_started && !@server.nil? && secure_run_running 
    end
    alias_method :started?, :running?

    def stop(shutdown_interval = 0)
      @server.shutdown if @server.respond_to?(:shutdown)
      @secure_server.shutdown if @secure_server.respond_to?(:shutdown)
      sleep shutdown_interval
      Thread.kill(@server_thread) if !@server_thread.nil?
      Thread.kill(@secure_thread) if !@secure_thread.nil?
      @server_started = @server = nil
      @secure_started = @secure_server = nil
    end

    private

    # Increment the number of times a handler was used (for asserting usage)
    def increment_return(handler)
      handler.response_count = handler.response_count + 1
      return handler.response
    end

    def call_handler_match?(call_handler, env, request, request_method, request_body)
      call_arg = (call_handler.env? ? env : request_body)

      return call_handler.method_key.call(call_arg)
    end

    def complex_handler_match?(complex_handler, env, request, request_method, request_body)

      complex_handler.method_key.inject(true) do |matching, handler|
        current_handler_match = false

        if matching
          current_handler = OpenStruct.new({
            :env? => complex_handler.env?,
            :method_key => handler
          })

          case 
          when handler.respond_to?(:call) then
            current_handler_match = call_handler_match?(current_handler, env, request, request_method, request_body)    
          when handler.is_a?(Regexp) then
            current_handler_match = regex_handler_match?(current_handler, env, request, request_method, request_body)
          when handler.is_a?(String) then
            current_handler_match = endpoint_handler_match?(current_handler, env, request, request_method, request_body)
          end
        end

        matching && current_handler_match
      end
    end

    def endpoint_handler_match?(endpoint_handler, env, request, request_method, request_body)
      return request.path_info =~ /#{endpoint_handler.method_key}/i
    end

    def regex_handler_match?(regex_handler, env, request, request_method, request_body)
      return regex_handler.method_key =~ request_body
    end

    def respond_with(env)
      req = Rack::Request.new(env)
      req_meth = req.request_method.downcase.to_sym
      request_body = req.body.string

      @complex_handlers[req_meth].each do |com_handler|
        return increment_return(com_handler) if complex_handler_match?(com_handler, env, req, req_meth, request_body)
      end

      @call_handlers[req_meth].each do |call_handler|
        return increment_return(call_handler) if call_handler_match?(call_handler, env, req, req_meth, request_body) 
      end

      @regex_handlers[req_meth].each do |reg_handler|
        return increment_return(reg_handler) if regex_handler_match?(reg_handler, env, req, req_meth, request_body) 
      end

      @endpoint_handlers[req_meth].each do |end_handler|
        return increment_return(end_handler) if endpoint_handler_match?(end_handler, env, req, req_meth, request_body) 
      end

      return no_mock_error(req_meth)
    end

    def no_mock_error(request_method)
      error_message = "No matched request handlers for: #{request_method}"
      [404, { "Content-Type" => "text/plain", "Content-Length" => error_message.size.to_s }, [error_message]]
    end

    def start_server
      if @server_thread.nil? || !@server_thread.alive?
        @server_thread = Thread.new(self, @options) do |server, options| 
          Rack::Handler::WEBrick.run(server, options) { |s| @server = s }
        end

        trap(:INT){ @server_thread.terminate; @server_thread = nil }
        @server_started = true
      end
    end

    def start_secure_server
      if @secure_thread.nil? || !@secure_thread.alive?
        @secure_thread = Thread.new(self, @options) do |server, options| 
          options = options.merge(:Port => options[:SSLPort],
                                  :SSLEnable => true,
                                  :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
                                  :SSLCertificate => ssl_cert,
                                  :SSLPrivateKey => ssl_key,
                                  :SSLCertName => [[ "CN", "127.0.0.1" ]])

          Rack::Handler::WEBrick.run(server, options) { |s| @secure_server = s }
        end

        trap(:INT){ @secure_thread.terminate; @secure_thread = nil }
        @secure_started = true
      end
    end

    def ssl_key
      OpenSSL::PKey::RSA.new(File.read(File.dirname(__FILE__) + "/ssl/" + "server.key"), "httparrot")
    end

    def ssl_cert
      OpenSSL::X509::Certificate.new(File.read(File.dirname(__FILE__) + "/ssl/" + "server.crt"))
    end

  end

end