#!/usr/bin/ruby
# SugarJar

require 'optparse'
require 'mixlib/shellout'
require_relative '../lib/sugarjar/commands'
require_relative '../lib/sugarjar/config'
require_relative '../lib/sugarjar/help'
require_relative '../lib/sugarjar/log'
require_relative '../lib/sugarjar/util'
require_relative '../lib/sugarjar/version'

SugarJar::Log.level = Logger::INFO

# Don't put defaults here, put them in SugarJar::Config - otherwise
# these defaults overwrite whatever is in config files.
options = {}

# If ENV['SUGARJAR_DEBUG'] is set, it overrides the config file,
# but not the command line options, so set that one here. Also
# start the logger at that level, in case we are debugging option loading
# itself
if ENV['SUGARJAR_LOGLEVEL']
  options['log_level'] = SugarJar::Log.level = ENV['SUGARJAR_LOGLEVEL'].to_sym
end

options['_cli_overrides'] = {}
parser = OptionParser.new do |opts|
  opts.banner = 'Usage: sj <command> [<args>] [<options>]'

  opts.separator ''
  opts.separator 'Command, args, and options, can appear in any order.'
  opts.separator ''
  opts.separator 'OPTIONS:'

  opts.on(
    '--default-forge-host HOST',
    'The default host of your forge (github, gitlab, etc.) when it cannot ' +
    'be determined automatically. In nearly every case SJ will ' +
    'automatically determine this. However, for `smartclone`, if ' +
    'you use shortnames (e.g. org/repo) that do not contain a host, this ' +
    'is the default to assume. Most useful in your config file when set ' +
    'to your most comomn forge host (e.g. github.com)',
  ) do |host|
    options['default_forge_host'] = host
  end

  opts.on(
    '--feature-prefix PREFIX',
    'Bypass the config and just use PREFIX.',
  ) do |prefix|
    options['_cli_overrides']['feature_prefix'] = prefix
  end

  opts.on(
    '--forge-type TYPE',
    'GENERALLY NOT NEEDED. In case SJ cannot detect the type of a forge ' +
    'from the hostname during smartclone. Only available in CLI, not a ' +
    'config file option. Forge type: github, gitlab.',
  ) do |type|
    options['_cli_overrides']['forge_type'] = type
  end

  opts.on(
    '--forge-user USER',
    'Bypass the config and just use USER as the forge user.',
  ) do |user|
    options['_cli_overrides']['user'] = user
  end

  opts.on('-h', '--help', 'Print this help message') do
    puts opts
    exit
  end

  opts.on(
    '--ignore-dirty',
    'Tell command that check for a dirty repo to carry on anyway. ' +
    '[default: false]',
  ) do
    options['ignore_dirty'] = true
  end

  opts.on(
    '--ignore-prerun-failure',
    'Ignore preprun failure on *push commands. [default: false]',
  ) do
    options['ignore_prerun_failure'] = true
  end

  opts.on(
    '--log-level LEVEL',
    'Set logging level (fatal, error, warning, info, debug, trace). This can ' +
    'also be set via the SUGARJAR_LOGLEVEL environment variable. [default: ' +
    'info]',
  ) do |level|
    options['log_level'] = level
  end

  opts.on(
    '--[no-]pr-autofill',
    'When creating a PR, auto fill the title & description from the top ' +
    'commit if we are using "gh". [default: true]',
  ) do |autofill|
    options['pr_autofill'] = autofill
  end

  opts.on(
    '--fork-name NAME',
    'When forking a repo (in `smartclone`), fork the repo to a different ' +
    'name. See the help for `smartclone` below.',
  ) do |val|
    options['fork_name'] = val
  end

  opts.on(
    '--[no-]pr-autostack',
    'When creating a PR, if this is a subfeature, should we make it a ' +
    'PR on the PR for the parent feature. If not specified, we prompt ' +
    'when this happens, when true always do this, when false never do ' +
    'this. Only applicable when usiing "gh" and on branch-based PRs.',
  ) do |autostack|
    options['pr_autostack'] = autostack
  end

  opts.on('--[no-]color', 'Enable color. [default: true]') do |color|
    options['color'] = color
  end

  opts.on('--version') do
    puts SugarJar::VERSION
    exit
  end

  opts.separator ''
  opts.separator 'COMMANDS:'
  opts.separator SugarJar::Help.summary_list
  opts.separator ''
  opts.separator "Run 'sj help <command>' for details on a specific command."
end

extra_opts = []
argv_copy = ARGV.dup

# We want to allow people to pass in extra args to be passed to commands (like
# `amend`), but OptionParser doesn't easily allow this. So we loop over it,
# catching exceptions.

begin
  # HOWEVER, anytime it throws an exception, for some reason, it clears
  # out all of ARGV, or whatever you passed to as ARGV.
  #
  # This not only prevents further parsing, but also means we lose
  # any non-option arguements (like the subcommand!)
  #
  # So we save a copy, and if we throw an exception, save the option that
  # caused it, remove that option from our copy, and then re-populate argv
  # with what's left.
  #
  # By doing this we not only get to parse all the options properly and
  # save unknown ones, but non-option arguements, which OptionParser
  # normally leaves in ARGV stay in ARGV.
  saved_argv = argv_copy.dup
  parser.parse!(argv_copy)
rescue OptionParser::InvalidOption => e
  SugarJar::Log.debug("Saving unknown argument #{e.args}")
  extra_opts += e.args

  # e.args is an array, but it's only ever one arguement per exception
  saved_argv.delete(e.args.first)
  argv_copy = saved_argv.dup
  SugarJar::Log.debug(
    "Continuing option parsing with remaining ARGV: #{argv_copy}",
  )
  retry
end

options = SugarJar::Config.config.merge(options)
SugarJar::Log.level = options['log_level'].to_sym if options['log_level']

subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
if ARGV.empty? || !subcommand
  puts parser
  exit
end

SugarJar::Log.debug("Final config: #{options}")

# if the command is help, we don't bother to create the Commands obj
if subcommand == 'help'
  help_target = argv_copy.reject { |x| x.start_with?('-') }[1]
  if help_target
    help_text = SugarJar::Help.command_help(help_target)
    if help_text
      puts help_text
    else
      SugarJar::Log.fatal("No such subcommand: #{help_target}")
      exit 1
    end
  else
    puts parser
  end
  exit
end

sj = SugarJar::Commands.new(options)
valid_commands = sj.public_methods - Object.public_methods
is_valid_command = valid_commands.include?(subcommand.to_sym)
# We can't do .delete(subcommand) because someone could, for example
# have a branch called 'co' and then do 'sj co co' - which will then
# remove _all_ instances of 'co'. So find the first instance and remove
# that.
argv_copy.delete_at(argv_copy.find_index(subcommand))
SugarJar::Log.debug("subcommand is #{subcommand}")

# Extra options we got, plus any left over arguements are what we
# pass to Commands so they can be passed to git as necessary
extra_opts += argv_copy
SugarJar::Log.debug("extra unknown options: #{extra_opts}")

extra_opts = [options] if subcommand == 'debuginfo'

unless is_valid_command
  SugarJar::Log.fatal("No such subcommand: #{subcommand}")
  exit 1
end

SugarJar::Log.debug(
  "running #{subcommand}; extra opts: #{extra_opts.join(', ')}",
)
sj.send(subcommand.to_sym, *extra_opts)
