###############################################
# mongrel_rails_svc
#
# This is where Win32::Daemon resides.
###############################################
require 'rubygems'
require 'mongrel'
require 'mongrel/rails'
require 'optparse'
require 'win32/service'

# We need to use OpenProcess and SetProcessAffinityMask on WinNT/2K/XP for
# binding the process to each cpu.
# Kernel32 Module Just for Win32 :D
require 'dl/win32'

module Kernel32
  [
    %w/OpenProcess            LLL    L/,
    %w/SetProcessAffinityMask LL     L/,
  ].each do |fn|
    const_set fn[0].intern, Win32API.new('kernel32.dll', *fn)
  end
  
  PROCESS_ALL_ACCESS = 0x1f0fff
  
  module_function

  def set_affinity(pid, cpu)
    handle = OpenProcess.call(PROCESS_ALL_ACCESS, 0, pid)
  
    # CPU mask is a bit weird, hehehe :)
    # default mask for CPU 1
    mask = 1
    mask = %w{1 2 4 8 16 32 64 128}[cpu.to_i - 1] if cpu.to_i.between?(1, 8)
    
    SetProcessAffinityMask.call(handle, mask.to_i)
  end
end
# End Kernel32 Module

DEBUG_LOG_FILE = File.expand_path(File.dirname(__FILE__) + '/debug.log') 
DEBUG_THREAD_LOG_FILE = File.expand_path(File.dirname(__FILE__) + '/debug_thread.log') 

def dbg(msg)
  File.open(DEBUG_LOG_FILE,"a+") { |f| f.puts("#{Time.now} - #{msg}") }
end
  
def dbg_th(msg)
  File.open(DEBUG_THREAD_LOG_FILE,"a+") { |f| f.puts("#{Time.now} - #{msg}") }  
end

# This class encapsulate the handler registering, http_server and working thread
# Is standalone, so using MongrelRails in your app get everything runnig 
# (in case you don't want use mongrel_rails script)
class MongrelRails
  def initialize(ip, port, rails_root, docroot, environment, mime_map, num_procs, timeout)
    dbg "mongrelrails_initialize entered"

    @ip = ip
    @port = port
    @rails_root = rails_root
    @docroot = docroot
    @environment = environment
    @mime_map = mime_map
    @num_procs = num_procs
    @timeout = timeout

    dbg "mongrelrails_initialize left"
  end
  
  def delayed_initialize
    dbg "delayed_initialize entered"
    
    @rails = configure_rails
    
    # start up mongrel with the right configurations
    @server = Mongrel::HttpServer.new(@ip, @port, @num_procs.to_i, @timeout.to_i)
    @server.register("/", @rails)
    
    dbg "delayed_initialize left"
    
  end
  
  def load_mime_map
    dbg "load_mime_map entered"

    mime = {}

    # configure any requested mime map
    if @mime_map
      puts "Loading additional MIME types from #@mime_map"
      mime.merge!(YAML.load_file(@mime_map))

      # check all the mime types to make sure they are the right format
      mime.each {|k,v| puts "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }
    end

    dbg "load_mime_map left"
    
    return mime
  end

  def configure_rails
    dbg "configure_rails entered"

    Dir.chdir(@rails_root)

    
    ENV['RAILS_ENV'] = @environment
    require 'config/environment'

    # configure the rails handler
    rails = Mongrel::Rails::RailsHandler.new(@docroot, load_mime_map)
    
    dbg "configure_rails left"

    return rails
  end

  def start_serve
    begin
      dbg "start_serve entered"
      
      @runner = Thread.new do 
        dbg_th "runner_thread suspended"
        Thread.stop
        
        dbg_th "runner_thread resumed"
        dbg_th "runner_thread acceptor.join"
        @server.acceptor.join
      end
      
      dbg "server.run"
      @server.run
      
      dbg "runner.run"
      @runner.run
      
      dbg "start_serve left"    
    rescue
      dbg "ERROR: #$!\r\n"
      dbg $!.backtrace.join("\r\n")
    end
  end
  
  def stop_serve
    dbg "stop_serve entered"

    if @runner.alive?
      dbg "killing thread"
      @runner.kill
    end
    
    @server.stop

    dbg "stop_serve left"
  end
end

class RailsDaemon < Win32::Daemon
  def initialize(rails)
    dbg "daemon_initialize entered"

    @rails = rails

    dbg "daemon_initialize left"
  end

  def service_init
    dbg "service_init entered"
    
    @rails.delayed_initialize
    
    dbg "service_init left"    
  end
  
  def service_main
    dbg "service_main entered"

    dbg "rails.start_serve"
    @rails.start_serve
    
    dbg "while RUNNING"
    while state == RUNNING
      sleep 1
    end
    dbg "state !RUNNING"

    dbg "rails.stop_serve"
    @rails.stop_serve
    
    dbg "service_main left"
  end

  def service_stop
    dbg "service_stop entered"
    
    dbg "service_stop left"
  end
end


begin
  if ARGV[0] == 'service'
    ARGV.shift
    
    # default options
    OPTIONS = {
      :rails_root   => Dir.pwd,
      :environment  => 'production',
      :ip           => '0.0.0.0',
      :port         => 3000,
      :mime_map     => nil,
      :num_procs    => 1024,
      :timeout      => 0,
      :cpu          => nil
    }
    
    ARGV.options do |opts|
      opts.on('-r', '--root PATH', "Set the root path where your rails app resides.") { |OPTIONS[:rails_root]| }
      opts.on('-e', '--environment ENV', "Rails environment to run as. (default: production)") { |OPTIONS[:environment]| }
      opts.on('-b', '--binding ADDR', "Address to bind to") { |OPTIONS[:ip]| }
      opts.on('-p', '--port PORT', "Which port to bind to") { |OPTIONS[:port]| }
      opts.on('-m', '--mime PATH', "A YAML file that lists additional MIME types") { |OPTIONS[:mime_map]| }
      opts.on('-P', '--num-procs INT', "Number of processor threads to use") { |OPTIONS[:num_procs]| }
      opts.on('-t', '--timeout SECONDS', "Timeout all requests after SECONDS time") { |OPTIONS[:timeout]| }
      opts.on('-c', '--cpu CPU', "Bind the process to specific cpu") { |OPTIONS[:cpu]| }
      
      opts.parse!
    end
    
    #expand RAILS_ROOT
    OPTIONS[:rails_root] = File.expand_path(OPTIONS[:rails_root])
    
    OPTIONS[:docroot] = File.expand_path(OPTIONS[:rails_root] + '/public')
    
    # We must bind to a specific cpu?
    if OPTIONS[:cpu]
      Kernel32.set_affinity(Process.pid, OPTIONS[:cpu])
    end
    
    rails = MongrelRails.new(OPTIONS[:ip], OPTIONS[:port], OPTIONS[:rails_root], OPTIONS[:docroot], OPTIONS[:environment], OPTIONS[:mime_map], OPTIONS[:num_procs].to_i, OPTIONS[:timeout].to_i)
    rails_svc = RailsDaemon.new(rails)
    rails_svc.mainloop
    
  elsif ARGV[0] == 'debug'
    ARGV.shift
    
    # default options
    OPTIONS = {
      :rails_root   => Dir.pwd,
      :environment  => 'production',
      :ip           => '0.0.0.0',
      :port         => 3000,
      :mime_map     => nil,
      :num_procs    => 20,
      :timeout      => 120,
      :cpu          => nil
    }
    
    ARGV.options do |opts|
      opts.on('-r', '--root PATH', "Set the root path where your rails app resides.") { |OPTIONS[:rails_root]| }
      opts.on('-e', '--environment ENV', "Rails environment to run as.") { |OPTIONS[:environment]| }
      opts.on('-b', '--binding ADDR', "Address to bind to") { |OPTIONS[:ip]| }
      opts.on('-p', '--port PORT', "Which port to bind to") { |OPTIONS[:port]| }
      opts.on('-m', '--mime PATH', "A YAML file that lists additional MIME types") { |OPTIONS[:mime_map]| }
      opts.on('-P', '--num-procs INT', "Number of processor threads to use") { |OPTIONS[:num_procs]| }
      opts.on('-t', '--timeout SECONDS', "Timeout all requests after SECONDS time") { |OPTIONS[:timeout]| }
      opts.on('-c', '--cpu CPU', "Bind the process to specific cpu") { |OPTIONS[:cpu]| }
      
      opts.parse!
    end
    
    #expand RAILS_ROOT
    OPTIONS[:rails_root] = File.expand_path(OPTIONS[:rails_root])
    
    OPTIONS[:docroot] = File.expand_path(OPTIONS[:rails_root] + '/public')
    
    # We must bind to a specific cpu?
    if OPTIONS[:cpu]
      Kernel32.set_affinity(Process.pid, OPTIONS[:cpu])
    end
    
    rails = MongrelRails.new(OPTIONS[:ip], OPTIONS[:port], OPTIONS[:rails_root], OPTIONS[:docroot], OPTIONS[:environment], OPTIONS[:mime_map], OPTIONS[:num_procs].to_i, OPTIONS[:timeout].to_i)
    rails.delayed_initialize
    rails.start_serve
    
    begin
      sleep
    rescue Interrupt
      dbg "ERROR: #$!\r\n"
      dbg $!.backtrace.join("\r\n")
      puts "graceful shutdown?"
    end
    
    begin
      rails.stop_serve
    rescue
      dbg "ERROR: #$!\r\n"
      dbg $!.backtrace.join("\r\n")
    end
  end  
rescue
  dbg "ERROR: #$!\r\n"
  dbg $!.backtrace.join("\r\n")  
end
