# encoding: utf-8
require 'fedux_org_stdlib/require_files'
require 'fedux_org_stdlib/file_finder/exceptions'
require 'fedux_org_stdlib/logging/logger'
require_library %w(json active_support/core_ext/string/inflections set active_support/core_ext/hash/slice active_support/core_ext/object/blank active_support/core_ext/hash/keys)

module FeduxOrgStdlib
  # This class detects the file name for an config file. By default it will
  # look for a suitable config file in the given order:
  #
  # 1. $HOME/.config/<application_name>/<config_file>.yaml
  # 2. $HOME/.<application_name>/<config_file>.yaml
  # 2. $HOME/.<config_file>.yaml
  # 2. $HOME/.<config_file>rc
  # 3. /etc/.<application_name>/<config_file>.yaml
  #
  # Please keep in mind
  #
  # * application_name: Module of your class, e.g. "MyApplication" becomes
  #   "my_application"
  # * config_file: Pluarized name of your class and "File" strip
  #   off, e.g "ClientFile" becomes "client.yaml"
  #
  # Most conventions defined by me are implemented as separate methods. If one convention
  # is not suitable for your use case, just overwrite the method.
  #
  # If you prefer to use a different path to the config file or name of the
  # config file one of the following methods needs to be overwritten:
  #
  # * config_file
  # * config_name
  # * application_name
  #
  # If you want the class to look for your config file at a different place
  # overwrite the following method
  #
  # * allowed_config_file_paths
  #
  # Below you find some examples for the usage of the class:
  #
  # @example Create config with one writer and reader
  #   module MyApplication
  #     class ClientFile < FileFinder
  #     end
  #   end
  class FileFinder
    # Create a new instance of config
    #
    # It tries to find a suitable configuration file. If it doesn't find one
    # the config is empty and uses the defaults defined within a config class
    #
    # @param [String] file
    #   Path where config file is stored. The file will be read by the
    #   `config_engine`.
    #
    # @raise [Exceptions::FileFinderNotReadable]
    #   If an avaiable config file could not be read by the config engine
    #
    # @return [FileFinder]
    #   The config instance. If the resulting data structure created by the
    #   config_engine does not respond to `:[]` an empty config object will be
    #   created.

    attr_reader :file, :logger

    def initialize(
      logger: FeduxOrgStdlib::Logging::Logger.new,
      file: _available_config_file
    )
      @logger             = logger
      @file               = file

      unless file
        logger.debug "No configuration file found at #{_allowed_config_file_paths.to_list}, therefor I'm going to use an empty config object instead."
      end
    end

    # Return the path to the preferred configuration file
    # @return [String]
    #   The path to the preferred configuration file
    def preferred_configuration_file
      _allowed_config_file_paths.first
    end

    private

    # The name of the config file
    #
    # @return [String]
    #   The name of the config file. It defaults to `<config_name>.yaml`. If
    #   you want to use a different file name you need to overwrite this
    #   method.
    def _config_file
      "#{_config_name}#{_config_file_suffix}"
    end

    # The suffix of the config file
    #
    # @return [String]
    #   The suffix of the config file
    def _config_file_suffix
      '.yaml'
    end

    # The base name of the config
    #
    # @return [String]
    #   This one returns the base name of the config file (without the file
    #   extension). It uses the class name of the config class
    #
    # @example Determine the base name of the config
    #
    #   class ClientFile; end
    #
    # This will result in `client` as base name for the config file.
    def _config_name
      unless (name = _class_name.sub(/File/, '').underscore).blank?
        return name
      end

      fail Exceptions::ClassNameIsMissing, JSON.dump(klass: _class_name)
    end

    # The name of your application
    #
    # @return [String]
    #  This will strip of the class part of fully qualified class name and
    #  converted it to a path.
    #
    # @example Determine application name
    #
    #   class MyApplication::MyFile; end
    #
    # This will be converted to
    #
    #    my_application
    def _application_name
      _module_name.underscore
    end

    # The paths where to look for the config file
    #
    # @return [Array]
    #   A list of paths where the config object should look for its config
    #   file.
    def _allowed_config_file_paths
      [
        ::File.expand_path(::File.join('~', '.config', _application_name, _config_file)),
        ::File.expand_path(::File.join('~', format('.%s', _application_name), _config_file)),
        ::File.expand_path(::File.join('~', format('.%s', _config_file))),
        ::File.expand_path(::File.join('~', format('.%src', _config_name))),
        ::File.expand_path(::File.join('/etc', _application_name, _config_file)),
      ]
    end

    def _class_name
      self.class.name.to_s.demodulize
    end

    def _module_name
      self.class.to_s.deconstantize
    end

    def _available_config_file
      _allowed_config_file_paths.find { |f| ::File.exist? f }
    end
  end
end