"""
Memcached based cache plugin.
"""

from BaseCache import *
from time import time

try:
  import memcache
except ImportError:
  raise CachedMethodError, "Memcache module is not available"

MEMCACHED_SERVER_MAX_KEY_LENGTH = memcache.SERVER_MAX_KEY_LENGTH
## number of seconds before creating a new connection to memcached server
KEEP_ALIVE_MEMCACHED_CONNECTION_INTERVAL = 30  
 
class DistributedRamCache(BaseCache):
  """ Memcached based cache plugin. """

  def __init__(self, params):
    self._servers = params.get('server', '')
    self._debugLevel = params.get('debugLevel', 7)
    self._cache = memcache.Client(self._servers.split('\n'), self._debugLevel)
    self._last_cache_conn_creation_time = time()
    BaseCache.__init__(self)
        
  def getCacheStorage(self):
    ## if we use one connection object this causes  "MemCached: while expecting 'STORED', got unexpected response 'END'"
    ## messages in log files and thus sometimes can block the thread. For the moment we create
    ## a new conn object for every memcache access which in turns cmeans another socket. 
    ## See  addiionaly expireOldCacheEntries() comments for one or many connections.
    self._cache = memcache.Client(self._servers.split('\n'), debug=self._debugLevel)    
    return self._cache

  def checkAndFixCacheId(self, cache_id, scope):
    ## memcached doesn't support namespaces (cache scopes) so to "emmulate"
    ## such behaviour when constructing cache_id we add scope in front
    cache_id = "%s.%s" %(scope, cache_id) 
    ## memcached will fail to store cache_id longer than MEMCACHED_SERVER_MAX_KEY_LENGTH.
    if len(cache_id) > MEMCACHED_SERVER_MAX_KEY_LENGTH:
      cache_id = cache_id[:MEMCACHED_SERVER_MAX_KEY_LENGTH]
    return cache_id
    
  def get(self, cache_id, scope, default=None):
    cache_storage = self.getCacheStorage()
    cache_id = self.checkAndFixCacheId(cache_id, scope)
    cache_entry = cache_storage.get(cache_id)
    self.markCacheHit()
    return cache_entry
       
  def set(self, cache_id, scope, value, cache_duration= None, calculation_time=0):
    cache_storage = self.getCacheStorage()
    cache_id = self.checkAndFixCacheId(cache_id, scope)
    if not cache_duration:
      ## what should be default cache_duration when None is specified?
      ## currently when 'None' it means forever so give it  big value of 100 hours
      cache_duration = 360000
    cache_entry = CacheEntry(value, cache_duration, calculation_time)
    cache_storage.set(cache_id, cache_entry, cache_duration)
    self.markCacheMiss()
   
  def expireOldCacheEntries(self, forceCheck = False):
    """ Memcache has its own built in expire policy """
    ## we can not use one connection to memcached server for time being of DistributedRamCache
    ## because if memcached is restarted for any reason our connection object will have its socket 
    ## to memcached server closed.
    ## The workaround of this problem is to create a new connection for every cache access
    ## but that's too much overhead or create a new connection when cache is to be expired. 
    ## This way we can catch memcached server failures. BTW: This hack is forced by the lack functionality in python-memcached 
    #self._cache = memcache.Client(self._servers.split('\n'), debug=self._debugLevel) 
    pass    
        
  def delete(self, cache_id, scope):
    cache_storage = self.getCacheStorage()
    cache_id = self.checkAndFixCacheId(cache_id, scope)
    cache_storage.delete(cache_id)
        
  def has_key(self, cache_id, scope):
    if self.get(cache_id, scope):
      return True
    else:
      return False

  def getScopeList(self):
    ## memcached doesn't support namespaces (cache scopes) neither getting cached key list 
    return []
    
  def getScopeKeyList(self, scope):
    ## memcached doesn't support namespaces (cache scopes) neither getting cached key list 
    return []

  def clearCache(self):
    BaseCache.clearCache(self)
    cache_storage = self.getCacheStorage()
    cache_storage.flush_all()
    
  def clearCacheForScope(self, scope):
    ## memcached doesn't support namespaces (cache scopes) neither getting cached key list 
    pass