from guppy.etc.Descriptor import property_nondata


class Classifier:
    def __init__(self, mod, name, cli=None, supers=(), depends=(), with_referrers=False):
        self.mod = mod
        self.name = name
        if cli is not None:
            self.cli = cli
        # Set of all super-classifiers (including self).
        # The partial order is defined in Notes Aug 30 2005.
        self.super_classifiers = mod.ImpSet.immnodeset([self])
        if supers:
            for s in supers:
                self.super_classifiers |= s.super_classifiers
        else:
            # The Unity classifier is super of all, but we must add it only
            # if not supers specified; init of ByUnity itself depends on this.
            self.super_classifiers |= [mod.Use.Unity.classifier]

        # The classifiers that self depends on.
        for d in depends:
            if d.with_referrers:
                with_referrers = True
                break

        # True if we need to setup referrers before calling (the) low-level classifier.
        self.with_referrers = with_referrers

        if with_referrers:
            self.call_with_referrers = mod.View.call_with_referrers

    def call_with_referrers(self, x, f):
        # Default is to not use referrers.
        return f(x)

    # This is not redefined by subclass unless they set cli property.
    def _get_cli(self):
        # This may be defined by subclass w/o setting cli property.
        return self.get_cli()

    cli = property_nondata(_get_cli)

    def get_alt(self, kind, alt):
        # Get alternative kind for a kind with self as fam.classifier.
        return self.mod.alt(kind, alt)

    def get_dictof(self, kind):
        name = '%s.dictof' % self.name
        er = self.mod.mker_memoized(
            name,
            lambda:
            self.mod._er_by_(ByDictOwner, self.mod, name, self))

        return er.classifier.dictof(kind)

    def get_kind(self, k):
        # Make an equivalence class from low-level classification
        return self.family(k)

    def get_kindarg(self, kind):
        # Inverse of get_kind
        cla, ka, cmp = kind.get_ckc()
        if cla is not self:
            raise ValueError(
                'get_kindarg: argument with classifier %r expected' % self)
        return ka

    def get_reprname(self):
        return '%s%s' % (self.mod.Use.reprefix, self.name)

    def get_sokind(self, er, *args, **kwds):
        k = er(*args, **kwds)
        return CallableSoKind(er, (k,))

    def get_sokindrepr(self, sokind):
        # Get the representation of a set of kinds
        # from this classifier / eqv. relation.
        return '%s.sokind%s' % (self.get_reprname(),
                                ''.join(['(%s)' % self.get_userkindargrepr(k)
                                         for k in sokind.kinds]))

    def get_tabheader(self, ctx=''):
        # If ctx = 'and', get the table header when used as a part of the 'and' classifier.
        #     It is sometimes a more compact or parenthesised version of the usual tab header.
        return self.get_byname()

    def get_tabrendering(self, cla, ctx=''):
        # If ctx = 'and', get the table rendering when used as a part of the 'and' classifier
        #     sometimes we want to enclose something in parenthesises.
        return cla.brief

    def get_userkind(self, *args, **kwds):
        # Make a kind from user-level arguments
        return self.family(*args, **kwds)

    def get_userkindarg(self, kind):
        return kind.arg

    def get_userkindargrepr(self, kind):
        return repr(self.get_userkindarg(kind))

    def partition(self, iterable):
        items = []
        for k, v in self.partition_cli(iterable):
            k = self.get_kind(k)
            v = self.mod.Use.idset(v, er=self.er)
            items.append((k, v))
        return items

    def partition_cli(self, a):
        ep = self.call_with_referrers(
            a,
            self.cli.epartition)
        return [(k, ep[k]) for k in ep.get_domain()]

    def relimg(self, X):
        p = self.partition_cli(X)
        kinds = [self.get_kind(k) for k, v in p]  # could be more efficient
        return self.mod.Use.union(kinds, maximized=1)

    def select_cli(self, a, b, cmp='=='):
        return self.call_with_referrers(
            a,
            lambda a: self.cli.select(a, b, cmp))

    def select_ids(self, X, k, alt=None):
        r = self.mod.Use.idset(self.select_cli(X.nodes, k, alt))
        return r


class SoKind(object):
    def __init__(self, er, kinds):
        self.er = er
        self.classifier = er.classifier
        self.kinds = kinds
        self.clikinds = self.classifier.mod.ImpSet.immnodeset(
            [self.classifier.get_kindarg(kind) for kind in kinds])

    def __eq__(self, other):
        if not isinstance(other, SoKind):
            return False
        if self.classifier != other.classifier:
            return False
        a = self.classifier.mod.Use.union(self.kinds)
        b = self.classifier.mod.Use.union(other.kinds)
        return a == b

    def __hash__(self):
        return hash(repr(self))

    def __repr__(self):
        return self.classifier.get_sokindrepr(self)

    def _get_refdby(self):
        return self.er.refdby(self)

    refdby = property(_get_refdby)


class CallableSoKind(SoKind):
    def __call__(self, *args, **kwds):
        k = self.er(*args, **kwds)
        return self.__class__(self.er, self.kinds + (k,))


class SoNoKind(SoKind):
    def __repr__(self):
        return '%s%s' % (self.classifier.mod.Use.reprefix, 'sonokind')


class QuickSoKind(SoKind):
    # Quicker to make than SoKind,
    # when clikinds is available but not kinds.
    __slots__ = 'classifier', 'clikinds'

    def __init__(self, classifier, clikinds):
        self.classifier = classifier
        self.clikinds = clikinds

    def _get_er(self):
        return self.classifier.er

    er = property(_get_er)

    def _get_kinds(self):
        return tuple([self.classifier.get_kind(k) for k in self.clikinds])

    kinds = property(_get_kinds)


class IdentityFamily:
    # Holds a single object node
    def __init__(self, mod, classifier):
        self.defrefining(mod.Use.Anything)
        self.classifier = classifier

    def _ge_ATOM(self, a, b):
        # b is known to not be Nothing since its c_le doesn't call back
        if self is b.fam:
            return a.arg is b.arg
        return b.fam.supercl is not None and b.fam.supercl <= a

    def _le_ATOM(self, a, b):
        # b is known to not be Nothing since its c_ge doesn't call back
        if self is b.fam:
            return a.arg is b.arg
        return self.supercl is not None and self.supercl <= b

    def c_contains(self, a, b):
        return b is a.arg

    def _and_ID(self, a, b):
        # Just a possible optimization
        return self.mod.Use.idset(b.nodes & [a.arg])

    def c_get_brief(self, a):
        return '<id %s>' % hex(id(a.arg))

    def c_repr(self, a):
        return '%s(%s)' % (self.classifier.get_reprname(), self.classifier.get_userkindargrepr(a))


class ByIdentity(Classifier):
    def __init__(self, mod, name):
        Classifier.__init__(self, mod, name, mod.hv.cli_id())
        self.family = mod.fam_mixin_argatom(IdentityFamily, self)
        # self.super_classifiers = mod.Use.Anything # Replace whatever Classifer had set it to

    def get_byname(self):
        return 'object identity'

    def get_tabheader(self, ctx=''):
        return 'Object Identity'

    def get_userkind(self, address):
        return self.get_kind(self.mod.View.obj_at(address))

    def get_userkindarg(self, kind):
        return id(kind.arg)

    def get_userkindargrepr(self, kind):
        return hex(self.get_userkindarg(kind))


class ByIdentitySet(Classifier):
    # Classification is, conceptually, a singleton immnodeset of each object

    # What this is used to is:
    # to be able to use an iso() set as a kind
    # combined with other classifiers eg in dictof, biper

    # The ckc returned from an iso is then
    # this classifier, nodes of iso, '<='

    # The cmp indicates subset
    # select thus selects every object for which it singleton is a subset of the set given
    # which is optimized to select the object that are members of that set
    # and may be optimized at higher levels to invoke the low-level set intersection

    def __init__(self, mod, name):
        Classifier.__init__(self, mod, name, mod.hv.cli_idset())
        self.family = mod.Use.idset
        # self.super_classifiers = mod.Use.Anything # Replace whatever Classifer had set it to

    def get_byname(self):
        return 'by identity set'

    def get_userkind(self, node):
        return self.family(self.mod.ImpSet.immnodeset([node]))

    def relimg(self, X):
        p = self.partition_cli(X)
        k = self.mod.ImpSet.immnodeset_union([k for k, v in p])
        return self.family(k)


class PyObjectFamily:
    def __init__(self, mod, classifier):
        self.classifier = classifier

    def c_contains(self, a, b):
        return True

    def c_get_idpart_header(self, a):
        return 'Kind: Name/Value/Address'

    def c_get_idpart_label(self, a):
        return ''

    def c_get_idpart_render(self, a):
        def render(x):
            x = self.mod.Use.iso(x)
            r = x.brief.lstrip('<1 ').rstrip('>')
            return r
        return render

    def c_get_brief(self, a):
        return '<Anything>'

    def c_repr(self, a):
        return '%s%s' % (self.mod.Use.reprefix, 'Anything')

    def _and_ID(self, a, b):
        # Optimization shortcut
        # shcould be made in classifer.select
        return b


class ByUnity(Classifier):
    """byunity
Classify by <unity>.
The classification returned for every object is <Anything>."""

    def __init__(self, mod, name):
        Classifier.__init__(self, mod, name, mod.hv.cli_none(),
                            # Must make it avoid referring to Unity !
                            supers=[self]
                            )
        self.family = mod.fam_mixin_argatom(PyObjectFamily, self)

    def get_byname(self):
        return 'unity'

    def get_tabheader(self, ctx=''):
        return '<unclassified>'

    def get_userkind(self, *args):
        return self.mod.Use.Anything

    def get_userkindarg(self, kind):
        return None


class IndiSizeFamily:
    def __init__(self, mod, classifier):
        self.defrefining(mod.Use.Anything)
        self.classifier = classifier

    def __call__(self, a):
        a = int(a)
        return self.mod.AtomFamily.__call__(self, a)

    def c_alt(self, a, alt):
        return self.classifier.get_alt(a, alt)

    def c_contains(self, a, b):
        return a.arg == self.classifier.cli.classify(b)

    def c_get_render(self, a):
        return self.mod.summary_str(a.arg)

    def c_get_brief(self, a):
        return '<size = %d>' % a.arg

    def c_get_brief_alt(self, a, alt):
        return '<size %s %d>' % (alt, a.arg)

    def c_repr(self, a):
        return '%s(%s)' % (self.classifier.get_reprname(), a.arg)


class ByIndiSize(Classifier):
    """byindisize
Classify by <individual size>.
The classification will be individual memory size of the object."""

    def __init__(self, mod, name):
        Classifier.__init__(self, mod, name)
        self.family = mod.fam_mixin_argatom(IndiSizeFamily, self)

    def get_byname(self):
        return 'individual size'

    def get_cli(self):
        return self.mod.hv.cli_indisize({})

    def get_tabheader(self, ctx=''):
        return 'Individual Size'

    def get_tabrendering(self, cla, ctx=''):
        if ctx:
            return '%d' % cla.arg
        else:
            return '%9d' % cla.arg


class TypeFamily:
    def __init__(self, mod, classifier):
        self.defrefining(mod.Use.Anything)
        self.classifier = classifier
        self.range = mod.fam_Family(self)

    def __call__(self, a):
        if not isinstance(a, type):
            raise TypeError("Argument should be a type.")
        return self.Set(self, a)

    def c_alt(self, a, alt):
        return self.classifier.get_alt(a, alt)

    def c_contains(self, a, b):
        return type(b) is a.arg

    def c_get_render(self, a):
        return self.mod.summary_str(a.arg)

    def c_get_brief(self, a):
        return self.mod.summary_str(type(a.arg))(a.arg)

    def c_get_brief_alt(self, a, alt):
        x = {
            '<': 'strict subtype',
            '<=': 'subtype',
            '>=': 'supertype',
            '>': 'strict supertype'
        }[alt]
        return '<%s of %s>' % (x, self.c_get_brief(a))

    def c_repr(self, a):
        return self.classifier.get_repr(a)


class ByType(Classifier):
    """bytype
Classify by <type>.
The classification will be the type of the object."""

    def __init__(self, mod, name):
        Classifier.__init__(self, mod, name, mod.hv.cli_type())
        self.family = mod.fam_mixin_argatom(TypeFamily, self)

    def get_attr_for_er(self, name):
        try:
            ty = getattr(self.mod.types, name+'Type')
        except AttributeError:
            ty = getattr(self.mod.builtins, name.lower())
        return self.get_userkind(ty)

    def get_byname(self):
        return 'type'

    def get_repr(self, kind):
        t = kind.arg
        rn = self.get_reprname()
        if t in self.mod.invtypemod:
            return '%s.%s' % (rn, self.mod.invtypemod[t])
        else:
            return '%s(%r)' % (rn, self.get_userkindarg(kind))

    def get_tabheader(self, ctx=''):
        return 'Type'

    def get_userkind(self, kind):
        kind = self.mod.tc_adapt(kind)
        return self.family(kind)

    def get_userkindarg(self, kind):
        # A representation that is a valid userkind arg.
        return self.mod.Use.tc_repr(kind.arg)


class OwnedDictFamily:
    def __init__(self, mod):
        self.defrefidis(mod.Use.Type(dict))

    def _get_ownerkind(self, a):
        return a.arg

    def c_alt(self, a, alt):
        return self(a.arg.alt(alt))

    def c_get_render(self, a):
        ka = self._get_ownerkind(a)
        if ka is self.mod.Use.Nothing:
            return self.mod.Use.Type.Dict.get_render()
        else:
            ownrender = ka.get_render()

            def render(x):
                ret = ownrender(self.mod.Use.iso(x).owners.theone)
                if '.' in ret:
                    ret = '..'+ret.split('.')[-1]
                return ret
            return render

        if ka == self.mod.fam_Type(self.types.ModuleType):
            modrender = self.mod.Use.Type.Module.get_render()

            def render(x):
                return modrender(self.mod.Use.iso(x).owners.theone)
            return render
        else:
            return self.mod.Use.Type.Dict.get_render()

    def c_get_brief(self, a):
        ka = self._get_ownerkind(a)
        if ka is self.mod.Use.Nothing:
            return 'dict (no owner)'
        else:
            return 'dict of ' + ka.brief

    def c_get_ckc(self, a):
        cla, k, cmp = a.arg.get_ckc()
        if cmp != '==':
            cla, k, cmp = a.arg.biper(0).get_ckc()

        docla = cla.er.dictof.classifier
        if a.arg is self.mod.Use.Nothing:
            k = docla.notownedtag
        return docla, k, cmp

    def c_get_str_for(self, a, b):
        return self.c_get_brief(a)

    def c_get_idpart_render(self, a):
        ka = self._get_ownerkind(a)
        if ka is not self.mod.Use.Nothing:
            owner_render = ka.fam.c_get_idpart_render(ka)

            def render(x):
                return owner_render(self.mod.Use.iso(x).owners.theone)
            return render
        else:
            b = self.mod._parent.Spec.Type.Dict
            return b.fam.c_get_idpart_render(b)

    def c_get_idpart_header(self, a):
        ka = self._get_ownerkind(a)
        if ka is self.mod.Use.Nothing:
            return 'Address*Length'
        else:
            return 'Owner ' + ka.fam.c_get_idpart_header(ka)

    def c_repr(self, a):
        ka = self._get_ownerkind(a)
        ra = repr(ka)
        if ra.startswith('~'):
            ra = '(%s)' % ra
        return '%s.dictof' % ra


class ByDictOwner(Classifier):
    def __init__(self, mod, name, ownerclassifier):
        Classifier.__init__(self, mod, name, depends=[ownerclassifier])
        self.ownerclassifier = ownerclassifier
        self.hv = mod.View.hv
        self.ownership = mod.View.dict_ownership
        self.family = mod.dictof

        self.notdict = mod.notdict
        self.dictofnothing = mod.dictofnothing

        # Hashable unique tags
        # Using sets methods since I dont want our hiding tag here!
        # Confuses heapg. Note feb 3 2006
        self.notdicttag = mod.ImpSet.immnodeset([[]])
        self.notownedtag = mod.ImpSet.immnodeset([[]])

    def get_byname(self):
        return '[dict of] %s' % self.ownerclassifier.get_byname()

    def get_cli(self):
        cli = self.hv.cli_dictof(self.ownership, self.ownerclassifier.cli, self.notdicttag,
                                 self.notownedtag)
        return cli

    def get_kind(self, k):
        if k is self.notdicttag:
            return self.notdict
        elif k is self.notownedtag:
            return self.dictofnothing
        else:
            return self.family(self.ownerclassifier.get_kind(k))

    def get_kindarg(self, kind):
        if kind is self.notdict:
            return self.notdicttag
        elif kind is self.dictofnothing:
            return self.notownedtag
        else:
            return self.ownerclassifier.get_kindarg(kind.arg)

    def get_tabheader(self, ctx=''):
        return 'Dict of %s' % self.ownerclassifier.get_tabheader(ctx)

    def get_tabrendering(self, kind, ctx=''):
        if kind is self.notdict:
            r = kind.brief
        elif kind is self.dictofnothing:
            r = 'dict (no owner)'
        else:
            r = 'dict of ' + \
                self.ownerclassifier.get_tabrendering(kind.arg, ctx)
        return r

    def get_userkind(self, k):
        if k is None:
            return self.notdict
        elif k is self.mod.Use.Nothing:
            return self.dictofnothing
        else:
            return self.family(k)

    def get_userkindarg(self, kind):
        if kind is self.notdict:
            return None
        elif kind is self.dictofnothing:
            return self.mod.Use.Nothing
        else:
            return kind.arg

    def owners(self, X):
        p = self.partition_cli(X.nodes)
        ns = self.mod.ImpSet.mutnodeset()
        drg = self.ownership
        for k in X.nodes:
            t = drg[k]
            if not t:
                self.mod.hv.update_dictowners(drg)
                t = drg[k]
            if t:
                v = t[0]
                if v is not None:
                    ns.add(v)
        return self.mod.Use.idset(ns)


class ByClassOrDictOwner(Classifier):
    """byclodo
Classify by <type or dict owner>.
The classification is performed as follows:
1.      If the object is not a dictionary,
        the classification will be the type of the object.
2.      The object is a dictionary. The referrers of the
        object are searched to find one that 'owns' the
        dictionary. That is, typically, that the dict is
        the __dict__ attribute of the owner. If no such
        owner is found, the type 'dict' will be the
        classification. If an owner is found, a special
        object that indicates the classification of the owner
        will be returned. The classification of the owner
        will be done by class. (As byclass.)"""

    def __init__(self, mod, name):
        a = mod.Type
        d = a.dictof
        ad = (a & d).classifier
        sup = a.classifier
        Classifier.__init__(self, mod, name, cli=None,
                            supers=[sup], depends=[ad])
        self.sup = sup
        self.a = a.classifier
        self.d = d.classifier
        self.ad = ad

    def get_byname(self):
        return '[dict of] class'

    def get_cli(self):
        return self.ad.cli

    def get_kind(self, xxx_todo_changeme):
        (ka, kd) = xxx_todo_changeme
        if kd is self.d.notdicttag:
            return self.a.get_kind(ka)
        else:
            return self.d.get_kind(kd)

    def get_kindarg(self, kind):
        if kind.fam is self.d.family:
            ka = dict
            kd = self.d.get_kindarg(kind)
        else:
            ka = self.a.get_kindarg(kind)
            kd = self.d.notdicttag
        return (ka, kd)

    def get_tabheader(self, ctx=''):
        return 'Kind (class / dict of class)'

    def get_userkind(self, kind=None, dictof=None):
        try:
            if kind is None and dictof is not None:
                if dictof == ():
                    do = self.mod.UniSet.Nothing
                else:
                    do = self.sup.get_userkind(dictof)
                return self.d.get_userkind(do)
            elif kind is not None and dictof is None:
                kind = self.mod.tc_adapt(kind)
                if kind is dict:
                    raise TypeError(
                        'dict is not an equivalence class of Clodo, use dictof=() etc')
                return self.sup.get_kind(kind)
            else:
                raise TypeError
        except TypeError:
            raise TypeError("""\
Argument should be either
    <type except dict>
    dictof=<type>
    dictof=()""")

    def get_userkindargrepr(self, kind):
        if kind.fam is self.d.family:
            if kind.arg is self.mod.UniSet.Nothing:
                d = '()'
            else:
                d = self.d.ownerclassifier.get_userkindargrepr(kind.arg)
            return 'dictof=%s' % d
        else:
            return kind.fam.classifier.get_userkindargrepr(kind)

    def owners(self, X):
        return self.d.owners(X)


class RetClaSetFamily:
    def __init__(self, mod, classifier):
        self.defrefining(mod.Use.Anything)
        self.classifier = classifier

    def c_alt(self, a, alt):
        return a.arg.classifier.er.refdby.classifier.get_alt(a, alt)

        return self.classifier.get_alt(a, alt)

    def _get_arg_brief(self, a):
        return a.arg.er.refdby.classifier.get_tabrendering(a, False)

    def c_get_brief(self, a):
        return '<referred by: %s>' % self._get_arg_brief(a)

    def c_get_brief_alt(self, a, alt):
        x = {
            '<': 'by less than',
            '<=': 'by at most',
            '>=': 'by at least',
            '>': 'by more than',
        }[alt]
        return '<referred %s: %s>' % (x, self._get_arg_brief(a))

    def c_get_ckc(self, a):
        return self.classifier, a.arg.clikinds, '=='

    def c_repr(self, a):
        return '%r.refdby' % a.arg

    # Public

    def sokind(self, sok):
        if not isinstance(sok, SoKind):
            raise TypeError('SoKind expected')

        er = sok.classifier.er.refdby
        kinds = (self(sok),)
        return CallableSoKind(er, kinds)


class ByRetClaSet(Classifier):
    def __init__(self, mod, name, rg, referrer_classifier, doc):
        Classifier.__init__(self, mod, name, with_referrers=True)
        self.rg = rg
        self.referrer_classifier = referrer_classifier
        self.family = self.mod.fam_mixin_argatom(RetClaSetFamily, self)
        self.__doc__ = doc

    def get_byname(self):
        return 'referrer kinds'

    def get_cli(self):
        memo = {}
        return self.mod.hv.cli_rcs(self.rg, self.referrer_classifier.cli, memo)

    def get_inverted_refkind(self, k):
        if k.fam.opname == 'OR':
            ks = k.arg
        elif k is self.mod.Use.Nothing:
            ks = ()
        else:
            ks = (k,)
        rks = []
        for k in ks:
            rks.append(self.referrer_classifier.get_kindarg(k))
        return self.mod.ImpSet.immnodeset(rks)

    def get_kind(self, k):
        if k:
            return self.family(QuickSoKind(self.referrer_classifier, k))
        else:
            return self.mod.refdbynothing

    def get_tabheader(self, ctx=''):
        th = 'Referrers by %s' % self.referrer_classifier.get_tabheader(ctx)
        if ctx:
            th = '{%s}' % th
        return th

    def get_tabrendering(self, cla, ctx):
        rs = [self.referrer_classifier.get_tabrendering(
            x, ctx) for x in cla.arg.kinds]
        rs.sort()
        r = ', '.join(rs)
        if ctx:
            r = '{%s}' % r
        elif not r:
            r = '<Nothing>'
        return r

    def get_userkind(self, *args):
        firstsok = None
        clikinds = []
        for arg in args:
            if isinstance(arg, SoKind):
                if not arg.classifier is self.referrer_classifier:
                    raise ValueError('Expected a SoKind with the %r classifier, argument had %r.' % (
                        self.referrer_classifier.name,
                        arg.classifier.name))
                clikinds.extend(arg.clikinds)
                if firstsok is None:
                    firstsok = arg

            else:
                # Assume we got a single kind
                # get_kindarg takes care of classifier error checking
                clikinds.append(self.referrer_classifier.get_kindarg(arg))

        if len(args) > 1 or firstsok is None:
            sok = QuickSoKind(self.referrer_classifier,
                              self.mod.ImpSet.immnodeset(clikinds))
        else:
            sok = firstsok

        return self.family(sok)


class InRelFamily:
    def __init__(self, mod, classifier):
        self.classifier = classifier
        self.defrefining(mod.Use.Anything)

    def _eq_args(self, a, b):
        # They are sequences (immnodesets) of relations.
        # I have not memoized them since I was afraid they would last too long
        # and I thought it not be worthwhile and hope this comparison is not done too often.
        # So I will compare them as equality based sets.
        a = dict([(x, ()) for x in a])
        b = dict([(x, ()) for x in b])
        return a == b

    def _ge_ATOM(self, a, b):
        # b is known to not be Nothing since its c_le doesn't call back
        if self is b.fam:
            return self._eq_args(a.arg, b.arg)
        return b.fam.supercl is not None and b.fam.supercl <= a

    def _le_ATOM(self, a, b):
        # b is known to not be Nothing since its c_ge doesn't call back
        if self is b.fam:
            return self._eq_args(a.arg, b.arg)
        return self.supercl is not None and self.supercl <= b

    def c_alt(self, a, alt):
        return self.classifier.get_alt(a, alt)

    def c_get_brief(self, a):
        return '<via %s>' % self.classifier.get_tabrendering(a, None)

    def c_repr(self, a):
        return '%s(%s)' % (self.classifier.get_reprname(),
                           self.classifier.get_userkindargrepr(a))


class ByInRel(Classifier):
    def __init__(self, mod, name, rg):
        Classifier.__init__(self, mod, name, with_referrers=True)
        self.rg = rg
        self.family = mod.fam_mixin_argatom(InRelFamily, self)

    def _rel2str(self, r):
        P = self.mod._parent.Path
        t = P.rel_table
        x = t[r.kind](r.relator)
        s = x.stra('_', safe=False)
        if s.startswith('_'):
            s = s[1:]
        return s

    def _str2rel(self, s):
        # Parse a string as generated by rel2str,
        # to recreate the relation object.
        P = self.mod._parent.Path
        orgs = s

        def mkrel(R, *args):
            return self.mod.View.heapyc.Relation(R.code, *args)
        if s.startswith('list(_)['):
            s = s[8:].rstrip(']')
            r = int(s)
            rel = mkrel(P.R_INSET, r)
            return rel
        if s.startswith('_'):
            s = s[1:]
        if s.startswith('['):
            s = s[1:].rstrip(']')
            loc = {'hp': self.mod.Use}
            r = eval(s, loc)
            rel = mkrel(P.R_INDEXVAL, r)
        elif s.startswith('.'):
            s = s[1:]
            if s.replace('_', 'x').isalnum():
                rel = mkrel(P.R_ATTRIBUTE, s)
            elif s.startswith('f_locals['):
                s = s[9:].rstrip(']')
                r = eval(s, {})
                rel = mkrel(P.R_LOCAL_VAR, r)
            elif s.startswith('f_locals ['):
                s = s[10:].rstrip(']')
                r = eval(s, {})
                rel = mkrel(P.R_CELL, r)
            elif s.startswith('keys()['):
                s = s[7:].rstrip(']')
                r = int(s)
                rel = mkrel(P.R_INDEXKEY, r)
            elif s.startswith('__dict__.keys()['):
                s = s[16:].rstrip(']')
                r = int(s)
                rel = mkrel(P.R_HASATTR, r)
            else:
                raise SyntaxError('Cant make a relation of %r.' % orgs)
        elif s.startswith('->'):
            s = s[2:]
            if s.startswith('f_valuestack['):
                s = s[13:].rstrip(']')
                r = int(s)
                rel = mkrel(P.R_STACK, r)
            else:
                rel = mkrel(P.R_INTERATTR, s)
        else:
            raise SyntaxError('Cant make a relation of %r.' % orgs)
        return rel

    def get_byname(self):
        return 'referred via'

    def get_cli(self):
        memokind = {}
        memorel = {}
        return self.mod.hv.cli_inrel(self.rg, memokind, memorel)

    def get_kind(self, k):
        return self.family(k)

    def get_tabheader(self, ctx=''):
        if not ctx:
            return "Referred Via:"
        else:
            r = 'Referred Via'
            if ctx == 'and':
                r = '{%s}' % r
            return r

    def get_tabrendering(self, kind, ctx=''):
        r = self.get_userkindargrepr(kind)
        if ctx == 'and':
            r = '{%s}' % r
        return r

    def get_userkind(self, *args):
        return self.get_kind([self._str2rel(x) for x in args])

    def get_userkindargrepr(self, kind):
        a = [repr(self._rel2str(x)) for x in kind.arg]
        a.sort()
        return ', '.join(a)


class AndClassifier(Classifier):
    def __init__(self, mod, name, args):  # At least 2 args
        if name is None:
            name = '(%s)' % ' & '.join([x.name for x in args])
        Classifier.__init__(self, mod, name, cli=None,
                            supers=args, depends=args)
        self.args = args

    def get_byname(self):
        return '<%s>' % ' & '.join([x.get_byname() for x in self.args])

    def get_cli(self):
        memo = {}
        return self.mod.hv.cli_and(tuple([x.cli for x in self.args]), memo)

    def get_kind(self, k):
        ks = []
        for ki, ci in zip(k, self.args):
            ks.append(ci.get_kind(ki))
        return self.mod.UniSet.fam_And._cons(ks)

    def get_reprname(self):
        return '(%s)' % ' & '.join([x.get_reprname() for x in self.args])

    def get_tabheader(self, ctx=''):
        r = '%s' % ' & '.join([x.get_tabheader('and') for x in self.args])
        if ctx == 'and':
            r = '(%s)' % r
        return r

    def get_tabrendering(self, cla, ctx=''):
        ss = []
        for a, cl in zip(cla.arg, self.args):
            s = cl.get_tabrendering(a, 'and')
            ss.append(s)
        r = ' & '.join(ss)
        if ctx == 'and':
            r = '(%s)' % r
        return r


class ModuleFamily:
    def __init__(self, mod, classifier):
        self.defrefining(mod.Use.Anything)
        self.classifier = classifier
        self.range = mod.fam_Family(self)

    def c_contains(self, a, b):
        return b is a.arg

    def c_get_render(self, a):
        return self.mod.summary_str(a.arg)

    def c_get_brief(self, a):
        return self.mod.summary_str(type(a.arg))(a.arg)

    def c_repr(self, a):
        return '%s(%s)' % (self.classifier.get_reprname(),
                           self.classifier.get_userkindargrepr(a))


class ByModule(Classifier):
    def __init__(self, mod, name):
        def classify(x):
            self.nc += 1
            return x

        cli = mod.hv.cli_user_defined(mod.Use.Type.classifier.cli,
                                      mod.Use.Type.Module.arg,
                                      classify,
                                      None
                                      )
        Classifier.__init__(self, mod, name, cli)
        self.not_module = ~mod.Use.Type.Module
        self.nc = 0
        self.family = mod.fam_mixin_argatom(ModuleFamily, self)
        self.ModuleType = mod.types.ModuleType

    def get_byname(self):
        return 'module'

    def get_kind(self, k):
        if k is None:
            return self.not_module
        else:
            return self.family(k)

    def get_kindarg(self, kind):
        if kind is self.not_module:
            return None
        else:
            assert kind.fam is self.family
            return kind.arg

    def get_tabheader(self, ctx=''):
        return 'Module'

    def get_userkind(self, name=None, at=None):
        if name is None and at is None:
            return self.not_module
        if at is None:
            try:
                m = self.mod.View.target.sys.modules[name]
            except KeyError:
                raise ValueError(
                    'No module %r in View.target.sys.modules.' % name)
        else:
            m = self.mod.View.obj_at(at)
        if not isinstance(m, self.ModuleType):
            raise TypeError(
                'The specified object is not of module type, but %r.' % type(m))
        if name is not None and m.__name__ != name:
            raise ValueError(
                'The specified module has not name %r but %r.' % (name, m.__name__))
        return self.family(m)

    def get_userkindargrepr(self, kind):
        if kind is self.not_module:
            return ''
        else:
            m = kind.arg
            name = m.__name__
            s = '%r' % name
            if self.mod._root.sys.modules.get(name) is not m:
                s += ', at=%s' % hex(id(m))
            return s


class AltFamily:
    def __init__(self, mod, altcode):
        if altcode not in ('<', '<=', '==', '!=', '>', '>='):
            raise ValueError('No such comparison symbol: %r' % altcode)
        self.altcode = altcode
        self.disjoints -= [self]

    def c_get_brief(self, a):
        return a.arg.fam.c_get_brief_alt(a.arg, self.altcode)

    def c_get_ckc(self, a):
        ckc = list(a.arg.get_ckc())
        if ckc[-1] == '==':
            ckc[-1] = self.altcode
        else:
            raise ValueError(
                'Can not make alternative kind, non-equality comparison on underlying kind.')
        return tuple(ckc)

    def c_repr(self, a):
        return '%s.alt(%r)' % (repr(a.arg), self.altcode)


class FindexFamily:
    def __init__(self, mod, classifier):
        self.defrefining(mod.Use.Anything)
        self.classifier = classifier
        self.range = mod.fam_Family(self)

    def c_get_brief(self, a):
        if not 0 <= a.arg < len(self.classifier.kinds):
            return '<None>'
        else:
            return '%s / %d' % (self.classifier.kinds[a.arg].brief, a.arg)

    def c_repr(self, a):
        return '%s(%d)' % (self.classifier.get_reprname(), a.arg)


class ByFindex(Classifier):
    def __init__(self, mod, name, kinds):
        self.alts = [k.fam.c_get_ckc(k) for k in kinds]
        depends = [ckc[0] for ckc in self.alts]
        Classifier.__init__(self, mod, name, depends=depends)
        self.kinds = kinds
        self.family = mod.fam_mixin_argatom(FindexFamily, self)

    def get_cli(self):
        alts = tuple([(cla.cli, k, cmp) for (cla, k, cmp) in self.alts])
        memo = {}
        cli = self.mod.hv.cli_findex(alts, memo)
        return cli

    def get_byname(self):
        return 'index of first matching kind of %s' % (self.kinds,)

    def get_tabheader(self, ctx=''):
        return 'First Matching Kind / Index'


class ProdFamily:
    def __init__(self, mod, classifier):
        self.defrefining(mod.Use.Anything)
        self.classifier = classifier
        self.range = mod.fam_Family(self)

    def c_alt(self, a, alt):
        return self.classifier.get_alt(a, alt)

    def c_get_brief(self, a):
        if a.arg is None:
            return 'unknown'
        else:
            return '%s:%d' % a.arg

    def c_repr(self, a):
        return '%s(%s)' % (self.classifier.get_reprname(),
                           self.classifier.get_userkindargrepr(a))


class ByProd(Classifier):
    def __init__(self, mod, name):
        Classifier.__init__(self, mod, name)
        self.family = mod.fam_mixin_argatom(ProdFamily, self)

    def get_byname(self):
        return 'producer'

    def get_cli(self):
        self.mod.Use._check_tracemalloc()
        memo = {}
        return self.mod.hv.cli_prod(memo)

    def get_tabheader(self, ctx=''):
        return 'Producer (line of allocation)'

    def get_userkind(self, *args, **kwds):
        if args and kwds:
            raise TypeError(
                '{}() takes either positional or keyword arguments'.format(
                    self.get_reprname()))
        if kwds:
            if set(kwds.keys()) != {'filename', 'lineno'}:
                raise TypeError(
                    '{}() keyword arguments must be '
                    '"filename" and "lineno"'.format(
                        self.get_reprname()))
            return self.family((kwds['filename'], kwds['lineno']))

        if len(args) == 0:
            return self.family(None)
        elif len(args) == 1:
            arg, = args
            if isinstance(arg, str):
                # filename
                return self.family((arg, None)).alt('<')

            filename = self.mod.inspect.getsourcefile(arg)
            lines, lnum = self.mod.inspect.getsourcelines(arg)
            lines, lnum = len(lines), max(lnum, 1)

            return (
                self.family((filename, None)).alt('<') &
                self.family((None, lnum)).alt('>=') &
                self.family((None, lnum + lines)).alt('<'))
        elif len(args) == 2:
            filename, lineno = args
            if filename is not None and not isinstance(filename, str):
                raise TypeError(
                    '{}() argument 1 must be string or None'.format(
                        self.get_reprname()))
            if lineno is not None and not isinstance(lineno, int):
                raise TypeError(
                    '{}() argument 2 must be integer or None'.format(
                        self.get_reprname()))
            return self.family((filename, lineno))
        else:
            raise TypeError(
                '{}() takes from 0 to 2 positional arguments '
                'but {} were given'.format(
                    self.get_reprname(), len(args)))

    def get_userkindargrepr(self, kind):
        if kind.arg is None:
            return ''
        return '%r, %r' % kind.arg


class _GLUECLAMP_:
    _imports_ = (
        '_parent:ImpSet',
        '_parent:View',
        '_parent.View:hv',
        '_parent:UniSet',
        '_parent.UniSet:fam_mixin_argatom',
        '_parent:Use',
        '_root.guppy.etc.etc:str2int',
        '_root:inspect',
        '_root:re',
        '_root:types',
        '_root:builtins',
    )

    def _er_by_(self, constructor, *args, **kwds):
        return self.UniSet.fam_EquivalenceRelation(constructor, *args, **kwds)

    # Exported equivalence relations

    def _get_Clodo(self):
        return self._er_by_(ByClassOrDictOwner, self, name='Clodo')

    def _get_Id(self):
        return self._er_by_(ByIdentity, self, name='Id')

    def _get_Idset(self):
        return self._er_by_(ByIdentitySet, self, name='Idset')

    def _get_Module(self):
        return self._er_by_(ByModule, self, name='Module')

    def _get_Prod(self):
        return self._er_by_(ByProd, self, name='Prod')

    def _get_Unity(self):
        return self._er_by_(ByUnity, self, name='Unity')

    def _get_Rcs(self):
        return self.mker_refdby(self.Clodo)

    def mker_and(self, ers):
        if len(ers) == 0:
            return self.Unity
        classifiers = [er.classifier for er in ers]
        name = None
        return self.UniSet.fam_EquivalenceRelation(AndClassifier, self, name, classifiers)

    def mker_dictof(self, er, name=None):
        if name is None:
            name = '%s.dictof' % er.classifier.name
        return self.mker_memoized(
            name,
            lambda:
            self._er_by_(ByDictOwner, self, name, er.classifier))

    def _get_memo_er(self):
        return {}

    def mker_memoized(self, name, f):
        v = self.memo_er.get(name)
        if v is None:
            self.memo_er[name] = v = f()
        return v

    def mker_refdby(self, er, name=None):
        if name is None:
            name = '%s.refdby' % er.classifier.name
        return self.mker_memoized(
            name,
            lambda:
            self._er_by_(
                ByRetClaSet,
                self,
                name,
                self.View.rg,
                er.classifier,
                """%s
Classify by <%s> of referrers.
This classifier uses the %r classifier to classify the
referrers of the object. The classifications of the referrers
are collected in a set. This set becomes the classification of
the object.
""" % (name, er.classifier.get_byname(), er.classifier.name)))

    def _get_Size(self):
        return self._er_by_(ByIndiSize, self, 'Size')

    def _get_Type(self):
        return self._er_by_(ByType, self, 'Type')

    def _get_Via(self):
        View = self.View
        return self._er_by_(
            ByInRel,
            self,
            'Via',
            View.rg)

    def tc_adapt(self, k):
        # Adapt to a type.
        # Accepts a type object, or a string representation
        # (at least as) by tc_repr.

        if isinstance(k, type):
            return k
        if not isinstance(k, str):
            raise TypeError('type, class or basestring expected')

        err = ("String argument to tc_adapt should be of form\n"
               "'<class MODULE.NAME at 0xADDR>' or\n"
               "'<type MODULE.NAME at 0xADDR>' or\n"
               "'<at 0xADDR>'. I got: %r" % k)

        s = k
        if not (s.startswith('<') and s.endswith('>')):
            raise ValueError(err)
        s = s.lstrip('<').rstrip('>')
        s = s.split(' ')
        if len(s) < 2:
            raise ValueError(err)
        t = s[0]
        addr = self.str2int(s[-1])
        kind = self.View.obj_at(addr)
        if t == 'at':
            if len(s) != 2:
                raise ValueError(err)
            ty = None
        else:
            if len(s) != 4:
                raise ValueError(err)
            if t not in ('type', 'class'):
                raise ValueError(err)
            try:
                ty = getattr(self.types, t.capitalize()+'Type')
            except AttributeError:
                ty = getattr(self.builtins, t.lower())
            if not isinstance(kind, ty):
                raise TypeError('%s object expected' % t)
            if not s[2] == 'at':
                raise ValueError(err)
            names = s[1].split('.')
            if len(names) < 2:
                raise ValueError(err)
            modulename = '.'.join(names[:-1])
            tcname = names[-1]
            if kind.__module__ != modulename:
                raise ValueError(
                    'The %s %r has wrong __module__, expected %r.' % (t, kind, modulename))
            if kind.__name__ != tcname:
                raise ValueError(
                    'The %s %r has wrong __name__, expected %r.' % (t, kind, tcname))

        return kind

    def tc_repr(self, k):
        # Represent a type object as a string,
        # so that it can converted back via tc_adapt,
        # as long as it still exists in the heap.
        # There is no absolute guarantee that it will always become the same object,
        # but I hope it will work well enough in practice.
        # See also Notes Sep 7 2005.

        if isinstance(k, type):
            t = 'type'
        else:
            raise TypeError('type expected')
        return '<%s %s.%s at %s>' % (t, k.__module__, k.__name__, hex(id(k)))

    # Convenience interfaces

    def _get_alt(self):
        altmemo = {
            '==': lambda k: k,
            '!=': lambda k: ~k,
        }

        def alt(kind, cmp):
            a = altmemo.get(cmp)
            if a is None:
                a = self.fam_mixin_argatom(AltFamily, cmp)
                altmemo[cmp] = a
            return a(kind)
        return alt

    def biper(self, kind):
        return self.findex(kind)

    def _get_dictof(self):
        return self.fam_mixin_argatom(OwnedDictFamily)

    def _get_dictofnothing(self):
        return self.dictof(self.Use.Nothing)

    def _get_invtypemod(self):
        invtypemod = {}
        for k, v in list(self.types._module.__dict__.items()):
            if k.endswith('Type'):
                invtypemod[v] = k[:-4]
        return invtypemod

    def _get_notdict(self):
        return ~self.Use.Type.Dict

    def findex(self, *kinds):
        return self._er_by_(
            ByFindex,
            self,
            'findex(%s)' % ', '.join([repr(k) for k in kinds]),
            kinds
        )

    def _get_refdbynothing(self):
        return self.sonokind.refdby

    def sokind(self, *kinds):
        """sokind(0..*:Kind+) -> SetOfKind
"""
        cla = None
        clikinds = []
        if not kinds:
            raise ValueError('At least one argument must be given.')
        for kind in kinds:
            ckc = kind.get_ckc()
            if cla is None:
                cla = ckc[0]
            else:
                if ckc[0] is not cla:
                    raise ValueError(
                        'Kind at index %d has wrong classifier.' % len(clikinds))
            if ckc[-1] != '==':
                raise ValueError(
                    'Kind at index %d has wrong comparision.' % len(clikinds))
            clikinds.append(ckc[1])
        return QuickSoKind(cla, self.ImpSet.immnodeset(clikinds))

    def _get_sonokind(self):
        return SoNoKind(self.Unity, ())
