require 'open4'
require 'excon'
require 'excon/test/plugin/server/webrick'
require 'excon/test/plugin/server/unicorn'
require 'excon/test/plugin/server/puma'
require 'excon/test/plugin/server/exec'


module Excon
  module Test
    class Server
      attr_accessor :app, :server, :bind, :pid, :read, :write, :error, :started_at, :timeout

      # Methods that must be implemented by a plugin
      INSTANCE_REQUIRES = [:start]
      Excon.defaults.merge!(
        connect_timeout: 5,
        read_timeout: 5,
        write_timeout: 5
      )

      def initialize(args)
        # TODO: Validate these args
        @server = args.keys.first
        @app = args[server]
        args[:bind] ||= 'tcp://127.0.0.1:9292'
        @bind = URI.parse(args[:bind])
        @is_unix_socket = (@bind.scheme == 'unix')
        @bind.host = @bind.host.gsub(/[\[\]]/, '') unless @is_unix_socket
        if args[:timeout]
          @timeout = args[:timeout]
        else
          @timeout = 20
        end
        name = @server.to_s.split('_').collect(&:capitalize).join
        plug = nested_const_get("Excon::Test::Plugin::Server::#{name}")
        self.extend plug
        check_implementation(plug)
      end

      def open_process(*args)
        if RUBY_PLATFORM == 'java'
          @pid, @write, @read, @error = IO.popen4(*args)
        else
          @pid, @write, @read, @error = Open4.popen4(*args)
        end
        @started_at = Time.now
      end

      def elapsed_time
        Time.now - started_at
      end

      def stop
        if RUBY_PLATFORM == 'java'
          Process.kill('USR1', pid)
        else
          Process.kill(9, pid)
          Process.wait(pid)
        end

        if @is_unix_socket
          socket = @bind.path
          File.delete(socket) if File.exist?(socket)
        end

        # TODO: Ensure process is really dead
        dump_errors
        true
      end
      def dump_errors
        lines = error.read.split($/)
        while (line = lines.shift)
          case line
            when /(ERROR|Error)/
              unless line.match?(/(null cert chain|did not return a certificate|SSL_read:: internal error)/)
                in_err = true
                puts
              end
            when /^(127|localhost)/
              in_err = false
            end
          puts line if in_err
        end
      end

      private

      def nested_const_get(namespace)
        namespace.split('::').inject(Object) do |mod, klass|
          mod.const_get(klass)
        end
      end

      def check_implementation(plug)
        INSTANCE_REQUIRES.each do |m|
          unless self.respond_to? m
            raise "FATAL: #{plug} does not implement ##{m}"
          end
        end
      end
    end
  end
end
