#!/usr/bin/ruby1.8

## requires are split in two for efficiency reasons: ditz should be really
## fast when using it for completion.
require 'operator'
op = Ditz::Operator.new

## a secret option for shell completion
if ARGV.include? '--commands'
  puts op.class.operations.map { |name, _| name }
  exit 0
end

begin
  require 'rubygems'
rescue LoadError
end

require 'fileutils'
require 'pathname'
require 'trollop'; include Trollop
require "ditz"

CONFIG_FN = ".ditz-config"
PLUGIN_FN = ".ditz-plugins"

config_dir = Ditz::find_dir_containing CONFIG_FN
plugin_dir = Ditz::find_dir_containing PLUGIN_FN

$opts = options do
  version "ditz #{Ditz::VERSION}"
  opt :issue_dir, "Issue database dir", :default => "bugs"
  opt :config_file, "Configuration file", :default => File.join(config_dir || ".", CONFIG_FN)
  opt :plugins_file, "Plugins file", :default => File.join(plugin_dir || ".", PLUGIN_FN)
  opt :verbose, "Verbose output", :default => false
  opt :list_hooks, "List all hooks and descriptions, and quit.", :short => 'l', :default => false
  stop_on_unknown
end
$verbose = true if $opts[:verbose]

Ditz::HookManager.register :startup, <<EOS
Executes at startup

Variables: project, config
No return value.
EOS

Ditz::HookManager.register :after_add, <<EOS
Executes before terminating if new issue files has been created.
Basically you want to instruct your SCM that these files has
been added.

Variables: project, config, issues
No return value.
EOS

Ditz::HookManager.register :after_delete, <<EOS
Executes before terminating if new issue files has been deleted.
Basically you want to instruct your SCM that these files has
been deleted.

Variables: project, config, issues
No return value.
EOS

Ditz::HookManager.register :after_update, <<EOS
Executes before terminating if new issue files has been updated.
You may want to instruct your SCM about these changes.
Note that new issues are not considered updated.

Variables: project, config, issues
No return value.
EOS

if $opts[:list_hooks]
  Ditz::HookManager.print_hooks
  exit 0
end

begin
  Ditz::load_plugins $opts[:plugins_file]
rescue SystemCallError => e
  Ditz::debug "can't load plugins file: #{e.message}"
end

config = begin
  Ditz::debug "loading config from #{$opts[:config_file]}"
  Ditz::Config.from $opts[:config_file]
rescue SystemCallError => e
  if ARGV.member? "<options>"
    ## special case here. if we're asking for tab completion, and the config
    ## file doesn't exist, don't do the interactive building. just make a
    ## fake empty one and carry on.
    Ditz::Config.new
  else
    puts <<EOS
I wasn't able to find a configuration file #{$opts[:config_file]}.
We'll set it up right now.
EOS
    Ditz::Config.create_interactively.save! $opts[:config_file]
  end
end

issue_dir = Pathname.new(config.issue_dir || $opts[:issue_dir])
cmd = ARGV.shift || "todo"
unless op.has_operation? cmd
  die "no such command: #{cmd}"
end

case cmd # some special commands not handled by Ditz::Operator
when "init"
  die "#{issue_dir} directory already exists" if issue_dir.exist?
  issue_dir.mkdir
  fn = issue_dir + Ditz::FileStorage::PROJECT_FN
  project = op.init
  project.save! fn
  puts "Ok, #{issue_dir} directory created successfully."
  exit
when "help"
  op.do "help", nil, nil, ARGV
  exit
end

$project_root = Ditz::find_dir_containing(issue_dir + Ditz::FileStorage::PROJECT_FN)
die "No #{issue_dir} directory---use 'ditz init' to initialize" unless $project_root
$project_root += issue_dir

storage = Ditz::FileStorage.new $project_root
project = begin
  storage.load
rescue SystemCallError, Ditz::Project::Error => e
  die "#{e.message} (use 'init' to initialize)"
end

Ditz::HookManager.run :startup, project, config

Ditz::debug "executing command #{cmd}"
begin
  op.do cmd, project, config, ARGV
rescue Ditz::Operator::Error, Ditz::Release::Error, Ditz::Project::Error, Ditz::Issue::Error => e
  $stderr.puts "Error: #{e.message}"
  exit(-1)
rescue Errno::EPIPE, Interrupt
  puts
  exit 1
end

changed_issues = project.issues.select { |i| i.changed? }
changed_not_added_issues = changed_issues - project.added_issues

storage.save project

## at this point, for compatibility with older hook stuff, we set the pathname
## directly on the issues.

project.issues.each { |i| i.pathname = storage.filename_for_issue(i) }
unless project.added_issues.empty?
  unless Ditz::HookManager.run :after_add, project, config, project.added_issues
    puts "You may have to inform your SCM that the following files have been added:"
    project.added_issues.each { |i| puts "  " + storage.filename_for_issue(i) }
  end
end

unless project.deleted_issues.empty?
  unless Ditz::HookManager.run :after_delete, project, config, project.deleted_issues
    puts "You may have to inform your SCM that the following files have been deleted:"
    project.deleted_issues.each { |i| puts "  " + storage.filename_for_issue(i) }
  end
end

unless changed_not_added_issues.empty?
  unless Ditz::HookManager.run :after_update, project, config, changed_not_added_issues
    puts "You may have to inform your SCM that the following files have been modified:"
    changed_not_added_issues.each { |i| puts "  " + storage.filename_for_issue(i) }
  end
end

## hack upon a hack
if project.changed?
  project.pathname = storage.filename_for_project
  unless Ditz::HookManager.run :after_update, project, config, [project]
    puts "You may have to inform your SCM that the following files have been modified:"
    puts "  " + storage.filename_for_project
  end
end

config.save! $opts[:config_file] if config.changed?

# vim: syntax=ruby
