class LogicalModel
  module Cache

    def self.included(base)
      base.extend ClassMethods
      base.send(:include, InstanceMethods)
      base.send(:after_initialize, :initialize_loaded_at)
      base.send(:define_model_callbacks, :new_nested)
      base.send(:after_new_nested, :delete_cached)
    end

    module InstanceMethods
      attr_accessor :loaded_at

      def initialize_loaded_at
        self.loaded_at = Time.now
      end

      def _save
        super
      end

      def _save_with_cache
        delete_cached
        _save_without_cache
      end
      alias_method_chain :_save, :cache

      def _update(params={})
        super
      end

      def _update_with_cache(params={})
        delete_cached
        _update_without_cache params
      end
      alias_method_chain :_update, :cache

      def _destroy(params={})
        super
      end

      def _destroy_with_cache(params={})
        delete_cached
        _destroy_without_cache
      end
      alias_method_chain :_destroy, :cache

      def delete_cached
        model_name = self.class.to_s.pluralize.underscore
        self.class.logger.debug "LogicalModel Log CACHE: Delete cache for #{model_name}\/#{self.id}-.*"
        # Delete cached models
        self.class.cache_store.delete_matched(/#{model_name}\/#{self.id}-.*/)
        
        if self.class.respond_to?(:belongs_to_keys) && !self.class.belongs_to_keys.blank?
          # If it belongs to another model (or models)
          self.class.belongs_to_keys.each do |association_name, association_class|
            model_name = association_class.to_s.pluralize.underscore
            self.class.logger.debug "LogicalModel Log CACHE: Delete cache for #{model_name}\/#{self.send("#{association_name}_id")}-.*"
            # Delete cached models
            self.class.cache_store.delete_matched(/#{model_name}\/#{self.send("#{association_name}_id")}-.*/)
          end
        end
      end
    end

    module ClassMethods
      attr_accessor :expires_in

      def cache_store
        @cache_store ||= Rails.cache
      end

      # Will return key for cache
      # @param id [String] (nil)
      # @param params [Hash]
      def cache_key(id, params = {})
        model_name = self.to_s.pluralize.underscore
        params_hash = Digest::MD5.hexdigest(params.to_s)
        
        cache_key = "#{model_name}/#{id}-#{params_hash}"
      end

      def async_find(id, params={})
        super(id, params)
      end

      def async_find_with_cache(id, params = {}, &block)
        # Generate key based on params
        cache_key = self.cache_key(id, params)
        # If there is a cached value return it
        self.logger.debug "LogicalModel Log CACHE: Reading cache key=#{cache_key}"
        cached_result = self.cache_store.read(cache_key)
        if cached_result
          yield cached_result
        else
          self.logger.debug 'LogicalModel Log CACHE: Cache not present. Calling find_async without cache'
          async_find_without_cache(id, params, &block)
        end
      end
      alias_method_chain :async_find, :cache

      def async_find_response(id, params={}, body)
        super(id, params, body)
      end

      def async_find_response_with_cache(id, params={}, body)
        # remove params not used in cache_key
        %w(app_key token).each {|k| params.delete(k) }
        cache_value = async_find_response_without_cache(id, params, body)
        # Generate key based on params
        cache_key = self.cache_key(id, params)
        self.logger.debug "LogicalModel Log CACHE: Writing cache key=#{cache_key}"
        self.cache_store.write(cache_key, cache_value, :expires_in => self.expires_in || 10.minutes)
        cache_value
      end
      alias_method_chain :async_find_response, :cache

      def delete(id, params={})
        super(id, params)
      end

      def delete_with_cache(id, params = {})
        model_name = self.to_s.pluralize.underscore
        self.logger.debug "LogicalModel Log CACHE: Delete cache for #{model_name}\/#{id}-.*"
        self.cache_store.delete_matched(/#{model_name}\/#{id}-.*/)
        #TODO: also delete cache for parent (belongs_to)
        delete_without_cache(id, params)
      end
      alias_method_chain :delete, :cache

      def delete_multiple(ids, params={})
        super(ids, params)
      end

      def delete_multiple_with_cache(ids, params = {})
        model_name = self.to_s.pluralize.underscore
        self.logger.debug "LogicalModel Log CACHE: Delete cache for #{model_name}\/(#{ids.join('|')})-.*"
        self.cache_store.delete_matched(/#{model_name}\/(#{ids.join('|')})-.*/)
        #TODO: also delete cache for parent (belongs_to)
        delete_multiple_without_cache(ids, params)
      end
      alias_method_chain :delete_multiple, :cache
    end
  end
end
