#!/usr/bin/env ruby

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.  The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License.  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations
# under the License.
#

require 'rubygems'
require 'optparse'
require 'yaml'

# See if we can require +name+ and return +true+ if the library is there,
# +false+ otherwise. Note that, as a side effect, the library will be
# loaded
def library_present?(name)
  begin
    require name
    true
  rescue LoadError
    false
  end
end

DEFAULT_CONFIG = "~/.deltacloud/config"
options = {
  :env => 'development'
}
optparse = OptionParser.new do |opts|

opts.banner = <<BANNER
Usage:
deltacloudd -i <driver> [options]

Options:
BANNER
  opts.on( '-i', '--driver DRIVER', 'Driver to use') do |driver|
    ENV["API_DRIVER"] = driver
  end
  opts.on( '-r', '--hostname HOSTNAME',
           'Bind to HOST address (default: localhost)') do |host|
    ENV["API_HOST"] = host
  end
  opts.on( '-p', '--port PORT', 'Use PORT (default: 3001)') do |port|
    ENV["API_PORT"] = port
  end
  opts.on( '-P', '--provider PROVIDER', 'Use PROVIDER (default is set in the driver)') do |provider|
    ENV['API_PROVIDER'] = provider
  end
  opts.on( '-c', '--config [FILE]', 'Read provider and other config from FILE (default: ~/.deltacloud/config)') do |config|
    options[:config] = File::expand_path(config || DEFAULT_CONFIG)
  end
  opts.on( '-e', '--env ENV', 'Environment (default: "development")') { |env| options[:env] = env }
  opts.on( '-d', '--daemon', 'Run daemonized in the background, logging to SYSLOG (default: "disabled")') do
    options[:daemon] = true
  end
  opts.on( '-u', '--user USER', 'User to run daemon as. Use with -d (default: "nobody")') { |user| options[:user] = user }
  opts.on( '', '--pid PID', 'File to store PID (default: tmp/pids/thin.pid)') { |pid| options[:pid] = pid }
  opts.on( '-l', '--drivers', 'List available drivers') { |env| options[:drivers] = true }
  opts.on( '-s', '--ssl', 'Enable SSL (default: disabled)') { |ssl| options[:ssl] = true }
  opts.on( '-k', '--ssl-key KEY', 'SSL key file to use') { |key| options[:ssl_key] = key }
  opts.on( '-C', '--ssl-cert CERT', 'SSL certificate file to use') { |cert| options[:ssl_cert] = cert }
  opts.on( '-t', '--timeout TIMEOUT', 'Timeout for single request (default: 60)') do |timeout|
    ENV["API_TIMEOUT"] = timeout
  end
  opts.on( '-h', '--help', '') { options[:help] = true }

  opts.separator <<EOS

Config file:

  Server configuration can be specified in a YAML file; the file must
  contain a hash, where the keys are driver names; each driver entry is
  also a hash. Possible keys are
    :provider - the provider to use for this driver
    :user     - the user name for this driver
    :password - the password for this driver

  Note that specifying :user and :password turns off authentication on the
  server, and any request is forwarded to the backend cloud with the
  specified credentials.

  Note, for SSL you need to have API_SSL_KEY and API_SSL_CERT environment
  variables set.
EOS
end

optparse.parse!

if options[:help]
 puts optparse
 exit(0)
end

unless options[:drivers] or ENV["API_DRIVER"]
  puts "You need to specify a driver to use (-i <driver>)"
  puts "To list all available drivers try: deltacloudd --drivers"
  exit(1)
end

if options[:drivers]
  require 'server.rb'
  puts "Available drivers:\n\n"
  puts "* " + driver_config.keys.map{ |d| d.to_s }.join("\n* ")
  puts
  exit(0)
end


if options[:ssl]
  unless options[:ssl_key]
    puts "You need to set SSL key using '-k /path/to/keyfile.key'"
    exit(1)
  end
  unless options[:ssl_cert]
    puts "You need to set SSL certificate using '-C /path/to/certificate.crt'"
    exit(1)
  end
end

if options[:config]
  cfg = YAML::load(File.read(options[:config]))
  if c = cfg[ENV["API_DRIVER"].to_sym]
    ENV["API_PROVIDER"] ||= c[:provider]
    ENV["API_USER"] ||= c[:user]
    ENV["API_PASSWORD"] ||= c[:password]
  end
end

ENV["API_HOST"] = "localhost" unless ENV["API_HOST"]
ENV["API_PORT"] = "3001" unless ENV["API_PORT"]

unless options[:daemon]
  msg = "Starting Deltacloud API :: #{ENV["API_DRIVER"]} "
  msg << ":: #{ENV['API_PROVIDER']} " if ENV['API_PROVIDER']
  if options[:ssl]
    msg << ":: https://#{ENV["API_HOST"]}:#{ENV["API_PORT"]}/api"
  else
    msg << ":: http://#{ENV["API_HOST"]}:#{ENV["API_PORT"]}/api"
  end
  puts msg
  puts
end

if ENV['API_USER'] && ENV['API_PASSWORD']
  puts "Warning: API_USER and API_PASSWORD set in environment"
  puts "         anybody can access this server with your credentials"
  puts
end

dirname="#{File.dirname(__FILE__)}/.."

have_thin = library_present?('thin')
have_rerun = library_present?('rerun')

unless have_thin
  require 'rack'

  # We can't chdir with webrick so add our root directory
  # onto the load path
  $: << dirname

  # Read in config.ru and convert it to an instance of Rack::Builder
  cfgfile = File.read(File.join(dirname, 'config.ru'))
  inner_app = eval("Rack::Builder.new {(" + cfgfile + "\n )}.to_app",
                   nil, 'config.ru')

  app = Rack::Builder.new {
    use Rack::CommonLogger # apache-like logging
    use Rack::Reloader if options[:env] == "development"
    set :root, dirname # Set Sinatra root since we can't chdir to ../
    run inner_app
  }.to_app

  # There's a bug with string ports on JRuby so convert to int
  # http://jira.codehaus.org/browse/JRUBY-4868
  port = ENV["API_PORT"].to_i

  puts "=> Ctrl-C to shutdown server"
  Rack::Handler::WEBrick.run(app,
                             :Host => ENV["API_HOST"],
                             :Port => port,
                             :AccessLog => [])
else
  argv_opts = ARGV.clone
  argv_opts << ['start'] unless Thin::Runner.commands.include?(options[0])
  argv_opts << ['--address', ENV["API_HOST"] ]
  argv_opts << ['--port', ENV["API_PORT"] ]
  argv_opts << ['--rackup', 'config.ru' ]
  argv_opts << ['--chdir', dirname ]
  argv_opts << ['-e', options[:env] ]
  argv_opts << ['--timeout', ENV["API_TIMEOUT"] || '60']
  argv_opts << ['--threaded', '-D' ]
  if options[:ssl]
    argv_opts << [ '--ssl', '--ssl-key-file', options[:ssl_key], '--ssl-cert-file', options[:ssl_cert]]
  end

  if options[:daemon]
    options[:env] = "production"
    argv_opts << [ "--daemonize", "--user", options[:user] || 'nobody', "--tag", "deltacloud-#{ENV['API_DRIVER']}"]
    argv << [ "--pid", options[:pid]] if options[:pid]
  end
  argv_opts.flatten!

  if have_rerun && options[:env] == "development"
    argv_opts.unshift "thin"
    command = argv_opts.join(" ")
    topdir = File::expand_path(File::join(File::dirname(__FILE__), ".."))
    rerun = Rerun::Runner.new(command, :dir => topdir)
    rerun.start
    rerun.join
  else
    thin = Thin::Runner.new(argv_opts)
    thin.run!
  end
end
