require 'optparse'

module Rack
  class Server
    class Options
      def parse!(args)
        options = {}
        opt_parser = OptionParser.new("", 24, '  ') do |opts|
          opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"

          opts.separator ""
          opts.separator "Ruby options:"

          lineno = 1
          opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
            eval line, TOPLEVEL_BINDING, "-e", lineno
            lineno += 1
          }

          opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
            options[:debug] = true
          }
          opts.on("-w", "--warn", "turn warnings on for your script") {
            options[:warn] = true
          }

          opts.on("-I", "--include PATH",
                  "specify $LOAD_PATH (may be used more than once)") { |path|
            options[:include] = path.split(":")
          }

          opts.on("-r", "--require LIBRARY",
                  "require the library, before executing your script") { |library|
            options[:require] = library
          }

          opts.separator ""
          opts.separator "Rack options:"
          opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
            options[:server] = s
          }

          opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
            options[:Host] = host
          }

          opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
            options[:Port] = port
          }

          opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
            options[:environment] = e
          }

          opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
            options[:daemonize] = d ? true : false
          }

          opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
            options[:pid] = ::File.expand_path(f, Dir.pwd)
          }

          opts.separator ""
          opts.separator "Common options:"

          opts.on_tail("-h", "--help", "Show this message") do
            puts opts
            exit
          end

          opts.on_tail("--version", "Show version") do
            puts "Rack #{Rack.version}"
            exit
          end
        end
        opt_parser.parse! args
        options[:config] = args.last if args.last
        options
      end
    end

    def self.start
      new.start
    end

    attr_accessor :options

    def initialize(options = nil)
      @options = options
    end

    def options
      @options ||= parse_options(ARGV)
    end

    def default_options
      {
        :environment => "development",
        :pid         => nil,
        :Port        => 9292,
        :Host        => "0.0.0.0",
        :AccessLog   => [],
        :config      => ::File.expand_path("config.ru", Dir.pwd)
      }
    end

    def app
      @app ||= begin
        if !::File.exist? options[:config]
          abort "configuration #{options[:config]} not found"
        end

        app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
        self.options.merge! options
        app
      end
    end

    def self.middleware
      @middleware ||= begin
        m = Hash.new {|h,k| h[k] = []}
        m["deployment"].concat  [lambda {|server| server.server =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }]
        m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
        m
      end
    end

    def middleware
      self.class.middleware
    end

    def start
      if options[:debug]
        $DEBUG = true
        require 'pp'
        p options[:server]
        pp wrapped_app
        pp app
      end

      if options[:warn]
        $-w = true
      end

      if includes = options[:include]
        $LOAD_PATH.unshift *includes
      end

      if library = options[:require]
        require library
      end

      daemonize_app if options[:daemonize]
      write_pid if options[:pid]
      server.run wrapped_app, options
    end

    def server
      @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default
    end

    private
      def parse_options(args)
        options = default_options

        # Don't evaluate CGI ISINDEX parameters.
        # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
        args.clear if ENV.include?("REQUEST_METHOD")

        options.merge! opt_parser.parse! args
        options
      end

      def opt_parser
        Options.new
      end

      def build_app(app)
        middleware[options[:environment]].reverse_each do |middleware|
          middleware = middleware.call(self) if middleware.respond_to?(:call)
          next unless middleware
          klass = middleware.shift
          app = klass.new(app, *middleware)
        end
        app
      end

      def wrapped_app
        @wrapped_app ||= build_app app
      end

      def daemonize_app
        if RUBY_VERSION < "1.9"
          exit if fork
          Process.setsid
          exit if fork
          Dir.chdir "/"
          ::File.umask 0000
          STDIN.reopen "/dev/null"
          STDOUT.reopen "/dev/null", "a"
          STDERR.reopen "/dev/null", "a"
        else
          Process.daemon
        end
      end

      def write_pid
        ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
        at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
      end
  end
end
