# frozen_string_literal: true

require_relative './multi_logger.rb'
require_relative './config/index.rb'
require_relative './config/yaml_duplication_checker.rb'
require 'singleton'
require 'yaml'

# module as namespace
module Fusuma
  # read keymap from yaml file
  class Config
    class NotFoundError < StandardError; end
    class InvalidFileError < StandardError; end

    include Singleton

    class << self
      def search(keys)
        instance.search(keys)
      end

      def custom_path=(new_path)
        instance.custom_path = new_path
      end
    end

    attr_reader :keymap
    attr_reader :custom_path

    def initialize
      @custom_path = nil
      @cache = nil
      @keymap = nil
    end

    def custom_path=(new_path)
      @custom_path = new_path
      reload
    end

    def reload
      @cache = nil
      path = find_filepath
      MultiLogger.info "reload config: #{path}"
      @keymap = validate(path)
      self
    end

    # @return [Hash]
    # @raise [InvalidError]
    def validate(path)
      duplicates = []
      YAMLDuplicationChecker.check(File.read(path), path) do |ignored, duplicate|
        MultiLogger.error "#{path}: #{ignored.value} is duplicated"
        duplicates << duplicate.value
      end
      raise InvalidFileError, "Detect duplicate keys #{duplicates}" unless duplicates.empty?

      yaml = YAML.load_file(path)

      raise InvalidFileError, 'Invaid YAML file' unless yaml.is_a? Hash

      yaml.deep_symbolize_keys
    rescue StandardError => e
      MultiLogger.error e.message
      raise InvalidFileError, e.message
    end

    # @param index [Index]
    def search(index)
      cache(index.cache_key) do
        index.keys.reduce(keymap) do |location, key|
          if location.is_a?(Hash)
            begin
              if key.skippable
                location.fetch(key.symbol, location)
              else
                location.fetch(key.symbol, nil)
              end
            end
          else
            location
          end
        end
      end
    end

    private

    def find_filepath
      filename = 'fusuma/config.yml'
      if custom_path
        return expand_custom_path if File.exist?(expand_custom_path)

        raise NotFoundError, "#{expand_custom_path} is NOT FOUND"
      elsif File.exist?(expand_config_path(filename))
        expand_config_path(filename)
      else
        MultiLogger.warn "config file: #{expand_config_path(filename)} is NOT FOUND"
        expand_default_path(filename)
      end
    end

    def expand_custom_path
      File.expand_path(custom_path)
    end

    def expand_config_path(filename)
      File.expand_path "~/.config/#{filename}"
    end

    def expand_default_path(filename)
      File.expand_path "../../#{filename}", __FILE__
    end

    def cache(key)
      @cache ||= {}
      key = key.join(',') if key.is_a? Array
      if @cache.key?(key)
        @cache[key]
      else
        @cache[key] = block_given? ? yield : nil
      end
    end
  end
end

# activesupport-4.1.1/lib/active_support/core_ext/hash/keys.rb
class Hash
  def deep_symbolize_keys
    deep_transform_keys do |key|
      begin
        key.to_sym
      rescue StandardError
        key
      end
    end
  end

  def deep_transform_keys(&block)
    result = {}
    each do |key, value|
      result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
    end
    result
  end
end