# http://pyrocko.org - GPLv3
#
# The pyrockoeost Developers, 21st Century
# ---|P------/S----------~Lg----------


class Stage(object):
    def __init__(self, f):
        self._f = f
        self._parent = None
        self._cache = {}

    def __call__(self, *x, **kwargs):
        if kwargs.get('nocache', False):
            return self.call_nocache(*x)

        if x not in self._cache:
            if self._parent is not None:
                self._cache[x] = self._f(self._parent(*x[:-1]), *x[-1])
            else:
                self._cache[x] = self._f(*x[-1])

        return self._cache[x]

    def call_nocache(self, *x):
        if self._parent is not None:
            return self._f(self._parent.call_nocache(*x[:-1]), *x[-1])
        else:
            return self._f(*x[-1])

    def clear(self):
        self._cache.clear()


class Chain(object):
    def __init__(self, *stages):
        parent = None
        self.stages = []
        for stage in stages:
            if not isinstance(stage, Stage):
                stage = Stage(stage)

            stage._parent = parent
            parent = stage
            self.stages.append(stage)

    def clear(self):
        for stage in self.stages:
            stage.clear()

    def __call__(self, *x, **kwargs):
        return self.stages[len(x)-1](*x, **kwargs)
