# encoding: utf-8
require 'fedux_org_stdlib/require_files'
require 'fedux_org_stdlib/gem_plugins/no_plugin'
require 'fedux_org_stdlib/gem_plugins/plugin'
require 'fedux_org_stdlib/gem_plugins/exceptions'
require 'fedux_org_stdlib/list'

require_library %w( active_support/core_ext/string/inflections active_support/core_ext/object/blank hirb/console )

module FeduxOrgStdlib
  module GemPlugins
    # Plugin Manager
    #
    # To use the plugin manager you should build the plugins for your
    # application as rubygems. How you name the plugins is up to you, but it is
    # recommended to use something like `your-application-<plugin>`, where
    # `your-application-` is the prefix (please mind the dash at the end).
    #
    # @example PluginManager-class
    #
    #    module YourApplication
    #      class PluginManager < FeduxOrgStdlib::GemPlugins::PluginManager
    #      end
    #    end
    #
    # @example Default prefix
    #
    # Please make sure you create a class for your plugin manager to make it
    # work. Otherwise give the prefix via parameter - see below.
    #
    #    # -- main.rb --
    #    # main module
    #    module YourApplication
    #      # The manager uses "#{self.name.underscore.gsub(/\//, '-')}-" as prefix
    #      @plugin_manager = PluginManager.new
    #
    #      class << self
    #        attr_reader :plugin_manager
    #
    #        def load_plugins
    #          self.plugin_manager.load_plugins
    #        end
    #      end
    #    end
    #
    #
    # @example Use different prefix
    #    # -- main.rb --
    #    # main module
    #    module YourApplication
    #      # The manager uses 'asdf-' as prefix
    #      @plugin_manager = PluginManager.new(prefix: 'asdf-')
    #
    #      [...]
    #    end
    #
    # At some place you need to load your plugins.
    #
    # @example Load plugins in your application
    #
    #   # -- runner.rb --
    #
    #   YourApplication.load_plugins
    #
    class PluginManager
      private

      attr_reader :plugins, :prefix, :whitelist, :blacklist

      public

      def initialize(prefix: __plugin_prefix, whitelist: [], blacklist: [])
        @prefix    = Regexp.new(prefix)
        @whitelist = Array(whitelist)
        @blacklist = Array(blacklist)

        # needs variables above
        @plugins   = locate_plugins
      end

      # Disable a plugin
      def disable_plugin(name)
        plugin = plugins.find(NoPlugin.new(name)) { |p| p.name == name }

        return if plugin.blank?

        plugin.disable
      end

      # Enable a plugin
      def enable_plugin(name)
        plugin = plugins.find(NoPlugin.new(name)) { |p| p.name == name }

        return if plugin.blank?

        plugin.enable
      end

      # Require all enabled plugins, disabled plugins are skipped.
      def load_plugins
        each_enabled_plugin(&:activate)
      end

      # String representation
      def to_s
        data = plugins.sort.reduce([]) { |a, e| a << { name: e.name, enabled: e.enabled?, gem_name: e.gem_name, required_file: e.required_file } }

        List.new(data).to_s(
          fields: [:name, :gem_name, :enabled, :required_file]
        )
      end

      private

      def each_enabled_plugin(&block)
        plugins.select(&:enabled?).each(&block)
      end

      # Find all installed plugins and store them in an internal array.
      def locate_plugins
        plgs = []

        Gem.refresh

        (Gem::Specification.respond_to?(:each) ? Gem::Specification : Gem.source_index.find_name('')).each do |gem|
          name = gem.name.sub(prefix, '')

          next if gem.name !~ prefix
          next if plgs.any? { |plugin| plugin.name == name }

          enabled = if !whitelist.blank? && !whitelist.include?(name)
                      false
                    elsif !blacklist.blank? && blacklist.include?(name)
                      false
                    else
                      true
                    end

          plgs << Plugin.new(
            name,
            gem_name: gem.name,
            enabled: enabled,
            prefix: prefix
          )
        end

        plgs
      end

      def __plugin_prefix
        name = if self.class.name.deconstantize.blank?
                 self.class.name
               else
                 self.class.name.deconstantize
               end

        "#{name.underscore.gsub(/\//, '-')}-"
      end
    end
  end
end