require "poet/version"
require "thor"
require "fileutils"
require "digest/md5"

class PoetCLI < Thor

  MAGIC_LINE = "# Generated by #{File.basename(__FILE__, '.rb')}"

  attr_accessor :files_to_include

  default_task :create
  class_option :dir,
      desc: 'Use specified directory to collect conf files',
      default: ENV['POET_GLOBDIR'] || File.expand_path('~/.ssh/config.d')
  class_option :output,
      desc: 'Generate output in specified file',
      aliases: '-o',
      default: ENV['POET_OUTPUT'] || File.expand_path('~/.ssh/config')
  class_option :with,
      desc: 'Include an otherwise disabled config file',
      aliases: '-w',
      default: ""
  class_option :verbose,
      desc: 'Be verbose',
      aliases: '-v',
      type: :boolean

  no_tasks do
    def print_tree(dir = ".", nesting = 0)
      Dir.entries(dir).sort.each do |entry|
        next if entry =~ /^\.{1,2}/   # Ignore ".", "..", or hidden files
        puts "|   " * nesting + "|-- #{entry}"
        if File.stat(d = "#{dir}#{File::SEPARATOR}#{entry}").directory?
          print_tree(d, nesting + 1)
        end
      end
    end
  end

  desc "bootstrap [FILE]",
      "Move ~/.ssh/config (or whatever you specified) to ~/.ssh/config.d/ to help you get started"
  def bootstrap(file=nil)
    file ||= File.expand_path(file || options[:output])
    if File.directory?(options[:dir])
      $stderr.puts "You're already good to go."
      Process.exit!(3)
    end
    FileUtils.mkdir_p(options[:dir])
    FileUtils.mv(file, options[:dir]) if File.file?(file)
    create
  end

  desc "completeme", "Install completion for Bash into ~/.bash_completion.d/"
  def completeme
    completion_dir = File.expand_path("~/.bash_completion.d/")
    FileUtils.mkdir_p(completion_dir)
    say "Copying completion file to #{completion_dir}"
    FileUtils.cp(File.expand_path('../../completion/poet.bash', __FILE__), completion_dir)
    say %Q(To use the completion, execute this command:\n
    echo 'source #{File.join(completion_dir, 'poet.bash')}' >> ~/.bashrc)
  end

  desc "", "Concatenate all host stanzas under ~/.ssh/config.d/ into a single ~/.ssh/config"
  def create
    if !File.directory?(options[:dir])
      $stderr.puts "#{options[:dir]} does not exist or is not a directory"
      Process.exit!(1)
    end

    if File.exists?(options[:output]) && File.new(options[:output]).gets == "#{MAGIC_LINE}\n"
      puts "Found generated ssh_config under #{options[:output]}. Overwriting..."
    elsif File.exists?(options[:output])
      $stderr.puts "Found hand-crafted ssh_config under #{options[:output]}. Please move it out of the way or specify a different output file with the -o option."
      Process.exit!(2)
    end

    whitelist = files_to_include || options[:with].split(',').map{|file| "#{file}.disabled"}
    files = Dir["#{options[:dir]}/**/*"].reject do |file|
      File.directory?(file) || \
        file =~ /\.disabled$/ && !whitelist.include?(File.basename(file))
    end

    files -= [options[:output]]

    entries = []

    files.sort.each do |file|
      entries << "\n# Located in " + file.to_s
      entries << File.read(file)
      $stdout.puts "Using #{file.gsub(/^\.\//, '')}" if options.verbose?
    end

    File.open(options[:output], 'w', 0600) do |ssh_config|
      ssh_config.puts(MAGIC_LINE)
      ssh_config.puts("# DO NOT EDIT THIS FILE")
      ssh_config.puts("# Create or modify files under #{options[:dir]} instead")
      ssh_config.puts(entries.join("\n"))
    end
  end

  desc "edit FILE", "Open FILE under ~/.ssh/config.d/ in your favorite $EDITOR"
  def edit(file)
    if ENV['EDITOR'].to_s.empty?
      $stderr.puts "$EDITOR is empty. Could not determine your favorite editor."
      Process.exit!(4)
    end
    filepath = File.join(options[:dir], file)
    checksum_before = Digest::MD5.file(filepath) rescue '0'*16
    system("#{ENV['EDITOR']} #{filepath}")
    self.files_to_include = [file]
    create if File.exists?(filepath) && checksum_before != Digest::MD5.file(filepath)
  end

  desc "ls", "List all configuration files"
  option :tree, aliases: '-t', type: :boolean, desc: 'Print tree of config dir'
  def ls
    if options.tree?
      print_tree(options[:dir])
    else
      files = Dir["#{options[:dir]}/**/*"].sort.reject { |file| File.directory?(file) }
      puts files.map{|filename| filename[options[:dir].size+1..-1]}.join("\n")
    end
  end
end