#!/usr/bin/env ruby
require 'drb'
require 'rubygems'
require 'cmdparse'
require 'yaml'
require 'highline'

UI = HighLine.new
DEFAULT_CONFIG = "conf/scgi.yaml"

def safe(error)
    begin
        yield
    rescue Object
        STDERR.puts("ERROR: #{error}: #$!")
        exit 1
    end
end

def load_config(file)
    safe("Could not load config") do
        return YAML.load_file(file)
    end
end

def connect(url)
    conn = nil
    safe("Can't connect to #{url}") do
         conn = DRbObject.new(nil, url)
    end
    
    safe("Failed communicating with #{url}") do
        yield conn
    end
end

def deduce_url(settings)
    settings[:control_url] || load_config(settings[:config] || DEFAULT_CONFIG)[:control_url]
end

def defaults(settings)
    defaults = nil
    if settings[:merge]
        UI.say("Merging with previous settings.")
        defaults = load_config(settings[:config] || DEFAULT_CONFIG)
    else
        defaults = {
        :env => "production",
        :host => "127.0.0.1",
        :port => 9999,
        :logfile => "log/scgi.log",
        :config => DEFAULT_CONFIG }
    end
    
    settings = defaults.merge(settings)
    # fix up the stuff that's not quite right yet
    settings[:control_url] = "druby://127.0.0.1:#{settings[:port]-1000}"
    settings[:port] = settings[:port].to_i
    settings[:throttle] = settings[:throttle].to_i if settings[:throttle]
    settings[:maxconns] = settings[:maxconns].to_i if settings[:maxconns]
    
    return settings
end
    

def configure(settings)
    settings = defaults(settings)
    
    pass = settings[:password] || UI.ask("What password do you want? ")
    salting = ('a' .. 'z').to_a + ('A' .. 'Z').to_a + ('0' .. '9').to_a
    settings[:password] = pass.crypt(salting[rand(salting.length)] + salting[rand(salting.length)])
    
    # great, they are not idiots.  Well, they can read --help at least.
    safe("Could not write config") do
        open(settings[:config],"w") {|f| f.write(YAML.dump(settings)) }
        UI.say("Configuration settings written to #{settings[:config]}")
    end
end

def password
    if not $password
        $password = UI.ask("Password: ")
    end
    $password
end

def start(cmd, config)
    fork do
        exec cmd, config
    end
end

def status(url)
    connect(url) do |conn|
        s = conn.status(password)
        times = s[:systimes]
        
        puts <<-END
#{UI.color("Status as of #{s[:time]}:",:green,:bold)}
PID: #{s[:pid]}\tStarted: #{s[:started]}\tEnvironment: #{s[:env]}
Connected Requests: #{s[:conns]}
Conns/Second: #{s[:conns_second] || "Not Set"}
Max Simultaneous Conns: #{s[:max_conns]}
Shutdown Started: #{s[:shutdown]}
Processing Time:  #{times.utime} #{times.stime} #{times.cutime} #{times.cstime}
Current Settings:
#{s[:settings].to_yaml.gsub(/:([a-z])/, '   \1')}
END
    end
end

def reconfigure(url)
    connect(url) do |conn|
        conn.reconfigure(password)
    end
end

def stop(url, force)
    connect(url) do |conn|
        conn.shutdown(password, force)
    end
end

def restart(url, force)
    connect(url) do |conn|
        conn.restart(password, force)
    end
end

def monitor(url)
    connect(url) do |conn|
        while true
            sleep 3
            puts "\e[2J"
            status(url)
        end
    end
end


def make_command(parent, name, desc, options)
    cmd = CmdParse::Command.new(name, false )
    cmd.short_desc = desc
    settings = {}
    cmd.options = CmdParse::OptionParserWrapper.new do |opt|
        options.each do |short, long, info, symbol|
            opt.on(short, long, info) {|val| settings[symbol] = val}
        end
    end
    cmd.set_execution_block do |args|
        yield(settings, args)
    end
    parent.add_command(cmd)
end


cmd = CmdParse::CommandParser.new( true )
cmd.program_name = "scgi_nitro"
cmd.program_version = [0, 4, 0]
cmd.options = CmdParse::OptionParserWrapper.new do |opt|
    opt.separator "Global options:"
    opt.on("--verbose", "Be verbose when outputting info") {|t| $verbose = true }
end

cmd.add_command( CmdParse::HelpCommand.new )
cmd.add_command( CmdParse::VersionCommand.new )

make_command(cmd, 'config', "Configure the SCGI servers", 
[['-e','--env STRING','Nitro environment', :env],
['-h','--host STRING', 'IP address to bind as server', :host],
['-p','--port NUMBER', 'Port to bind to (starts at 9999)', :port],
['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
['-l','--log-file PATH', 'Use a different log from from log/scgi.log', :logfile],
['-t','--throttle NUMBER', 'Max conn/second to allow.', :conns_second],
['-m','--max-conns NUMBER', 'Max simultaneous connections before the busy message', :maxconns],
['-P','--moron-mode PASSWORD', 'You are an idiot and you want your password on the command line', :password],
['-M','--merge', 'Merge new settings with previous rather than defaults', :merge],
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
    configure(settings)
end

make_command(cmd, 'start', "Start the application",
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
    cmd = File.dirname(__FILE__) + "/scgi_service"
    start(cmd, settings[:config] || DEFAULT_CONFIG)
end

make_command(cmd, 'reconfig', "Reconfigure the SCGI servers with a new config", 
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
    reconfigure(deduce_url(settings))
end

make_command(cmd, 'status', "Get status",
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
    status(deduce_url(settings))
end

make_command(cmd, 'stop', "Stop the application",
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config],
['-f','--force', 'Forced shutdown rather than graceful (default graceful)', :force]]) do |settings, args|
    stop(deduce_url(settings), settings[:force] || false)
end

make_command(cmd, 'restart', "Restart the application",
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config],
['-f','--force', 'Forced shutdown rather than graceful (default graceful)', :force]]) do |settings, args|
    restart(deduce_url(settings), settings[:force] || false)
end

make_command(cmd, 'monitor', "Monitor the application",
[['-u','--control-url URL', 'DRuby URL to run control on (same as SCGI -1000)', :control_url],
['-c','--config PATH', 'Config file to use (#{DEFAULT_CONFIG})', :config]]) do |settings, args|
    monitor(deduce_url(settings))
end

cmd.parse