#!/usr/bin/env ruby

# Copyright 2008-2011 Red Hat, Inc, and individual contributors.
# 
# This is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of
# the License, or (at your option) any later version.
# 
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public
# License along with this software; if not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA, or see the FSF site: http://www.fsf.org.

require 'rubygems'

require 'thor'
require 'torquebox-rake-support'
require 'torquebox/server'
require 'torquebox/rails'

class TorqueBoxCommand < Thor

  class_option 'help', :type => :boolean, :desc => 'Display help'

  map "run" => "start"
  desc "run", "Run TorqueBox (binds to localhost, use -b to override)"
  method_option 'clustered', :type => :boolean, :desc => "Run TorqueBox in clustered mode"
  method_option 'data-directory', :type => :string, :desc => 'Override the directory TorqueBox uses to store it runtime data'
  method_option 'extra', :aliases => '-e', :type => :string, :desc => 'Extra options to pass through to JBoss AS, you will need to escape dashes with \ (e.g. \--help)'
  method_option 'max-threads', :type => :numeric, :desc => "Maximum number of HTTP threads"
  method_option 'bind-address', :aliases => '-b', :type => :string, :desc => "IP address to bind to - don't set this to 0.0.0.0 if used with --clustered"
  method_option 'port', :aliases => '-p', :type => :numeric, :desc => 'HTTP port to listen on'
  method_option 'node-name', :type => :string, :desc => 'Override the name of the node (which by default is the hostname)'
  method_option 'port-offset', :type => :numeric, :desc => 'Offset all port numbers listened on by TorqueBox by this number'
  method_option 'jvm-options', :aliases => '-J', :type => :string, :desc => 'Pass options on to the JVM'
  def start
    help(__method__) and return if options.help
    TorqueBox::Server.setup_environment
    TorqueBox::DeployUtils.run_server(:clustered => options.clustered,
                                      :max_threads => options['max-threads'],
                                      :bind_address => options['bind-address'],
                                      :port => options['port'],
                                      :port_offset => options['port-offset'],
                                      :pass_through => options['extra'],
                                      :node_name => options['node-name'],
                                      :data_directory => options['data-directory'],
                                      :jvm_options => options['jvm-options'])
  end

  desc "rails ROOT", "Create a Rails application at ROOT using the TorqueBox Rails template, or apply the TorqueBox template to an existing application at ROOT."
  def rails(root = ".", *args)
    help(__method__) and return if options.help
    ARGV.shift
    # Ensure the root defaults to the current directory if the user
    # doesn't specify one but passes in additional args
    if root.start_with?('-')
      args.unshift( root )
      root = '.'
    end
    TorqueBox::Server.setup_environment
    if File.exist?( File.join(root, 'config', 'environment.rb') )
      TorqueBox::Rails.apply_template( root )
    else
      TorqueBox::Rails.new_app( root )
    end
  end

  desc "archive ROOT", "Create a nice self-contained application archive"
  method_option :deploy, :type => :boolean, :desc => "Deploy the archive to TORQUEBOX_HOME."
  method_option :package_gems, :type => :boolean, :desc => "Include all Bundler gem dependencies in the archive."
  method_option :package_without, :type => :array, :desc => "Package without these bundler groups.", :default => "development test assets"
  method_option :precompile_assets, :type => :boolean, :desc => "Precompile all assets (Rails-specific)."
  method_option :exclude, :type => :array, :desc => "Exclude files from the archive with regexps."
  long_desc <<-EOS
    Creates an application archive containing all of your application dependencies.
    The archive can be deployed to TorqueBox with the --deploy option or by
    hand after the archive file, known as a .knob, has been created by using
    `torquebox deploy myapp.knob`.

    The exclude parameter takes a list of Ruby-compatible regular expressions of
    paths to exclude. The paths are implicitly anchored at the beginning. For
    example, `torquebox archive foo --exclude public/500.html config/.+` will
    exclude public/500.html and everything from the config directory.
  EOS
  def archive(root = Dir.pwd)
    help(__method__) and return if options.help
    root = Dir.pwd if (root == '.')
    TorqueBox::Server.setup_environment
    archive_name = TorqueBox::DeployUtils.archive_name( root )
    archive_options = {
      :name => archive_name,
      :app_dir => root,
      :precompile_assets => options.precompile_assets,
      :package_gems => options.package_gems,
      :package_without => options.package_without
    }
    unless options.exclude.nil?
      archive_options[:exclude] = options.exclude.join(',')
    end
    path = TorqueBox::DeployUtils.create_archive(archive_options)
    puts "Created archive: #{path}"
    if options.deploy
      puts "Deployed: #{archive_name}"
      puts "    into: #{ENV['TORQUEBOX_HOME']}"
      TorqueBox::DeployUtils.deploy_archive( :archive_path => path )
    end
  end

  desc "deploy ROOT", "Deploy an application to TorqueBox"
  long_desc <<-EOS
    Deploy an application to TorqueBox. The ROOT argument should point to either
    a directory containing the application you want to deploy, a -knob.yml file,
    a .knob archive, or any Java deployable artifact (.war, .ear, etc).
  EOS
  method_option :context_path, :type => :string, :desc => "Context Path (ex: /, /my_app)"
  method_option :env, :type => :string, :desc => "Application Environment (ex: development, test, production)"
  method_option :name, :type => :string, :desc => "The desired name of the deployment artifact (ex: foo)"
  def deploy(root = ".")
    help(__method__) and return if options.help
    TorqueBox::Server.setup_environment
    root = File.expand_path(root)
    opts = {:root => root}.merge(options)
    descriptor = TorqueBox::DeployUtils.basic_deployment_descriptor(opts)
    deployed_name, deploy_dir = TorqueBox::DeployUtils.deploy_yaml(descriptor, opts)
    failed_file = File.join( deploy_dir, "#{deployed_name}.failed" )
    if File.exists? failed_file
      puts "Removing failed descriptor: #{failed_file}" 
      FileUtils.rm( failed_file ) 
    end
    puts "Deployed: #{deployed_name}"
    puts "    into: #{deploy_dir}"
  end

  desc "undeploy ROOT", "Undeploy an application from TorqueBox"
  method_option :name, :type => :string, :desc => "The name of the artifact to undeploy (ex: foo)"
  def undeploy(root = ".")
    help(__method__) and return if options.help
    TorqueBox::Server.setup_environment
    root = File.expand_path(root)
    opts = {:root => root}.merge(options)
    deploy_name, deploy_dir = TorqueBox::DeployUtils.undeploy_yaml(opts)
    unless deploy_name
      deploy_name, deploy_dir = TorqueBox::DeployUtils.undeploy_archive(opts)
    end

    if deploy_name
      puts "Undeployed: #{deploy_name}"
      puts "      from: #{deploy_dir}"
    else
      puts "Nothing to undeploy"
    end
  end

  desc "list", "List applications deployed to TorqueBox and their deployment status"
  def list(root = ".")
    help(__method__) and return if options.help
    TorqueBox::Server.setup_environment
    apps = TorqueBox::DeployUtils.deployment_status
    puts "Nothing deployed." if apps.empty?
    apps.each do |k,v|
      puts k
      puts "  Descriptor: #{v[:descriptor]}"
      puts "  Status: #{v[:status]}"
    end
  end

  desc "cli", "Run the JBoss AS7 CLI"
  def cli
    help(__method__) and return if options.help
    TorqueBox::Server.setup_environment
    options = "--connect"
    if RbConfig::CONFIG['host_os'] =~ /mswin/
      path = File.join(ENV['JBOSS_HOME'], "bin\\jboss-cli")
      exec(path, options)
    else
      path = "/bin/sh #{File.join(ENV['JBOSS_HOME'], 'bin/jboss-cli.sh')}"
      exec "#{path} #{options}"
    end
  end

  desc "env [VARIABLE]", "Display TorqueBox environment variables"
  def env(variable=nil)
    help(__method__) and return if options.help
    TorqueBox::Server.setup_environment
    env_keys = %w(TORQUEBOX_HOME JBOSS_HOME JRUBY_HOME)
    if variable.nil?
      env_keys.each { |key| shell.say "#{key}=#{ENV[key]}" }
    else
      key = env_keys.find { |key| variable.downcase == key.downcase }
      shell.say(ENV[key]) unless key.nil?
    end
  end

  map "exec" => "tb_exec"
  desc "exec [KNOB_FILE] [COMMAND]", "Execute a command within the context of a TorqueBox application"
  method_option :no_bundle, :aliases => '-nb', :type => :boolean, :desc => "Run without `bundle exec`"
  def tb_exec(knob_file, command)
    help(__method__) and return if options.help
    TorqueBox::Server.setup_environment
    jruby_path = File.join(ENV['JRUBY_HOME'], "bin")
    knob_path = File.expand_path(knob_file)
    bundle_exec = options.no_bundler_exec ? "" : "bundle exec"
    rb_version = case RUBY_VERSION
                 when /^1\.8\./ then '1.8'
                 when /^1\.9\./ then '1.9'
                 when /^2\.0\./ then '1.9' # 2.0 gems get put under 1.9
                 end
    ENV['PATH'] = "#{ENV['PATH']}:#{jruby_path}"
    Dir.mktmpdir do |tmpdir|
      commands = ["cd #{tmpdir}", "jar -xf #{knob_path}"]
      commands << "chmod +x vendor/bundle/jruby/#{rb_version}/bin/*"
      commands << "jruby -S #{bundle_exec} #{command}"
      exec(commands.join(" && "))
    end
  end

end

TorqueBoxCommand.start
