
from __future__ import absolute_import

import logging
import threading

from vdsm.storage import constants as sc
from vdsm.storage import exception as se
from vdsm.storage import lvm
from vdsm.storage import misc
from vdsm.storage import multipath


class DomainProxy(object):

    def __init__(self, cache, sdUUID):
        self._sdUUID = sdUUID
        self._cache = cache

    def __getattr__(self, attrName):
        return getattr(self.getRealDomain(), attrName)

    def getRealDomain(self):
        return self._cache._realProduce(self._sdUUID)


class StorageDomainCache:

    log = logging.getLogger('storage.StorageDomainCache')

    STORAGE_UPDATED = 0
    STORAGE_STALE = 1
    STORAGE_REFRESHING = 2

    def __init__(self, storage_repo):
        self._syncroot = threading.Condition()
        self.__domainCache = {}
        self.__inProgress = set()
        self.__staleStatus = self.STORAGE_STALE
        self.storage_repo = storage_repo

    def invalidateStorage(self):
        with self._syncroot:
            self.__staleStatus = self.STORAGE_STALE

    @misc.samplingmethod
    def refreshStorage(self, resize=True):
        self.__staleStatus = self.STORAGE_REFRESHING

        multipath.rescan()
        if resize:
            multipath.resize_devices()
        lvm.invalidateCache()

        with self._syncroot:
            if self.__staleStatus == self.STORAGE_REFRESHING:
                self.__staleStatus = self.STORAGE_UPDATED

    def produce_manifest(self, sdUUID):
        return self.produce(sdUUID).manifest

    def produce(self, sdUUID):
        domain = DomainProxy(self, sdUUID)
        domain.getRealDomain()
        return domain

    def _realProduce(self, sdUUID):
        with self._syncroot:
            while True:
                domain = self.__domainCache.get(sdUUID)

                if domain is not None:
                    return domain

                if sdUUID not in self.__inProgress:
                    self.__inProgress.add(sdUUID)
                    break

                self._syncroot.wait()

        try:
            if self.__staleStatus != self.STORAGE_UPDATED:
                self.refreshStorage()

            domain = self._findDomain(sdUUID)

            with self._syncroot:
                self.__domainCache[sdUUID] = domain
                return domain

        finally:
            with self._syncroot:
                self.__inProgress.remove(sdUUID)
                self._syncroot.notifyAll()

    def _findDomain(self, sdUUID):
        try:
            findMethod = self.knownSDs[sdUUID]
        except KeyError:
            findMethod = self._findUnfetchedDomain

        return findMethod(sdUUID)

    def _findUnfetchedDomain(self, sdUUID):
        from vdsm.storage import blockSD
        from vdsm.storage import glusterSD
        from vdsm.storage import localFsSD
        from vdsm.storage import nfsSD

        self.log.debug("looking for domain %s", sdUUID)

        for mod in (blockSD, glusterSD, localFsSD, nfsSD):
            try:
                return mod.findDomain(sdUUID)
            except se.StorageDomainDoesNotExist:
                pass
            except Exception:
                self.log.error("Error while looking for domain `%s`", sdUUID,
                               exc_info=True)

        raise se.StorageDomainDoesNotExist(sdUUID)

    def getUUIDs(self):
        from vdsm.storage import blockSD
        from vdsm.storage import fileSD

        uuids = []
        for mod in (blockSD, fileSD):
            uuids.extend(mod.getStorageDomainsList())

        return uuids

    def refresh(self):
        with self._syncroot:
            lvm.invalidateCache()
            self.__domainCache.clear()

    def manuallyAddDomain(self, domain):
        with self._syncroot:
            self.__domainCache[domain.sdUUID] = domain

    def manuallyRemoveDomain(self, sdUUID):
        with self._syncroot:
            try:
                del self.__domainCache[sdUUID]
            except KeyError:
                pass


sdCache = StorageDomainCache(sc.REPO_DATA_CENTER)
