"""
Tasks cached on data store.
"""

from .base import BaseTask


class BaseCachedTask(BaseTask):

    datastore = None
    """
    Data store instance.  Child class **must** set this attribute.
    """

    def is_finished(self):
        current = self.get_taskhash()
        cached = self.get_cached_taskhash()
        return current is not None and \
            cached is not None and \
            current == cached

    def get_paramvalue(self):
        """
        Return a hash-able object which can identify this task.

        Note that this value should not depend on the result
        of the :meth:`run` function.  However, this value should
        provide enough information to get the same result.

        """
        raise NotImplementedError

    def get_taskhash(self):
        if not self.is_parent_cacheable():
            return None
        parent_hashes = self.get_parent_hashes()
        if any(h is None for h in parent_hashes):
            return None
        paramvalue = self.get_paramvalue()
        # TODO: This relies on that `hash` returns the same value for every
        #       run.  Does it hold always?  Find out!
        return hash((paramvalue, tuple(parent_hashes)))

    def get_parent_hashes(self):
        return map(BaseCachedTask.get_cached_taskhash, self.get_parents())

    def is_parent_cacheable(self):
        return all(isinstance(p, BaseCachedTask) for p in self.get_parents())

    def get_taskhashstore(self):
        return self.datastore.get_metastore().get_filestore('taskhash')

    def get_cached_taskhash(self):
        store = self.get_taskhashstore()
        if not store.exists():
            return None
        with store.open() as f:
            return int(f.read())

    def set_cached_taskhash(self):
        taskhash = self.get_taskhash()
        with self.get_taskhashstore().open('w') as f:
            f.write(str(taskhash))

    def post_run(self):
        # TODO: check if task raises an error and do NOT set cache if so.
        self.set_cached_taskhash()
        super(BaseCachedTask, self).post_run()

"""
Tasks cached on data store.
"""

from .base import BaseTask


class BaseCachedTask(BaseTask):

    datastore = None
    """
    Data store instance.  Child class **must** set this attribute.
    """

    def is_finished(self):
        current = self.get_taskhash()
        cached = self.get_cached_taskhash()
        return current is not None and \
            cached is not None and \
            current == cached

    def get_paramvalue(self):
        """
        Return a hash-able object which can identify this task.

        Note that this value should not depend on the result
        of the :meth:`run` function.  However, this value should
        provide enough information to get the same result.

        """
        raise NotImplementedError

    # TODO: Use this value to consider result of parent tasks.
    def get_resultvalue(self):
        """
        Return a hash-able object which represents result of this task.

        Define this method to return an object which can identify the
        result of this task.  For example, if the result differs for
        every run, downstream tasks must be run again when this task
        is run after them.  This method is needed to detect when to
        re-run downstream tasks.

        """
        return None

    def get_taskhash(self):
        if not self.is_parent_cacheable():
            return None
        parent_hashes = self.get_parent_hashes()
        if any(h is None for h in parent_hashes):
            return None
        paramvalue = self.get_paramvalue()
        # TODO: This relies on that `hash` returns the same value for every
        #       run.  Does it hold always?  Find out!
        return hash((paramvalue, tuple(parent_hashes)))

    def get_parent_hashes(self):
        return map(BaseCachedTask.get_cached_taskhash, self.get_parents())

    def is_parent_cacheable(self):
        return all(isinstance(p, BaseCachedTask) for p in self.get_parents())

    def get_taskhashstore(self):
        return self.datastore.get_metastore().get_filestore('taskhash')

    def get_cached_taskhash(self):
        store = self.get_taskhashstore()
        if not store.exists():
            return None
        with store.open() as f:
            return int(f.read())

    def set_cached_taskhash(self):
        taskhash = self.get_taskhash()
        with self.get_taskhashstore().open('w') as f:
            f.write(str(taskhash))

    def post_run(self):
        # TODO: check if task raises an error and do NOT set cache if so.
        self.set_cached_taskhash()
        super(BaseCachedTask, self).post_run()

Define BaseCachedTask.get_resultvalue (not used)