#!/usr/bin/env ruby

require 'rubygems'
require 'uri'
require 'tempfile'
require 'trollop'
require "sup"

## save a message 'm' to an open file pointer 'fp'
def save m, fp
  m.source.each_raw_full_message_line(m.source_info) { |l| fp.print l }
end

opts = Trollop::options do
  version "sup-sync-back (sup #{Redwood::VERSION})"
  banner <<EOS
Partially synchronizes a message source with the Sup index by deleting
or moving any messages from the source that are marked as deleted or
spam in the Sup index.

Currently only works with mbox sources.

Usage:
  sup-sync-back [options] <source>*

where <source>* is zero or more source URIs. If no sources are given,
sync back all usual sources.

You probably want to run sup-sync --changed after this command.

Options include:
EOS
  opt :delete_deleted, "Delete deleted messages.", :default => false, :short => "d"
  opt :move_deleted, "Move deleted messages to a local mbox file.", :type => String, :short => :none
  opt :delete_spam, "Delete spam messages.", :default => false, :short => "s"
  opt :move_spam, "Move spam messages to a local mbox file.", :type => String, :short => :none
  opt :verbose, "Print message ids as they're processed."
  opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
  opt :version, "Show version information", :short => :none

  conflicts :delete_deleted, :move_deleted
  conflicts :delete_spam, :move_spam
end

Redwood::start
index = Redwood::Index.new
index.lock_or_die

deleted_fp, spam_fp = nil
unless opts[:dry_run]
  deleted_fp = File.open(opts[:move_deleted], "a") if opts[:move_deleted] 
  spam_fp = File.open(opts[:move_spam], "a") if opts[:move_spam]
end

begin
  index.load

  sources = ARGV.map do |uri|
    s = index.source_for(uri) or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
    s.is_a?(Redwood::MBox::Loader) or Trollop::die "#{uri} is not an mbox source."
    s
  end

  if sources.empty?
    sources = index.usual_sources.select { |s| s.is_a? Redwood::MBox::Loader } 
  end

  modified_sources = []
  sources.each do |source|
    $stderr.puts "Scanning #{source}..."

    unless ((opts[:delete_deleted] || opts[:move_deleted]) && index.has_any_from_source_with_label?(source, :deleted)) || ((opts[:delete_spam] || opts[:move_spam]) && index.has_any_from_source_with_label?(source, :spam))
      $stderr.puts "Nothing to do from this source; skipping"
      next
    end

    source.reset!
    num_deleted = num_moved = num_scanned = 0
    
    out_fp = Tempfile.new "sup-sync-back-#{source.id}"
    Redwood::PollManager.add_messages_from source do |m, offset, entry|
      num_scanned += 1

      if entry
        labels = entry[:label].split.map { |x| x.intern }.to_boolean_h

        if labels.member? :deleted
          if opts[:delete_deleted]
            puts "Dropping deleted message #{source}##{offset}" if opts[:verbose]
            num_deleted += 1
          elsif opts[:move_deleted] && labels.member?(:deleted)
            puts "Moving deleted message #{source}##{offset}" if opts[:verbose]
            save m, deleted_fp unless opts[:dry_run]
            num_moved += 1
          end

        elsif labels.member? :spam
          if opts[:delete_spam]
            puts "Deleting spam message #{source}##{offset}" if opts[:verbose]
            num_deleted += 1
          elsif opts[:move_spam] && labels.member?(:spam)
            puts "Moving spam message #{source}##{offset}" if opts[:verbose]
            save m, spam_fp unless opts[:dry_run]
            num_moved += 1
          end
        else
          save m, out_fp unless opts[:dry_run]
        end
      else
        save m, out_fp unless opts[:dry_run]
      end

      nil # don't actually add anything!
    end
    $stderr.puts "Scanned #{num_scanned}, deleted #{num_deleted}, moved #{num_moved} messages from #{source}."
    modified_sources << source if num_deleted > 0 || num_moved > 0
    out_fp.close unless opts[:dry_run]

    unless opts[:dry_run] || (num_deleted == 0 && num_moved == 0)
      deleted_fp.flush if deleted_fp
      spam_fp.flush if spam_fp
      $stderr.puts "Moving #{out_fp.path} to #{source.file_path}"
      FileUtils.mv out_fp.path, source.file_path
    end
  end

  unless opts[:dry_run]
    deleted_fp.close if deleted_fp
    spam_fp.close if spam_fp
  end

  $stderr.puts "Done."
  unless modified_sources.empty?
    $stderr.puts "You should now run: sup-sync --changed #{modified_sources.join(' ')}"
  end
rescue Exception => e
  File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
  raise
ensure
  index.save
  Redwood::finish
  index.unlock
end