module Restruct
  class NestedHash
    
    def self.new(type)
      Class.new Structure do

        include Enumerable
        
        const_set :TYPE, type

        def [](key)
          self.class::TYPE.new id: id[key], redis: redis, parent: self
        end

        def fetch(key)
          raise KeyError, "key not found: #{key}" unless key? key
          self[key]
        end

        def delete(key)
          self[key].tap(&:destroy)
        end

        def delete_if
          each { |k,v| delete k if yield k, v }
          self
        end

        def keep_if
          each { |k,v| delete k unless yield k, v }
          self
        end
        alias_method :select!, :keep_if

        def clear
          destroy
          self
        end

        def keys
          sections = id.sections.count + 1
          redis.call('KEYS', id['*']).map do |k| 
            Id.new(k).sections.take(sections).last
          end.uniq.sort
        end

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

        def values_at(*keys)
          keys.map { |key| self[key] }
        end

        def key?(key)
          keys.include? key.to_s
        end
        alias_method :has_key?, :key?

        def size
          keys.count
        end
        alias_method :count, :size
        alias_method :length, :size

        def empty?
          size == 0
        end

        def each
          keys.each { |key| yield key, self[key] }
        end
        alias_method :each_pair, :each

        def each_key
          each { |k,v| yield k }
        end

        def each_value
          each { |k,v| yield v }
        end
        
        def to_h
          each_with_object({}) do |(key, value), hash|
            hash[key] = value.respond_to?(:to_primitive) ? value.to_primitive : value
          end
        end
        alias_method :to_primitive, :to_h

        def dump
          each_with_object({}) do |(key, value), hash|
            hash[key] = value.dump
          end
        end

        def restore(dump)
          dump.each { |f,d| self[f].restore d }
        end

        def destroy
          values.each(&:destroy)
        end

      end
    end

  end
end