#
# Fluentd
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
#

require 'fluent/config/error'

module Fluent
  class Registry
    DEFAULT_PLUGIN_PATH = File.expand_path('../plugin', __FILE__)
    FLUENT_LIB_PATH = File.dirname(File.dirname(DEFAULT_PLUGIN_PATH))

    def initialize(kind, search_prefix, dir_search_prefix: nil)
      @kind = kind
      @search_prefix = search_prefix
      @dir_search_prefix = dir_search_prefix
      @map = {}
      @paths = []
    end

    attr_reader :kind, :paths, :map, :dir_search_prefix

    def register(type, value)
      type = type.to_sym
      @map[type] = value
    end

    def lookup(type)
      type = type.to_sym
      if value = @map[type]
        return value
      end
      search(type)
      if value = @map[type]
        return value
      end
      raise NotFoundPluginError.new("Unknown #{@kind} plugin '#{type}'. Run 'gem search -rd fluent-plugin' to find plugins",
                                    kind: @kind, type: type)
    end

    def reverse_lookup(value)
      @map.each do |k, v|
        return k if v == value
      end
      nil
    end

    def search(type)
      # search from additional plugin directories
      if @dir_search_prefix
        path = "#{@dir_search_prefix}#{type}"
        files = @paths.filter_map { |lp|
          lpath = File.expand_path(File.join(lp, "#{path}.rb"))
          File.exist?(lpath) ? lpath : nil
        }
        unless files.empty?
          # prefer newer version
          require files.max
          return
        end
      end

      path = "#{@search_prefix}#{type}"

      # prefer LOAD_PATH than gems
      files = $LOAD_PATH.filter_map { |lp|
        if lp == FLUENT_LIB_PATH
          nil
        else
          lpath = File.expand_path(File.join(lp, "#{path}.rb"))
          File.exist?(lpath) ? lpath : nil
        end
      }
      unless files.empty?
        # prefer newer version
        require files.max
        return
      end

      # Find from gems and prefer newer version
      specs = Gem::Specification.find_all { |spec|
        if spec.name == 'fluentd'.freeze
          false
        else
          spec.contains_requirable_file? path
        end
      }.sort_by { |spec| spec.version }
      if spec = specs.last
        spec.require_paths.each { |lib|
          file = "#{spec.full_gem_path}/#{lib}/#{path}"
          if File.exist?("#{file}.rb")
            require file
            return
          end
        }
      end

      # Lastly, load built-in plugin
      lpath = File.expand_path(File.join(FLUENT_LIB_PATH, "#{@search_prefix}#{type}.rb"))
      if File.exist?(lpath)
        require lpath
        return
      end
    end
  end
end
