module Temple
  # Immutable hash class which supports hash merging
  # @api public
  class ImmutableHash
    include Enumerable

    def initialize(*hash)
      @hash = hash.compact
    end

    def include?(key)
      @hash.any? {|h| h.include?(key) }
    end

    def [](key)
      @hash.each {|h| return h[key] if h.include?(key) }
      nil
    end

    def each
      keys.each {|k| yield(k, self[k]) }
    end

    def keys
      @hash.inject([]) {|keys, h| keys.concat(h.keys) }.uniq
    end

    def values
      keys.map {|k| self[k] }
    end

    def to_hash
      result = {}
      each {|k, v| result[k] = v }
      result
    end
  end

  # Mutable hash class which supports hash merging
  # @api public
  class MutableHash < ImmutableHash
    def initialize(*hash)
      super({}, *hash)
    end

    def []=(key, value)
      @hash.first[key] = value
    end

    def update(hash)
      @hash.first.update(hash)
    end
  end

  class OptionHash < MutableHash
    def initialize(*hash, &block)
      super(*hash)
      @handler = block
      @valid = {}
      @deprecated = {}
    end

    def []=(key, value)
      validate_key!(key)
      super
    end

    def update(hash)
      validate_hash!(hash)
      super
    end

    def valid_keys
      keys.concat(@valid.keys).uniq
    end

    def add_valid_keys(*keys)
      keys.flatten.each { |key| @valid[key] = true }
    end

    def add_deprecated_keys(*keys)
      keys.flatten.each { |key| @valid[key] = @deprecated[key] = true }
    end

    def validate_hash!(hash)
      hash.to_hash.keys.each {|key| validate_key!(key) }
    end

    def validate_key!(key)
      @handler.call(self, key, true) if deprecated_key?(key)
      @handler.call(self, key, false) unless valid_key?(key)
    end

    def deprecated_key?(key)
      @deprecated.include?(key) ||
        @hash.any? {|h| h.deprecated_key?(key) if h.respond_to?(:deprecated_key?) }
    end

    def valid_key?(key)
      include?(key) || @valid.include?(key) ||
        @hash.any? {|h| h.valid_key?(key) if h.respond_to?(:valid_key?) }
    end
  end
end
