#!/usr/bin/env ruby

# Trap interrupts to quit cleanly. This will be overridden at some point
# by Vagrant. This is made to catch any interrupts while Vagrant is
# initializing which have historically resulted in stack traces.
Signal.trap("INT") { abort }

# Disable exception reporting by default if available
if Thread.respond_to?(:report_on_exception=)
  Thread.report_on_exception = false
end

# Split arguments by "--" if its there, we'll recombine them later
argv = ARGV.dup
argv_extra = []

# These will be the options that are passed to initialize the Vagrant
# environment.
opts = {}

if idx = argv.index("--")
  argv_extra = argv.slice(idx+1, argv.length-2)
  argv = argv.slice(0, idx)
end

require_relative "../lib/vagrant/version"
# Fast path the version of Vagrant
if argv.include?("-v") || argv.include?("--version")
  puts "Vagrant #{Vagrant::VERSION}"
  exit 0
end

# Disable plugin loading for commands where plugins are not required. This will
# also disable loading of the Vagrantfile if it available as the environment
# is not required for these commands
argv.each_index do |i|
  arg = argv[i]

  if !arg.start_with?("-")
    if arg == "box" && argv[i+1] == "list"
      opts[:vagrantfile_name] = ""
      ENV['VAGRANT_NO_PLUGINS'] = "1"
    end

    # Do not load plugins when performing plugin operations
    if arg == "plugin"
      if argv.none?{|a| a == "--local" } && !ENV["VAGRANT_LOCAL_PLUGINS_LOAD"]
        opts[:vagrantfile_name] = ""
      end
      ENV['VAGRANT_NO_PLUGINS'] = "1"
      # Only initialize plugins when listing installed plugins
      if argv[i+1] != "list"
        ENV['VAGRANT_DISABLE_PLUGIN_INIT'] = "1"
      end
    end

    break
  end
end

# Set logging level to `debug`. This is done before loading 'vagrant', as it
# sets up the logging system.
if argv.include?("--debug")
  argv.delete("--debug")
  ENV["VAGRANT_LOG"] = "debug"
end

# Enable log timestamps if requested
if argv.include?("--timestamp")
  argv.delete("--timestamp")
  ENV["VAGRANT_LOG_TIMESTAMP"] = "1"
end

# Convenience flag to enable debug with timestamps
if argv.include?("--debug-timestamp")
  argv.delete("--debug-timestamp")
  ENV["VAGRANT_LOG"] = "debug"
  ENV["VAGRANT_LOG_TIMESTAMP"] = "1"
end

# Stdout/stderr should not buffer output
$stdout.sync = true
$stderr.sync = true

# Before we start activate all our dependencies
# so we can provide correct resolutions later
builtin_specs = []

vagrant_spec = Gem::Specification.find_all_by_name("vagrant").detect do |spec|
  spec.version == Gem::Version.new(Vagrant::VERSION)
end

dep_activator = proc do |spec|
  spec.runtime_dependencies.each do |dep|
    gem(dep.name, *dep.requirement.as_list)
    dep_spec = Gem::Specification.find_all_by_name(dep.name).detect(&:activated?)
    if dep_spec
      builtin_specs << dep_spec
      dep_activator.call(dep_spec)
    end
  end
end

if vagrant_spec
  dep_activator.call(vagrant_spec)
end

env = nil
begin
  require 'vagrant'
  require 'vagrant/bundler'
  require 'vagrant/cli'
  require 'vagrant/util/platform'
  require 'vagrant/util/experimental'

  # Set our list of builtin specs
  Vagrant::Bundler.instance.builtin_specs = builtin_specs

  # Schedule the cleanup of things
  at_exit(&Vagrant::Bundler.instance.method(:deinit))

  # If this is not a pre-release disable verbose output
  if !Vagrant.prerelease?
    $VERBOSE = nil
  end

  # Add any option flags defined within this file here
  # so they are automatically propagated to all commands
  Vagrant.add_default_cli_options(proc { |o|
    o.on("--[no-]color", "Enable or disable color output")
    o.on("--machine-readable", "Enable machine readable output")
    o.on("-v", "--version", "Display Vagrant version")
    o.on("--debug", "Enable debug output")
    o.on("--timestamp", "Enable timestamps on log output")
    o.on("--debug-timestamp", "Enable debug output with timestamps")
    o.on("--no-tty", "Enable non-interactive output")
  })

  # Create a logger right away
  logger = Log4r::Logger.new("vagrant::bin::vagrant")
  logger.info("`vagrant` invoked: #{ARGV.inspect}")

  # Disable color in a few cases:
  #
  #   * --no-color is anywhere in our arguments
  #   * STDOUT is not a TTY
  #   * The terminal doesn't support colors (Windows)
  #
  if argv.include?("--no-color") || ENV["VAGRANT_NO_COLOR"]
    # Delete the argument from the list so that it doesn't
    # cause any invalid arguments down the road.
    argv.delete("--no-color")

    opts[:ui_class] = Vagrant::UI::Basic
  elsif !Vagrant::Util::Platform.terminal_supports_colors?
    opts[:ui_class] = Vagrant::UI::Basic
  elsif !$stdout.tty? && !Vagrant::Util::Platform.cygwin?
    # Cygwin always reports STDOUT is not a TTY, so we only disable
    # colors if its not a TTY AND its not Cygwin.
    opts[:ui_class] = Vagrant::UI::Basic
  end

  # Also allow users to force colors.
  if argv.include?("--color") || ENV["VAGRANT_FORCE_COLOR"]
    argv.delete("--color")
    opts[:ui_class] = Vagrant::UI::Colored
  end

  # Highest precedence is if we have enabled machine-readable output
  if argv.include?("--machine-readable")
    argv.delete("--machine-readable")
    opts[:ui_class] = Vagrant::UI::MachineReadable
  end

  # Setting to enable/disable showing progress bars
  if argv.include?("--no-tty")
    argv.delete("--no-tty")
    opts[:ui_class] = Vagrant::UI::NonInteractive
  end

  # Default to colored output
  opts[:ui_class] ||= Vagrant::UI::Colored

  # Recombine the arguments
  if !argv_extra.empty?
    argv << "--"
    argv += argv_extra
  end

  sub_cmd = Class.new(Vagrant.plugin("2", :command)) {
    def sub_command
      split_main_and_subcommand(@argv)[1]
    end
  }.new(argv.dup, nil).sub_command

  # Check if we are running the server. If we are, we extract
  # the command from the plugin manager and run it directly.
  # Doing this prevents Vagrant from attempting to load an
  # Environment directly.
  if sub_cmd == "serve"
    cmd = Vagrant.plugin("2").manager.commands[:serve].first
    Vagrant.enable_server_mode!
    cmd_call = cmd.call
    result = cmd_call.new([], nil).execute
    exit(result)
  else
    # Create the environment, which is the cwd of wherever the
    # `vagrant` command was invoked from
    logger.debug("Creating Vagrant environment")
    env = Vagrant::Environment.new(opts)

    # If we are running with the Windows Subsystem for Linux do
    # some extra setup to allow access to Vagrant managed machines
    # outside the subsystem
    if Vagrant::Util::Platform.wsl?
      recreate_env = Vagrant::Util::Platform.wsl_init(env, logger)
      if recreate_env
        logger.info("Re-creating Vagrant environment due to WSL modifications.")
        env = Vagrant::Environment.new(opts)
      end
    end

    if !Vagrant.in_installer? && !Vagrant.very_quiet?
      # If we're not in the installer, warn.
      env.ui.warn(I18n.t("vagrant.general.not_in_installer") + "\n", prefix: false)
    end

    # Acceptable experimental flag values include:
    #
    # Unset  - Disables experimental features
    # 0      - Disables experimental features
    # 1      - Enables all features
    # String - Enables one or more features, separated by commas
    if Vagrant::Util::Experimental.enabled?
      experimental = Vagrant::Util::Experimental.features_requested
      ui = Vagrant::UI::Prefixed.new(env.ui, "vagrant")
      logger.debug("Experimental flag is enabled")
      if Vagrant::Util::Experimental.global_enabled?
        ui.warn(I18n.t("vagrant.general.experimental.all"), bold: true, prefix: true, channel: :error)
      else
        ui.warn(I18n.t("vagrant.general.experimental.features", features: experimental.join(", ")), bold: true, prefix: true, channel: :error)
      end
    end

    begin
      # Execute the CLI interface, and exit with the proper error code
      exit_status = env.cli(argv)
    ensure
      # Unload the environment so cleanup can be done
      env.unload
    end

    # Exit with the exit status from our CLI command
    exit(exit_status)
  end
rescue Exception => e
  # It is possible for errors to happen in Vagrant's initialization. In
  # this case, we don't have access to this class yet, so we check for it.
  raise if !defined?(Vagrant) || !defined?(Vagrant::Errors)
  raise if !e.is_a?(Vagrant::Errors::VagrantError)

  require 'log4r'
  logger = Log4r::Logger.new("vagrant::bin::vagrant")
  logger.error("Vagrant experienced an error! Details:")
  logger.error(e.inspect)
  logger.error(e.message)
  logger.error(e.backtrace.join("\n"))

  if env
    opts = { prefix: false }
    env.ui.error(e.message, **opts) if e.message
    env.ui.machine("error-exit", e.class.to_s, e.message.to_s)
  else
    $stderr.puts "Vagrant failed to initialize at a very early stage:\n\n"
    $stderr.puts e.message
  end

  exit e.status_code if e.respond_to?(:status_code)
  exit 255 # An error occurred with no status code defined
end
