from parallels.core.registry import Registry
from parallels.core.utils.common.threading_utils import synchronized
from parallels.core.utils.data_storage import SessionFileDataStorage


class CacheStateControllers(object):
    def __init__(self):
        session_file = Registry.get_instance().get_context().session_files.get_cache_state_controllers_data_file()
        data_storage = SessionFileDataStorage(session_file)
        self._common = CommonCacheStateController(data_storage)
        self._reseller = ResellerCacheStateController(data_storage)
        self._customer = CustomerCacheStateController(data_storage)
        self._subscription = SubscriptionCacheStateController(data_storage)
        self._domain = DomainCacheStateController(data_storage)

    @property
    def common(self):
        return self._common

    @property
    def reseller(self):
        return self._reseller

    @property
    def customer(self):
        return self._customer

    @property
    def subscription(self):
        return self._subscription

    @property
    def domain(self):
        return self._domain


class CacheStateController(object):
    """Typical usage:

    class ContractCacheStateController(CacheController):
        @property
        def name(self):
            return 'contract'


    contract_cache_state_controller = ContractCacheStateController()


    class Employee(CacheOwner):
        def __init__(self, name)
            self.name = name

        @property
        def cache_owner_name:
            return 'employee-%s' % self.name

        def do_work():
            self.check_and_sign_employment_contract()
            ...  # start to work

        def check_and_sign_employment_contract():
            cache_state = contract_cache_state_controller.get_cache_state('employment contract', employee)
            if cache_state.is_valid():
                return
            ...  # go to HR department, view and sign the new contract
            cache_state.set_valid()


    def change_employment_contract()
        contract_cache_state_controller.invalidate_cache_states('employment contract')
        ...  # make changes in the existing contract, so all employee need to sign it again


    def get_to_work(employee)
        employee.do_work()


    # day one
    get_to_work(Employee('ipetrov'))  # ipetrov go to HR department before can start to work
    ...
    # day two
    get_to_work(Employee('ipetrov'))  # ipetrov start to work immediately
    ...
    change_employment_contract()  # someone has changed employment contract
    ...
    # day three
    get_to_work(Employee('ipetrov'))  # ipetrov go to HR department again
    """

    def __init__(self, data_storage):
        """
        :type data_storage: parallels.core.utils.data_storage.DataStorage
        """
        self._data_storage = data_storage
        self._data = self._data_storage.get_data(self.name, {})
        self._cache_states = {}

    @property
    def name(self):
        """
        :rtype: str
        """
        raise NotImplementedError()

    @synchronized
    def get_cache_state(self, entity_name, cache_owner):
        """Retrieve cache state of given entity for given owner

        :type entity_name: str
        :type cache_owner: parallels.core.utils.cache.CacheOwner
        :rtype: parallels.core.utils.cache.CacheState
        """
        cache_state_key = (entity_name, cache_owner.cache_owner_name)
        if cache_state_key not in self._cache_states:
            is_valid = False
            if entity_name in self._data and cache_owner.cache_owner_name in self._data[entity_name]:
                is_valid = self._data[entity_name][cache_owner.cache_owner_name]
            cache_state = CacheState(is_valid, self)
            self._cache_states[cache_state_key] = cache_state
        return self._cache_states[cache_state_key]

    @synchronized
    def invalidate_cache_states(self, entity_name):
        """Invalidate cache states of given entity for all owners

        :type entity_name: str
        """
        for (current_entity_name, _), cache_state in self._cache_states.items():
            if current_entity_name == entity_name:
                # suppose that CacheState aggregated into CacheStateController, both classes
                # belongs to the same module, so protected methods should be accessible here
                # noinspection PyProtectedMember
                cache_state._set_valid(False)
        if entity_name in self._data:
            del self._data[entity_name]
        self.flush()

    @synchronized
    def flush(self):
        for (entity_name, cache_owner_name), cache_state in self._cache_states.items():
            if cache_state.is_valid():
                if entity_name not in self._data:
                    self._data[entity_name] = {}
                self._data[entity_name][cache_owner_name] = True
            else:
                # not need to store invalid cache states
                if entity_name not in self._data:
                    continue
                if cache_owner_name in self._data[entity_name]:
                    del self._data[entity_name][cache_owner_name]
                if len(self._data[entity_name]) == 0:
                    del self._data[entity_name]

        self._data_storage.set_data(self.name, self._data)


class CacheState(object):
    def __init__(self, is_valid, cache_state_controller):
        self._is_valid = is_valid
        self._cache_state_controller = cache_state_controller

    def is_valid(self):
        """Check if cache is valid

        :rtype: bool
        """
        return self._is_valid

    def set_valid(self, is_valid=True):
        """Mark cache as valid or invalid according given flag and flush changes

        :type is_valid: bool
        """
        self._set_valid(is_valid)
        self._cache_state_controller.flush()

    def _set_valid(self, is_valid):
        self._is_valid = is_valid


class CacheOwner(object):
    @property
    def cache_owner_name(self):
        raise NotImplementedError()


class CommonCacheStateController(CacheStateController):
    @property
    def name(self):
        return 'common'


class ResellerCacheStateController(CacheStateController):
    @property
    def name(self):
        return 'reseller'


class CustomerCacheStateController(CacheStateController):
    @property
    def name(self):
        return 'customer'


class SubscriptionCacheStateController(CacheStateController):
    @property
    def name(self):
        return 'subscription'


class DomainCacheStateController(CacheStateController):
    @property
    def name(self):
        return 'domain'
