# -*- encoding : utf-8 -*-

class Card
  # The {Cache} class manages and integrates {Temporary} and {Shared}
  # caching. The {Temporary} cache is typically process- and request- specific
  # and is often "ahead" of the database; the {Shared} cache is typically
  # shared across processes and tends to stay true to the database.
  #
  # Any ruby Class can declare and/or retrieve its own cache as follows:
  #
  # ```` Card::Cache[MyClass] ````
  #
  # Typically speaking, mod developers do not need to use the Cache classes
  # directly, because caching is automatically handled by Card#fetch
  #
  class Cache
    extend Card::Cache::ClassMethods

    attr_reader :shared, :temp

    # Cache#new initializes a {Temporary} cache, and -- if a :store opt
    # is provided -- a {Shared} cache
    # @param opts [Hash]
    # @option opts [Rails::Cache] :store
    # @option opts [Constant] :class
    def initialize opts={}
      @klass = opts[:class]
      @shared = Shared.new opts if opts[:store]
      @temp = Temporary.new @klass
    end

    # read cache value (and write to temp cache if missing)
    # @param key [String]
    def read key
      @temp.fetch(key) do
        track :read, key do
          @shared&.read key
        end
      end
    end

    def read_multi keys
      with_multiple_keys keys do
        @temp.fetch_multi keys do |missing_keys|
          track :read_multi, missing_keys do
            @shared ? @shared.read_multi(missing_keys) : {}
          end
        end
      end
    end

    # write to hard (where applicable) and temp cache
    # @param key [String]
    # @param value
    def write key, value
      track :write, key do
        @shared&.write key, value
        @temp.write key, value
      end
    end

    # read and (if not there yet) write
    # @param key [String]
    def fetch key, &block
      @temp.fetch(key) do
        track :fetch, key do
          @shared ? @shared.fetch(key, &block) : yield(key)
        end
      end
    end

    # delete specific cache entries by key
    # @param key [String]
    def delete key
      track :delete, key do
        @shared&.delete key
        @temp.delete key
      end
    end

    # reset both caches (for a given Card::Cache instance)
    def reset
      @shared&.reset
      @temp.reset
    end

    # test for the existence of the key in either cache
    # @return [true/false]
    def exist? key
      @temp.exist?(key) || @shared&.exist?(key)
    end

    private

    def with_multiple_keys keys
      keys.size > 1 ? yield : {}
    end

    def track action, key
      return yield unless (log_action = Cardio.config.cache_log_level)

      key = key.size if key.is_a? Array
      unless log_action == :tally
        Rails.logger.send log_action, "#{action.to_s.upcase} (#{@klass}): #{key}"
      end
      tally action
      yield
    end

    def tally type
      h = Card::Cache.counter ||= {}
      t = h[@klass] ||= {}
      t[type] ||= 0
      t[type] += 1
    end
  end
end
