#!/usr/bin/env ruby

RAILS_ENV = 'builder'

require File.dirname(__FILE__) + '/../config/boot'
require 'optparse'


CRUISE_OPTIONS = { :verbose => false }

ARGV.options do |opts|
  opts.banner = "Usage: cruise builder <project_name> []options]"

  opts.separator ""

  opts.on("-t", "--trace", "Trace-level (verbose) logging") { CRUISE_OPTIONS[:verbose] = true }
  opts.on("-h", "--help",
          "Show this help message.") { puts opts; exit }

  args = opts.parse!

  unless args.length == 1
    STDERR.puts "Project name not specified"
    STDERR.puts
    puts opts
    exit(-1)
  end

  CRUISE_OPTIONS[:project_name] = args[0]
  CRUISE_OPTIONS[:log_file_name] = "log/#{CRUISE_OPTIONS[:project_name]}_builder.log"
end

require RAILS_ROOT + "/config/environment"

CruiseControl::Log.verbose = CRUISE_OPTIONS[:verbose]

project_path = File.expand_path(File.join(CRUISE_DATA_ROOT, 'projects', CRUISE_OPTIONS[:project_name]))

unless File.directory? project_path
  STDERR.puts "Project '#{CRUISE_OPTIONS[:project_name]}' not found in '#{project_path}'"
  exit(-1)
end

def write_to_log_and_console(message, severity = :info)
  CruiseControl::Log.event(message, severity) rescue nil
  (puts message unless CRUISE_OPTIONS[:verbose]) rescue nil 
end

def cleanup(project)
  write_to_log_and_console "Builder for project '#{CRUISE_OPTIONS[:project_name]}' exited"
  if project
    ProjectBlocker.release(project) rescue nil
  end
end

def load_project(path)
  begin
    Project.read(path)
  rescue Exception => e
    write_to_log_and_console("Failed to load the new project configuration. The builder will stop.", :fatal)
    raise
  end
end

project = nil

begin
  project = load_project(project_path)

  # this will create builder.pid file in project's CC directory and grab an exclusive lock on it, or else
  # blow up saying that something else is already locking it
  ProjectBlocker.block(project)

  write_to_log_and_console "Builder for project '#{project.name}' started"
  puts "Logging to: #{File.expand_path(CRUISE_OPTIONS[:log_file_name])}"

  while (true) do
    catch(:reload_project) do
      project.scheduler.run
    end
    project = load_project(project_path)
    # this will cause the next call to scheduler to run the build immediately
    project.request_build rescue nil
  end
rescue Interrupt
  # this is okay, we're just control-c'ing the app
rescue Exception => e
  begin
    CruiseControl::Log.fatal(e)
  rescue => logging_error
    STDERR.puts e.message
    STDERR.puts e.backtrace.map { |line| "    #{line}" }
    STDERR.puts "Attempt to log the above error failed with this:"
    STDERR.puts logging_error.message
    STDERR.puts logging_error.backtrace
  end
  CRUISE_OPTIONS[:verbose] ? raise : exit(1)
ensure
  cleanup(project)
end

