require 'optparse'

require 'glue/configuration'
require 'nitro/compiler'

#require 'facet/kernel/autoreload'
require 'glue/autoreload'

module Nitro

# The Runner is a helper class that encapsulates a web 
# application and is responsible for launching the 
# application in different modes. 
#
# The runner provides default parsing of command line
# and environment parameters. 
#
# The default execution modes are:
#
# :debug, :stage, :live
#
# You can implement your own, custom version of the Runner
# to run your custom web applications.
#--
# FIXME: Rename/Reimplement this class.
#++

class Runner

  # The adapter used.
  
  setting :adapter, :default => :webrick, :doc => 'The web adapter'
  
  # Execution mode = (:debug, :stage, :live) 
  #
  # [:debug]
  #    useful when debugging, extra debug information
  #    is emmited, actions, templates and shaders are
  #    reloaded, etc. The execution speed of the application
  #    is impaired.
  #
  # [:stage]
  #    test the application with live parameters 
  #    (typically on a staging server).
  #
  #  [:live]
  #    use the parameters for the live (production)
  #    server. Optimized for speed.
  #
  # The default mode is :debug

  setting :mode, :default => :debug, :doc => 'The execution mode'
          
  # :start, :stop, :restart

  attr_accessor :action
  
  # The server used to run this web application.
  # :webrick, :nitro, :lhttp, :apache, :mod_apache 
  #
  # At the moment only :webrick and :lhttp are available.
  
  attr_accessor :server

  # Run as daemon.

  attr_accessor :daemon

  # Spidering mode. Acceptable values are :crawl, :render
  # and false.

  attr_accessor :spider 

  # Parse the command line arguments and the environment 
  # parameters to setup the application.
  
  def setup_options
    @action ||= :start
    @server ||= :webrick
    @daemon = false
    @spider = false

    # Setup from environment

    if mode = ENV['NITRO_MODE']
      self.class.mode = mode.to_sym
    else
      self.class.mode ||= :debug
    end

    # Setup from command line arguments.
    
    parser = OptionParser.new do |opts|

      opts.banner = 'Usage: run.rb [options]'
      opts.separator ''
      opts.separator 'Specific options:'

      opts.on('-s', '--start', 'Start application.') do 
        @action = :start  
      end

      opts.on('-S', '--stop', 'Stop application.') do
        @action = :stop
      end

      opts.on('-r', '--restart', 'Restart application.') do
        @action = :restart
      end

      opts.on('-d', '--daemon', 'Run application as a daemon.') do
        @daemon = true
      end
      
      opts.on('-D', '--debug', 'Run application in debug mode.') do
        self.class.mode = :debug
      end

      opts.on('-T', '--stage', 'Run application in stage mode.') do
        self.class.mode = :stage
      end

      opts.on('-L', '--live', 'Run application in live mode.') do
        self.class.mode = :live
      end

      opts.on('--address IP', 'Force the server to run on this address.') do |a|
        @server_address = a
      end

      opts.on('--port PORT', 'Force the server to run on this port.') do |p|
        @server_port = p.to_i
      end

      opts.on('-w', '--webrick', 'Use a webrick server [default].') do
        @server = :webrick
      end

      opts.on('-m', '--mongrel', 'Use the Mongrel Ruby server.') do
        @server = :mongrel
        # FIXME: handle logging.
      end

      opts.on('-l', '--lhttpd', 'Use a lighttpd server (FastCGI).') do
        @server = :lhttpd
        Logger.set(Logger.new('log/app.log'))
      end

      opts.on('-l', '--lhttpd-scgi', 'Use the SCGI adapter (Lighttpd).') do
        @server = :lhttpd_scgi
        Logger.set(Logger.new('log/app.log'))
      end

      opts.on('-a', '--apache', 'Use an apache server.') do
        @server = :apache
        Logger.set(Logger.new('log/app.log'))
      end

      opts.on('--apache-cgi', 'Use the CGI adapter (Apache)') do
        @server = :cgi
        Logger.set(Logger.new('log/app.log'))
      end

      opts.on('--scgi', 'Use the generic SCGI adapter') do
        @server = :scgi
      end
      
      opts.on('-C', '--console', 'Start a console attached to an instance of the application.') do
        if RUBY_PLATFORM =~ /mswin32/
          irb_name = 'irb.bat'
        else
          irb_name = 'irb'
        end
        ENV['NITRO_INVOKE'] = 'irb'
        $NITRO_NO_INVOKE = true
        @server = :console
        conf_file = File.basename(caller.last.split(':').first)
        exec "#{irb_name} -r #{conf_file} -r irb/completion --noinspect"
        exit
      end

      opts.on('--crawl', 'Crawl the application.') do
        @server = :webrick
        @spider = :crawl
      end

      opts.on('--render', 'Crawl the application and render all pages as static html files.') do
        @server = :webrick
        @spider = :render
      end

      opts.on('--record FILENAME', 'Record the application server session to the given file.') do |filename|
        @server = :webrick
        $record_session_filename = filename || 'vcrsession.yaml'
      end

      opts.on('--playback FILENAME', 'Playback a previously recorded session from the given file.') do |filename|
        @server = :webrick
        $playback_session_filename = filename || 'vcrsession.yaml'
      end

      opts.on_tail('-v', '--version', 'Show version.') do 
        puts "Nitro #{Nitro::Version}"
        exit
      end

      opts.on_tail('-h', '--help', 'Show this message.') do
        puts opts
        exit
      end
      
    end

    parser.parse!(ARGV)

    return self
  end

  # Setup the declared execution mode,
  # using the passed configuration parameters.
  
  def setup_mode
    case self.class.mode
      when :debug
        setup_debug

      when :stage
        setup_stage
        
      when :live
        setup_live
    end
    
    # Special setup for distributed sessions.
=begin    
    if defined?(Session) and defined?(DRbObject)
      if Session.store.is_a?(DRbObject)
        system('ruby ' + File.join(Nitro::LibPath, 'session', 'drbserver.rb') + ' --address #{Session.drb_address} --port #{Session.drb_port} --daemon')
      end
    end
=end    
  end

  # Setup in debug mode.
  
  def setup_debug
    $DBG = true
    Compiler.reload = true
    autoreload(3)
    Caching.caching_enabled = false
    
    load_external_configuration(:debug)
  end

  def setup_stage
    $DBG = false
    Compiler.reload = true
    autoreload(3)    
    Logger.set(Logger.new('log/app.log'))
    
    load_external_configuration(:stage)    
  end

  def setup_live
    $DBG = false
    
    # Enable the reloading even on live apps by default.
    # But have a longer thread sleep time.
    # If you really need sligthly faster dispatching enable
    # reloading (Compiler.reload = false)

    Compiler.reload = true
    autoreload(2 * 60)    
    Logger.set(Logger.new('log/app.log'))
    
    load_external_configuration(:live)        
    load_external_configuration(:production)        
  end
  alias_method :setup_production, :setup_live

  # ...
  
  def invoke(server)
    case ENV['NITRO_INVOKE']
    when 'fcgi_proc'
      require 'nitro/adapter/fastcgi'
      FastCGI.start(server)

    when 'scgi_proc'
      require 'nitro/adapter/scgi'
      Scgi.start(server)
      
    when 'cgi_proc'
      require 'nitro/adapter/cgi'
      CgiAdapter.start(server)
      
    when 'irb'
      require 'nitro/adapter/console'
      $server = server
      $app = ConsoleAdapter.new(server)
      
    else
      invoke_server(server)
    end
  end
  
  # ...
  
  def invoke_server(server)
    spider_thread = nil
    
    # FIXME refactor !
    server.address = @server_address if @server_address
    server.port = @server_port if @server_port
    
    case @action
    when :start
      
      case @spider
        when :render
          spider_thread = Thread.new do
            sleep(6)
            `wget -k -m -p #{server.address}:#{server.port} -directory-prefix=rendered`
          end 
        when :crawl
          spider_thread = Thread.new do
            sleep(6)
            `wget -m --spider #{server.host}:#{server.port}`
          end 
      end

      @server ||= Runner.adapter

      puts "\n==> Setup for #{self.class.mode} mode"

      case @server
        when :webrick
          require 'nitro/adapter/webrick'
          puts "==> Listening at #{server.address}:#{server.port}. [WEBRICK]"
          puts "==> Press Ctrl-C to shutdown; Run with --help for options.\n\n"    
          Webrick.start(server)

        when :mongrel
          require 'nitro/adapter/mongrel'
          puts "==> Listening at #{server.address}:#{server.port}. [MONGREL]"
          puts "==> Press Ctrl-C to shutdown; Run with --help for options.\n\n"

          Mongrel.start(server)

        when :lhttpd
          require 'nitro/adapter/fastcgi'
          puts "==> Launching lighttpd (FastCGI)."
          `lighttpd -f conf/lhttpd_fcgi.conf`

        when :lhttpd_scgi
          require 'nitro/adapter/scgi'
          puts "==> Launching lighttpd (SCGI)."
          `lighttpd -f conf/lhttpd_scgi.conf`

        when :apache
          require 'nitro/adapter/fastcgi'
          puts "==> Launching apache (FastCGI)."
          `apachectl -d #{Dir.pwd} -f conf/apache.conf -k start`

        when :cgi
          require 'nitro/adapter/cgi'
          puts "==> Using standard CGI. Please look into using Fast/Scgi"


        when :scgi
          require 'nitro/adapter/scgi'
          SCGI.start(server)
        
      end
      
    when :stop

      case @server
      when :webrick
        
      when :lhttpd

      when :apache
        `apachectl -d #{Dir.pwd} -f conf/apache.conf -k stop`
      
      end

    end
  end

  # :section: Utilities
  
  # Run this proccess as a daemon (UNIX only).
  
  def daemonize
    require 'daemons/daemonize'
    pwd = Dir.pwd
    Daemonize.daemonize(File.join(pwd, 'log/app.log'))
    # Restore the original pwd (daemonize sets the 
    # pwd to '/').
    Dir.chdir(pwd)
    # Set the logger to a file (daemonize closes the
    # std streams).
    Logger.set(Logger.new('log/app.log'))
  end

  # Attempt to load external configuration in Ruby or
  # YAML format. The files:
  #
  # * conf/mode.rb
  # * conf/mode.yaml
  #
  # are considered.
  
  def load_external_configuration(mode = :debug)
    
    # require "conf/#{mode}.rb"
    ruby_conf = "conf/#{mode}.rb"
    load ruby_conf if File.exist?(ruby_conf)

    # Try to configure from a yaml file.
    yml_conf = "conf/#{mode}.yml"
    Configuration.load yml_conf if File.exist?(yml_conf)

  end

end

Run = Runner

end

# * George Moschovitis <gm@navel.gr>
# * James Britt <james_b@neurogami.com>