#!/usr/bin/env ruby

$0 = 'larch' # hide arguments from ps and top

require 'rubygems'
require 'highline/import' # optional dep: termios
require 'trollop'

require 'larch'

module Larch

  # Parse command-line options.
  options = Trollop.options do
    version "Larch #{APP_VERSION}\n" << APP_COPYRIGHT
    banner <<-EOS
Larch copies messages from one IMAP server to another. Awesomely.

Usage:
  larch [config section] [options]
  larch --from <uri> --to <uri> [options]

Server Options:
EOS
    opt :from,        "URI of the source IMAP server", :short => '-f', :type => :string
    opt :from_folder, "Source folder to copy from (default: INBOX)", :short => '-F', :default => Config::DEFAULT['from-folder'], :type => :string
    opt :from_pass,   "Source server password (default: prompt)", :short => '-p', :type => :string
    opt :from_user,   "Source server username (default: prompt)", :short => '-u', :type => :string
    opt :to,          "URI of the destination IMAP server", :short => '-t', :type => :string
    opt :to_folder,   "Destination folder to copy to (default: INBOX)", :short => '-T', :default => Config::DEFAULT['to-folder'], :type => :string
    opt :to_pass,     "Destination server password (default: prompt)", :short => '-P', :type => :string
    opt :to_user,     "Destination server username (default: prompt)", :short => '-U', :type => :string

    text "\nCopy Options:"
    opt :all,              "Copy all folders recursively", :short => '-a'
    opt :all_subscribed,   "Copy all subscribed folders recursively", :short => '-s'
    opt :delete,           "Delete messages from the source after copying them, or if they already exist at the destination", :short => '-d'
    opt :exclude,          "List of mailbox names/patterns that shouldn't be copied", :short => :none, :type => :strings, :multi => true
    opt :exclude_file,     "Filename containing mailbox names/patterns that shouldn't be copied", :short => :none, :type => :string
    opt :expunge,          "Expunge deleted messages from the source", :short => '-x'
    opt :no_recurse,       "Don't copy subfolders recursively (cannot be used with --all or --all_subscribed)", :short => :none
    opt :sync_flags,       "Sync message flags from the source to the destination for messages that already exist at the destination", :short => '-S'

    text "\nGeneral Options:"
    opt :config,           "Specify a non-default config file to use", :short => '-c', :default => Config::DEFAULT['config']
    opt :database,         "Specify a non-default message database to use", :short => :none, :default => Config::DEFAULT['database']
    opt :dry_run,          "Don't actually make any changes", :short => '-n'
    opt :max_retries,      "Maximum number of times to retry after a recoverable error", :short => :none, :default => Config::DEFAULT['max-retries']
    opt :no_create_folder, "Don't create destination folders that don't already exist", :short => :none
    opt :ssl_certs,        "Path to a trusted certificate bundle to use to verify server SSL certificates", :short => :none, :type => :string
    opt :ssl_verify,       "Verify server SSL certificates", :short => :none
    opt :verbosity,        "Output verbosity: debug, info, warn, error, or fatal", :short => '-V', :default => Config::DEFAULT['verbosity']
  end

  if options[:config_given]
    Trollop.die :config, ": file not found: #{options[:config]}" unless File.exist?(options[:config])
  end

  # Load config.
  begin
    config = Config.new(ARGV.shift || 'default', options[:config], options)
  rescue Config::Error => e
    abort "Config error: #{e}"
  end

  # Create URIs.
  uri_from = URI(config.from)
  uri_to   = URI(config.to)

  # Use --from-folder and --to-folder unless folders were specified in the URIs.
  uri_from.path = uri_from.path.empty? ? '/' + CGI.escape(config.from_folder.gsub(/^\//, '')) : uri_from.path if config.from_folder
  uri_to.path   = uri_to.path.empty?   ? '/' + CGI.escape(config.to_folder.gsub(/^\//, ''))   : uri_to.path if config.to_folder

  # --all and --all-subscribed options override folders
  if config.all || config.all_subscribed
    uri_from.path = ''
  end

  # Usernames and passwords specified as arguments override those in the URIs
  uri_from.user     = CGI.escape(config.from_user) if config.from_user
  uri_from.password = CGI.escape(config.from_pass) if config.from_pass
  uri_to.user       = CGI.escape(config.to_user) if config.to_user
  uri_to.password   = CGI.escape(config.to_pass) if config.to_pass

  # If usernames/passwords aren't specified in either URIs or config, then prompt.
  uri_from.user     ||= CGI.escape(ask("Source username (#{uri_from.host}): "))
  uri_from.password ||= CGI.escape(ask("Source password (#{uri_from.host}): ") {|q| q.echo = false })
  uri_to.user       ||= CGI.escape(ask("Destination username (#{uri_to.host}): "))
  uri_to.password   ||= CGI.escape(ask("Destination password (#{uri_to.host}): ") {|q| q.echo = false })

  # Go go go!
  init(config)

  imap_from = Larch::IMAP.new(uri_from,
      :dry_run     => config[:dry_run],
      :log_label   => '[<]',
      :max_retries => config[:max_retries],
      :ssl_certs   => config[:ssl_certs] || nil,
      :ssl_verify  => config[:ssl_verify]
  )

  imap_to = Larch::IMAP.new(uri_to,
      :create_mailbox => !config[:no_create_folder] && !config[:dry_run],
      :dry_run        => config[:dry_run],
      :log_label      => '[>]',
      :max_retries    => config[:max_retries],
      :ssl_certs      => config[:ssl_certs] || nil,
      :ssl_verify     => config[:ssl_verify]
  )

  unless RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince|java/
    begin
      for sig in [:SIGINT, :SIGQUIT, :SIGTERM]
        trap(sig) { @log.fatal "Interrupted (#{sig})"; Kernel.exit }
      end
    rescue => e
    end
  end

  if config.all
    copy_all(imap_from, imap_to)
  elsif config.all_subscribed
    copy_all(imap_from, imap_to, true)
  else
    copy_folder(imap_from, imap_to)
  end
end
