"""
:mod: RSSCache

Extension of DictCache to be used within RSS

"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__RCSID__ = '$Id$'

import datetime
import threading
import time

from DIRAC import S_OK, S_ERROR
from DIRAC.Core.Utilities.DictCache import DictCache


class RSSCache(object):
  '''
  Cache with purgeThread integrated
  '''

  def __init__(self, lifeTime, updateFunc=None, cacheHistoryLifeTime=None):
    '''
    Constructor
    '''

    self.__lifeTime = lifeTime
    # lifetime of the history on hours
    self.__cacheHistoryLifeTime = (1 and cacheHistoryLifeTime) or 24
    self.__updateFunc = updateFunc

    # RSSCache
    self.__rssCache = DictCache()
    self.__rssCacheStatus = []  # ( updateTime, message )
    self.__rssCacheLock = threading.Lock()

    # Create purgeThread
    self.__refreshStop = False
    self.__refreshThread = threading.Thread(target=self.__refreshCacheThreadRun)
    self.__refreshThread.setDaemon(True)

  def startRefreshThread(self):
    '''
    Run refresh thread.
    '''
    self.__refreshThread.start()

  def stopRefreshThread(self):
    '''
    Stop refresh thread.
    '''
    self.__refreshStop = True

  def isCacheAlive(self):
    '''
    Returns status of the cache refreshing thread
    '''
    return S_OK(self.__refreshThread.is_alive())

  def setLifeTime(self, lifeTime):
    '''
    Set cache life time
    '''
    self.__lifeTime = lifeTime

  def setCacheHistoryLifeTime(self, cacheHistoryLifeTime):
    '''
    Set cache life time
    '''
    self.__cacheHistoryLifeTime = cacheHistoryLifeTime

  def getCacheKeys(self):
    '''
    List all the keys stored in the cache.
    '''
    self.__rssCacheLock.acquire()
    keys = self.__rssCache.getKeys()
    self.__rssCacheLock.release()

    return S_OK(keys)

  def acquireLock(self):
    '''
    Acquires RSSCache lock
    '''
    self.__rssCacheLock.acquire()

  def releaseLock(self):
    '''
    Releases RSSCache lock
    '''
    self.__rssCacheLock.release()

  def getCacheStatus(self):
    '''
    Return the latest cache status
    '''
    self.__rssCacheLock.acquire()
    if self.__rssCacheStatus:
      res = dict([self.__rssCacheStatus[0]])
    else:
      res = {}
    self.__rssCacheLock.release()
    return S_OK(res)

  def getCacheHistory(self):
    '''
    Return the cache updates history
    '''
    self.__rssCacheLock.acquire()
    res = dict(self.__rssCacheStatus)
    self.__rssCacheLock.release()
    return S_OK(res)

  def get(self, resourceKey):
    '''
    Gets the resource(s) status(es). Every resource can have multiple statuses,
    so in order to speed up things, we store them on the cache as follows::

      { (<resourceName>,<resourceStatusType0>) : whatever0,
        (<resourceName>,<resourceStatusType1>) : whatever1,
      }

    '''

    # cacheKey = '%s#%s' % ( resourceName, resourceStatusType )

    self.__rssCacheLock.acquire()
    resourceStatus = self.__rssCache.get(resourceKey)
    self.__rssCacheLock.release()

    if resourceStatus:
      return S_OK({resourceKey: resourceStatus})
    return S_ERROR('Cannot get %s' % resourceKey)

  def getBulk(self, resourceKeys):
    '''
    Gets values for resourceKeys in one ATOMIC operation.
    '''

    result = {}
    self.__rssCacheLock.acquire()

    for resourceKey in resourceKeys:

      resourceRow = self.__rssCache.get(resourceKey)
      if not resourceRow:
        return S_ERROR('Cannot get %s' % resourceKey)
      result.update({resourceKey: resourceRow})

    self.__rssCacheLock.release()
    return S_OK(result)

  def resetCache(self):
    '''
    Reset cache.
    '''
    self.__rssCacheLock.acquire()
    self.__rssCache.purgeAll()
    self.__rssCacheLock.release()

    return S_OK()

  def refreshCache(self):
    '''
    Clears the cache and gets its latest version, not Thread safe !
    Acquire a lock before using it ! ( and release it afterwards ! )
    '''

    self.__rssCache.purgeAll()

    if self.__updateFunc is None:
      return S_ERROR('RSSCache has no updateFunction')
    newCache = self.__updateFunc()
    if not newCache['OK']:
      return newCache

    itemsAdded = self.__updateCache(newCache['Value'])

    return itemsAdded

  def refreshCacheAndHistory(self):
    '''
    Method that refreshes the cache and updates the history. Not thread safe,
    you must acquire a lock before using it, and release it right after !
    '''

    refreshResult = self.refreshCache()

    now = datetime.datetime.utcnow()

    if self.__rssCacheStatus:
      # Check oldest record
      dateInserted, _message = self.__rssCacheStatus[-1]
      if dateInserted < now - datetime.timedelta(hours=self.__cacheHistoryLifeTime):
        self.__rssCacheStatus.pop()

    self.__rssCacheStatus.insert(0, (now, refreshResult))

################################################################################
# Private methods

  def __updateCache(self, newCache):
    '''
    The new cache must be a dictionary, which should look like::

      { ( <resourceName>,<resourceStatusType0>) : whatever0,
        ( <resourceName>,<resourceStatusType1>) : whatever1,
      }

    '''

    itemsCounter = 0

    for cacheKey, cacheValue in newCache.items():
      self.__rssCache.add(cacheKey, self.__lifeTime, value=cacheValue)
      itemsCounter += 1

    return S_OK(itemsCounter)

  def __refreshCacheThreadRun(self):
    '''
    Method that refreshes periodically the cache.
    '''

    while not self.__refreshStop:

      self.__rssCacheLock.acquire()
      self.refreshCacheAndHistory()
      self.__rssCacheLock.release()

      time.sleep(self.__lifeTime)

    self.__refreshStop = False

################################################################################
# EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF
