module BBLib
  # Easy way to see if all objects in an array are of a given class.
  def self.are_all?(klass, *objects)
    objects.all? { |object| object.is_a?(klass) }
  end

  # Easy way to see if any of the passed objects are of the given class.
  def self.are_any?(klass, *objects)
    objects.any? { |object| object.is_a?(klass) }
  end

  # Checks to see if an object is of any of the given classes.
  def self.is_any?(object, *klasses)
    klasses.any? { |klass| object.is_a?(klass) }
  end

  # Takes any type of object and converts it into a hash based on its instance
  # variables.
  def self.to_hash(obj)
    return { obj => nil } if obj.instance_variables.empty?
    hash = {}
    obj.instance_variables.each do |var|
      value = obj.instance_variable_get(var)
      if value.is_a?(Array)
        hash[var.to_s.delete('@')] = value.map { |v| v.respond_to?(:obj_to_hash) && !v.instance_variables.empty? ? v.obj_to_hash : v }
      elsif value.is_a?(Hash)
        begin
          unless hash[var.to_s.delete('@')].is_a?(Hash) then hash[var.to_s.delete('@')] = {} end
        rescue
          hash[var.to_s.delete('@')] = {}
        end
        value.each do |k, v|
          hash[var.to_s.delete('@')][k.to_s.delete('@')] = v.respond_to?(:obj_to_hash) && !v.instance_variables.empty? ? v.obj_to_hash : v
        end
      elsif value.respond_to?(:obj_to_hash) && !value.instance_variables.empty?
        hash[var.to_s.delete('@')] = value.obj_to_hash
      else
        hash[var.to_s.delete('@')] = value
      end
    end
    hash
  end

  # Extracts all hash based arguments from an ary of arguments. Only hash pairs with
  # a symbol as the key are returned. Use hash_args if you also want to treat
  # String keys as named arguments.
  def self.named_args(*args)
    args.last.is_a?(Hash) && args.last.keys.all? { |k| k.is_a?(Symbol) } ? args.last : {}
  end

  # Same as standard named_args but removes the named arguments from the array.
  def self.named_args!(*args)
    if args.last.is_a?(Hash) && args.last.keys.all? { |k| k.is_a?(Symbol) }
      args.delete_at(-1)
    else
      {}
    end
  end

  # Similar to named_args but also treats String keys as named arguments.
  def self.hash_args(*args)
    args.find_all { |a| a.is_a?(Hash) }.each_with_object({}) { |a, h| h.merge!(a) }
  end

  # Send a chain of methods to an object and each result of the previous method.
  def self.recursive_send(obj, *methods)
    methods.each do |args|
      obj = obj.send(*args)
    end
    obj
  end

  # Returns the encapsulating object space of a given class.
  # Ex: For a class called BBLib::String, this method will return BBLib as the namespace.
  # Ex2: For a class BBLib::String::Char, this method will return BBLib::String as the namespace.
  def self.namespace_of(klass)
    split = klass.to_s.split('::')
    return klass if split.size == 1
    Object.const_get(split[0..-2].join('::'))
  end

  # Returns the root namespace of a given class if it is nested.
  def self.root_namespace_of(klass)
    Object.const_get(klass.to_s.gsub(/::.*/, ''))
  end

  # Create a new class or module recursively within a provided namespace. If a
  # constant matching the requested one already exist it is returned. Any
  # block passed to this method will be evaled in the created/found constant.
  def self.const_create(name, value, strict: true, base: Object, type_of_missing: nil, &block)
    namespace = base
    unless base.const_defined?(name)
      type_of_missing = Module unless type_of_missing
      name = name.uncapsulate('::')
      if name.include?('::')
        namespaces = name.split('::')
        name = namespaces.pop
        namespaces.each do |constant|
          unless namespace.const_defined?(constant)
            match = namespace.const_set(constant, type_of_missing.new)
          end
          namespace = namespace.const_get(constant)
        end
      end
      namespace.const_set(name, value)
    end
    object = namespace.const_get(name)
    raise TypeError, "Expected a #{value.class} but #{namespace}::#{name} is a #{object.class}" if strict && object.class != value.class
    object.tap do |constant|
      constant.send(:class_exec, &block) if block
    end
  end

  def self.class_create(name, *args, **opts, &block)
    const_create(name, Class.new(*args), **opts, &block)
  end

  def self.module_create(name, *args, **opts, &block)
    const_create(name, Module.new(*args), **opts, &block)
  end
end