#!/usr/bin/env ruby

require 'optparse'

env = ENV['RACK_ENV'] || 'development'
browse = false
server = nil
public_dir = 'public' if File.directory?('public')
options = {:Port => 9393, :Host => '127.0.0.1', :AccessLog => [], :Path => '/'}

opts = OptionParser.new("", 24, '  ') { |opts|
  opts.banner = "Usage: shotgun [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)") {
    $DEBUG = true
  }
  opts.on("-w", "--warn", "turn warnings on for your script") {
    $-w = true
  }

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

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

  opts.separator ""
  opts.separator "Rack options:"
  opts.on("-s", "--server SERVER", "server (webrick, mongrel, thin, etc.)") { |s|
    server = s
  }

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

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

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

  opts.separator ""
  opts.separator "Shotgun options:"

  opts.on("-O", "--browse", "open browser immediately after starting") { |browse|
    browse = true
  }

  opts.on("-u", "--url URL", "specify url path (default: /)") { |url|
    options[:Path] = url
  }

  opts.on("-P", "--public PATH", "serve static files under PATH") { |path|
    public_dir = path
  }

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

  opts.on_tail("--version", "show version") do
    require 'rack'
    puts "Rack #{Rack.version}"
    exit
  end

  opts.parse! ARGV
}

config = ARGV[0] || "config.ru"
abort "configuration #{config} not found" unless File.exist? config

# extract additional arguments from first #\ line in config file.
if File.read(config)[/^#\\(.*)/]
  opts.parse! $1.split(/\s+/)
end

# use the BROWSER environment variable or fall back to a more or less standard
# set of commands
ENV['BROWSER'] ||=
  %w[open xdg-open x-www-browser firefox opera mozilla netscape].find do |comm|
    next if comm == 'open' && `uname` !~ /Darwin/
    ENV['PATH'].split(':').any? { |dir| File.executable?("#{dir}/#{comm}") }
  end
ENV['RACK_ENV'] = env

require 'rack'
require 'rack/utils'
require 'rack/request'
require 'rack/response'
require 'rack/lint'
require 'rack/commonlogger'
require 'rack/showexceptions'

require 'shotgun'

require 'thin' if server.to_s.downcase == 'thin'
server = Rack::Handler.get(server) || Rack::Handler.default

app =
  Rack::Builder.new do
    # these middleware run in the master process.
    use Shotgun::Static, public_dir if public_dir
    use Shotgun::SkipFavicon

    # loader forks the child and runs the embedded config followed by the
    # application config.
    run Shotgun::Loader.new(config) {
      case env
      when 'development'
        use Rack::CommonLogger, STDERR  unless server.name =~ /CGI/
        use Rack::ShowExceptions
        use Rack::Lint
      when 'deployment', 'production'
        use Rack::CommonLogger, STDERR  unless server.name =~ /CGI/
      end
    }
  end

Shotgun.enable_copy_on_write

# trap exit signals
downward = false
['INT', 'TERM', 'QUIT'].each do |signal|
  trap(signal) do
    exit! if downward
    downward = true
    server.shutdown if server.respond_to?(:shutdown)
    Process.wait rescue nil
    exit!
  end
end

# load shotgun.rb in current working directory if it exists
Shotgun.preload

base_url = "http://#{options[:Host]}:#{options[:Port]}#{options[:Path]}"
puts "== Shotgun/#{server.to_s.sub(/Rack::Handler::/, '')} on #{base_url}"
server.run app, options do |inst|
  if browse
    if ENV['BROWSER']
      system "#{ENV['BROWSER']} '#{base_url}'"
    else
      abort "BROWSER environment variable not set and no browser detected"
    end
  end
end
