#!/usr/bin/env ruby
# CLI client for Trocla.
#
require 'rubygems'
require 'trocla'
require 'optparse'
require 'yaml'

options = { :config_file => nil, :ask_password => true, :trace => false }

OptionParser.new do |opts|
  opts.on('--version', '-V', 'Version information') do
    puts Trocla::VERSION::STRING
    exit
  end

  opts.on('--config CONFIG', '-c', 'Configuration file') do |v|
    if File.exist?(v)
      options[:config_file] = v
    else
      STDERR.puts "Cannot find config file: #{v}"
      exit 1
    end
  end

  opts.on('--trace', 'Show stack trace on failure') do
    options[:trace] = true
  end

  opts.on('--no-random', 'Do not generate a random password if there is no plain text password available') do
    options['random'] = false
  end

  opts.on('--no-format', 'Do not format a password when setting it using `set`') do
    options['no_format'] = true
  end

  opts.on('--length LENGTH', 'Length for a randomly created password') do |v|
    options['length'] = v.to_i
  end

  opts.on('--password [PASSWORD]', '-p', 'Provide password at command line or STDIN') do |pass|
    options[:ask_password] = false
    options[:password] = pass
  end
end.parse!

def create(options)
  [Trocla.new(options.delete(:config_file)).password(
    options.delete(:trocla_key),
    options.delete(:trocla_format),
    options.merge(YAML.safe_load(options.delete(:other_options).shift.to_s) || {})
  ), 0]
end

def get(options)
  res = Trocla.new(options.delete(:config_file)).get_password(
    options.delete(:trocla_key),
    options.delete(:trocla_format),
    options.merge(YAML.safe_load(options.delete(:other_options).shift.to_s) || {})
  )
  [res, res.nil? ? 1 : 0]
end

def set(options)
  if options.delete(:ask_password)
    require 'highline/import'
    password = ask('Enter your password: ') { |q| q.echo = 'x' }.to_s
    pwd2 = ask('Repeat password: ') { |q| q.echo = 'x' }.to_s
    unless password == pwd2
      STDERR.puts 'Passwords did not match, exiting!'
      return [nil, 1]
    end
  else
    password = options.delete(:password) || STDIN.read.chomp
  end
  format = options.delete(:trocla_format)
  no_format = options.delete('no_format')
  trocla = Trocla.new(options.delete(:config_file))
  value = if no_format
            password
          else
            trocla.formats(format).format(password, (YAML.safe_load(options.delete(:other_options).shift.to_s) || {}))
          end
  trocla.set_password(
    options.delete(:trocla_key),
    format,
    value
  )
  ['', 0]
end

def reset(options)
  [Trocla.new(options.delete(:config_file)).reset_password(
    options.delete(:trocla_key),
    options.delete(:trocla_format),
    options.merge(YAML.safe_load(options.delete(:other_options).shift.to_s) || {})
  ), 0]
end

def delete(options)
  res = Trocla.new(options.delete(:config_file)).delete_password(
    options.delete(:trocla_key),
    options.delete(:trocla_format)
  )
  [res, res.nil? ? 1 : 0]
end

def formats(options)
  key = (options.delete(:trocla_key) || '')
  if key.empty?
    "Available formats: #{Trocla::Formats.all.join(', ')}"
  else
    res = Trocla.new(options.delete(:config_file)).available_format(
      key,
      options.merge(YAML.safe_load(options.delete(:other_options).shift.to_s) || {})
    )
    [res.nil? ? res : res.join(', '), res.nil? ? 1 : 0]
  end
end

def search(options)
  res = Trocla.new(options.delete(:config_file)).search_key(
    options.delete(:trocla_key)
  )
  [res.nil? ? res : res.join("\n"), res.nil? ? 1 : 0]
end

def check_format(format_name)
  if format_name.nil?
    STDERR.puts 'Missing format, exiting...'
    exit 1
  elsif !Trocla::Formats.available?(format_name)
    STDERR.puts "Error: The format #{format_name} is not available"
    exit 1
  end
end

actions = ['create', 'get', 'set', 'reset', 'delete', 'formats', 'search']

if (action=ARGV.shift) && actions.include?(action)
  options[:trocla_key] = ARGV.shift
  options[:trocla_format] = ARGV.shift
  options[:other_options] = ARGV
  check_format(options[:trocla_format]) unless ['delete','formats','search'].include?(action)
  begin
    result, excode = send(action, options)
    if result
      puts result.is_a?(String) ? result : result.inspect
    end
  rescue Exception => e
    unless e.message == 'exit'
      STDERR.puts "Action failed with the following message: #{e.message}"
      STDERR.puts '(See full trace by running task with --trace)'
    end
    raise e if options[:trace]

    exit 1
  end
  exit excode.nil? ? 0 : excode
else
  STDERR.puts "Please supply one of the following actions: #{actions.join(', ')}"
  STDERR.puts "Use #{$0} --help to get a list of options for these actions"
  exit 1
end
