#!/usr/bin/env ruby

while (index = ARGV.index('-I'))
  _, path = ARGV.slice!(index, 2)
  $LOAD_PATH << path
end

# Always add ./lib to the load path as that is the default location for Ruby
# files in a project.
$LOAD_PATH << File.expand_path('./lib')

require 'optparse'
require 'fileutils'
require 'logger'

require 'agoo'

# Prepare the +usage+ string.
# Basically a banner text and description passed on +OptionParser+.
usage = %{
Usage: agoo [options] [<handler_class>@<path>]... [<file>.ru | <file>.rb]

version #{Agoo::VERSION}

Agoo is a Ruby web server. It can be run as a standalone application using
this application. The handler/class arguments must have the form of
<path>@<class> where the class is a Ruby class and the path is the URL path
that will be directed to the class or an instance of the class provided. If
the class implements a call() or on_request() method it will be used directly
otherwise an instance of the class will be created. The new method must take no
arguments and have either a call() method or a on_request() method.

Files with a .rb or .ru can also be listed. If no files or path-class pairs are
listed then Agoo will look for either a config.ru or a config.rb file to load.
In that case a kernel level run method is defined to start the server.

Example:

  agoo -I example -r simple /simple@Simple

}

@verbose = 1
@port = 0
@binds = []
@root = '.'
@log_dir = nil
@classic = true
@console = true
@colorize = true
@threads = 0
@workers = 0
@first = false
@graphql = nil
@ssl_cert = nil
@ssl_key = nil


@opts = OptionParser.new(usage)
@opts.on('-h', '--help', 'Show this display.')                                       { puts @opts.help; Process.exit!(0) }
@opts.on('-s', '--silent', 'Silent.')                                                { @verbose = 0 }
@opts.on('-v', '--verbose', 'Increase verbosity.')                                   { @verbose += 1 }
@opts.on('-f', '--root_first', 'Check the root directory before the handle paths.')  { @first = true }
@opts.on('-p', '--port PORT', Integer, 'Port to listen on.')                         { |p| @port = p }
@opts.on('-b', '--bind URL', String, 'URL to receive connections on.')               { |b| @binds << b }
@opts.on('-d', '--dir DIR', String, 'Directory to serve static assets from.')        { |d| @root = d }
@opts.on('-g', '--graphql PATH', String, 'URL path for GraphQL requests.')           { |g| @graphql = g }
@opts.on('-r', '--require FILE', String, 'Ruby require.')                            { |r| require r }
@opts.on('-t', '--threads COUNT', Integer, 'Number of threads to use.')              { |t| @threads = t }
@opts.on('-w', '--workers COUNT', Integer, 'Number of workers to use.')              { |w| @workers = w }
@opts.on('--ssl_cert', String, 'SSL certificate.')                                   { |c| @ssl_cert = c }
@opts.on('--ssl_key', String, 'SSL key.')                                            { |k| @ssl_key = k }
@opts.on('--log-dir DIR', String, 'Log file directory.')                             { |d| @log_dir = d }
@opts.on('--[no-]log-classic', 'Classic log entries instead of JSON.')               { |b| @classic = b }
@opts.on('--[no-]log-console', 'Display log entries on the console.')                { |b| @console = b }
@opts.on('--[no-]log-colorize', 'Display log entries in color.')                     { |b| @colorize = b }

handler_paths = @opts.parse(ARGV)

@threads = 0 if @threads < 0
@workers = 1 if @workers < 1
@run_file = nil

if handler_paths.empty?
  if File.exist?('config.ru')
    @run_file = 'config.ru'
  elsif File.exist?('config.rb')
    @run_file = 'config.rb'
  end
else
  handler_paths.each { |hp|
    unless hp.include?('@')
      @run_file = hp
      break
    end
  }
end

if @run_file.nil?
  @port = 6464 if 0 == @port && @binds.empty?
  Agoo::Log.configure(dir: @log_dir,
                      console: @console,
                      classic: @classic,
                      colorize: @colorize,
                      states: {
                        INFO: 1 <= @verbose,
                        DEBUG: 3 <= @verbose,
                        connect: 2 <= @verbose,
                        request: 2 <= @verbose,
                        response: 2 <= @verbose,
                        eval: 2 <= @verbose,
                        push: 2 <= @verbose,
                      })

  Agoo::Server.init(@port,
                    @root,
                    bind: @binds,
                    thread_count: @threads,
                    worker_count: @workers,
                    root_first: @first,
                    ssl_cert: @ssl_cert,
                    ssl_key: @ssl_key,
                    graphql: @graphql)

  puts "Agoo #{Agoo::VERSION} is listening on port #{@port}. Path mappings are:" if 1 <= @verbose

  handler_paths.each { |hp|
    path, classname = hp.split('@')
    if classname.nil? || path.nil? || classname.empty? || path.empty?
      raise "Invalid handler/path specification. Both a class and path must be present."
    end
    c = Object.const_get(classname)
    if c.respond_to?(:call) || c.respond_to?(:on_request)
      Agoo::Server.handle(nil, path, c)
    else
      Agoo::Server.handle(nil, path, c.new)
    end
    puts "  #{path} => #{classname}" if 1 <= @verbose
  }

  puts "Agoo is only serving static files in '#{@root}'." if handler_paths.empty?
  Agoo::Server.start()

else
  @port = 9292 if 0 == @port
  def run(handler, options={})
    options[:port] = @port unless options.has_key?(:port) || options.has_key?(:p)
    options[:root] = @root unless options.has_key?(:root) || options.has_key?(:dir) || options.has_key?(:d)
    options[:workers] = @workers unless options.has_key?(:workers) || options.has_key?(:wc)
    options[:root_first] = @first unless options.has_key?(:root_first) || options.has_key?(:f) || options.has_key?(:rmux)
    options[:graphql] = @graphql unless options.has_key?(:graphql) || options.has_key?(:g)
    options[:ssl_cert] = @ssl_cert unless options.has_key?(:ssl_cert) || options.has_key?(:ssl_cert)
    options[:ssl_key] = @ssl_key unless options.has_key?(:ssl_key) || options.has_key?(:ssl_key)
    unless @binds.empty?
      if options.has_key?(:b)
        b2 = options[:b]
        b2 = b2.split(',') if b2.is_a?(String)
        @binds = @binds + b2
        options.delete(:b)
      elsif options.has_key?(:bind)
        b2 = options[:bind]
        b2 = b2.split(',') if b2.is_a?(String)
        @binds = @binds + b2
        options.delete(:bind)
      end
      options[:bind] = @binds
    end
    unless options.has_key?(:silent) || options.has_key?(:verbose) || options.has_key?(:debug)
      if 0 == @verbose
        options[:silent] = true
      elsif 2 == @verbose
        options[:verbose] = true
      elsif 3 <= @verbose
        options[:debug] = true
      end
    end
    options[:log_dir] = @log_dir unless options.has_key?(:log_dir)
    unless options.has_key?(:log_classic) || options.has_key?(:no_log_classic)
      if @classic
        options[:log_classic] = true
      else
        options[:no_log_classic] = true
      end
    end
    unless options.has_key?(:log_console) || options.has_key?(:no_log_console)
      if @console
        options[:log_console] = true
      else
        options[:no_log_console] = true
      end
    end
    unless options.has_key?(:log_colorize) || options.has_key?(:no_log_colorize)
      if @colorize
        options[:log_colorize] = true
      else
        options[:no_log_colorize] = true
      end
    end
    ::Rack::Handler::Agoo.run(handler, options)
  end

  path = File.expand_path(@run_file)
  if @run_file.end_with?('.ru')
    begin
      require 'rack'
      puts "  Racking #{@run_file}" if 1 <= @verbose
      app = Rack::Builder.new {
        eval(File.open(path).read)
      }
      run app
    rescue LoadError
      puts "  Running #{path}" if 1 <= @verbose
      load(path)
    end
  else
    puts "  Running #{path}" if 1 <= @verbose
    load(path)
  end
end
