module IdentityCache
  class CacheFetcher
    attr_accessor :cache_backend

    def initialize(cache_backend)
      @cache_backend = cache_backend
    end

    def write(key, value)
      @cache_backend.write(key, value)
    end

    def delete(key)
      @cache_backend.write(key, IdentityCache::DELETED, :expires_in => IdentityCache::DELETED_TTL.seconds)
    end

    def clear
      @cache_backend.clear
    end

    def fetch_multi(keys, &block)
      results = cas_multi(keys, &block)
      results = add_multi(keys, &block) if results.nil?
      results
    end

    def fetch(key)
      result = nil
      yielded = false
      @cache_backend.cas(key) do |value|
        yielded = true
        unless IdentityCache::DELETED == value
          result = value
          break
        end
        result = yield
      end
      unless yielded
        result = yield
        add(key, result)
      end
      result
    end

    private

    def cas_multi(keys)
      result = nil
      @cache_backend.cas_multi(*keys) do |results|
        deleted = results.select {|_, v| IdentityCache::DELETED == v }
        results.reject! {|_, v| IdentityCache::DELETED == v }

        result = results
        updates = {}
        missed_keys = keys - results.keys
        unless missed_keys.empty?
          missed_vals = yield missed_keys
          missed_keys.zip(missed_vals) do |k, v|
            result[k] = v
            if deleted.include?(k)
              updates[k] = v
            else
              add(k, v)
            end
          end
        end

        break if updates.empty?
        updates
      end
      result
    end

    def add_multi(keys)
      values = yield keys
      result = Hash[keys.zip(values)]
      result.each {|k, v| add(k, v) }
    end

    def add(key, value)
      @cache_backend.write(key, value, :unless_exist => true)
    end
  end
end
