#
# Gems
#

require 'active_support/core_ext/module/introspection'

#
# Project
#

require 'metasploit/framework/command'
require 'metasploit/framework/parsed_options'
require 'metasploit/framework/require'

# Based on pattern used for lib/rails/commands in the railties gem.
class Metasploit::Framework::Command::Base
  #
  # Attributes
  #

  # @!attribute [r] application
  #   The Rails application for metasploit-framework.
  #
  #   @return [Metasploit::Framework::Application]
  attr_reader :application

  # @!attribute [r] parsed_options
  #   The parsed options from the command line.
  #
  #   @return (see parsed_options)
  attr_reader :parsed_options

  #
  # Class Methods
  #

  # @note {require_environment!} should be called to load
  #   `config/application.rb` to so that the RAILS_ENV can be set from the
  #   command line options in `ARGV` prior to `Rails.env` being set.
  # @note After returning, `Rails.application` will be defined and configured.
  #
  # Parses `ARGV` for command line arguments to configure the
  # `Rails.application`.
  #
  # @return (see parsed_options)
  def self.require_environment!
    parsed_options = self.parsed_options
    # RAILS_ENV must be set before requiring 'config/application.rb'
    parsed_options.environment!
    ARGV.replace(parsed_options.positional)

    # allow other Rails::Applications to use this command
    if !defined?(Rails) || Rails.application.nil?
      # @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/commands.rb#L39-L40
      require Pathname.new(__FILE__).parent.parent.parent.parent.parent.join('config', 'application')
    end

    # have to configure before requiring environment because
    # config/environment.rb calls initialize! and the initializers will use
    # the configuration from the parsed options.
    parsed_options.configure(Rails.application)

    Rails.application.require_environment!

    parsed_options
  end

  def self.parsed_options
    parsed_options_class.new
  end

  def self.parsed_options_class
    @parsed_options_class ||= parsed_options_class_name.constantize
  end

  def self.parsed_options_class_name
    @parsed_options_class_name ||= "#{module_parent.module_parent}::ParsedOptions::#{name.demodulize}"
  end

  def self.start
    parsed_options = require_environment!
    new(application: Rails.application, parsed_options: parsed_options).start
  end

  #
  # Instance Methods
  #

  # @param attributes [Hash{Symbol => ActiveSupport::OrderedOptions,Rails::Application}]
  # @option attributes [Rails::Application] :application
  # @option attributes [ActiveSupport::OrderedOptions] :parsed_options
  # @raise [KeyError] if :application is not given
  # @raise [KeyError] if :parsed_options is not given
  def initialize(attributes={})
    @application = attributes.fetch(:application)
    @parsed_options = attributes.fetch(:parsed_options)
  end

  # @abstract Use {#application} to start this command.
  #
  # Starts this command.
  #
  # @return [void]
  # @raise [NotImplementedError]
  def start
    raise NotImplementedError
  end
end
