# frozen_string_literal: true

require_relative 'version'

# The Getopt module serves as a namespace only
module Getopt
  # The Getopt::Std class serves as a base class for the getopts method.
  class Std
    include Getopt::Version

    # The Getopt::Std::Error class is raised if there are any illegal
    # command line arguments.
    #
    class Error < StandardError; end

    # Processes single character command line options with option
    # clustering. This information is parsed from ARGV and returned
    # as a hash, with the switch (minus the "-") as the key. The value
    # for that key is either true/false (boolean switches) or the argument
    # that was passed to the switch.
    #
    # Characters followed by a ":" require an argument. The rest are
    # considered boolean switches. If a switch that accepts an argument
    # appears more than once, the value for that key becomes an array
    # of values.
    #
    # Example:
    #
    #  # Look for -o with argument, and -I and -D boolean arguments
    #  opt = Getopt::Std.getopts("o:ID")
    #
    #  if opt["I"]
    #    # Do something if -I passed
    #  end
    #
    #  if opt["D"]
    #    # Do something if -D passed
    #  end
    #
    #  if opt["o"]
    #    case opt["o"]
    #      # Do something
    #    end
    #  end
    #
    def self.getopts(switches)
      args  = switches.split(/ */)
      hash  = {}
      regex = /^-([^\s-])\s*(\S*)/s

      while !ARGV.empty? && regex.match(ARGV.first)
        first, rest = Regexp.last_match(1), Regexp.last_match(2)
        pos = switches.index(first)

        # Switches on the command line must appear among the characters
        # declared in +switches+.
        raise Error, "invalid option '#{first}'" unless pos

        if args[pos + 1] == ':'
          ARGV.shift
          if rest.empty?
            rest = ARGV.shift

            # Ensure that switches requiring arguments actually
            # receive a (non-switch) argument.
            if rest.nil? || rest.empty?
              raise Error, "missing argument for '-#{args[pos]}'"
            end

            # Do not permit switches that require arguments to be
            # followed immediately by another switch.
            temp_args = args.map{ |e| "-#{e}" }

            if temp_args.include?(rest) || temp_args.include?(rest[1..])
              err = "cannot use switch '#{rest}' as argument to another switch"
              raise Error, err
            end

            # For non boolean switches, arguments that appear multiple
            # times are converted to an array (or pushed onto an already
            # existant array).
            if hash.key?(first)
              hash[first] = [hash[first], rest].flatten
            else
              hash[first] = rest
            end
          else
            # Do not permit switches that require arguments to be
            # followed immediately by another switch.
            if args.include?(rest) || args.include?(rest[1..])
              err = "cannot use switch '#{rest}' as argument to another switch"
              raise Error, err
            end
          end
        else
          hash[first] = true # Boolean switch
          if rest.empty?
            ARGV.shift
          else
            ARGV[0] = "-#{rest}"
          end
        end
      end

      hash
    end
  end
end
