"""
Functions for inspecting and interpreting the structure of an Xmrs.
"""

import warnings
from itertools import product

from delphin.mrs.components import nodes, links, var_id
from delphin.mrs.util import rargname_sortkey
from delphin.mrs.config import IVARG_ROLE

# query methods
def select_nodeids(xmrs, iv=None, label=None, pred=None):
    """
    Return the list of all nodeids whose respective [EP] has the
    matching *iv* (intrinsic variable), *label*, or *pred* values. If
    none match, return an empty list.
    """
    def datamatch(nid):
        ep = xmrs.ep(nid)
        return ((iv is None or ep.iv == iv) and
                (pred is None or ep.pred == pred) and
                (label is None or ep.label == label))
    return list(filter(datamatch, xmrs.nodeids()))


def select_nodes(xmrs, nodeid=None, pred=None):
    """
    Return the list of all [Nodes] that have the matching *nodeid*
    and/or *pred* values. If none match, return an empty list.
    """
    nodematch = lambda n: ((nodeid is None or n.nodeid == nodeid) and
                           (pred is None or n.pred == pred))
    return list(filter(nodematch, nodes(xmrs)))


def select_eps(xmrs, nodeid=None, iv=None, label=None, pred=None):
    """
    Return the list of all [EPs] that have the matching *nodeid*,
    *iv*, *label*, and or *pred* values. If none match, return an
    empty list.
    """
    epmatch = lambda n: ((nodeid is None or n.nodeid == nodeid) and
                         (iv is None or n.iv == iv) and
                         (label is None or n.label == label) and
                         (pred is None or n.pred == pred))
    return list(filter(epmatch, xmrs.eps()))


def select_args(xmrs, nodeid=None, rargname=None, value=None):
    """
    Return a list of triples (nodeid, rargname, value) for all arguments
    matching *nodeid*, *rargname*, and/or *value* values. If none match,
    return an empty list.
    """
    argmatch = lambda a: ((nodeid is None or a[0] == nodeid) and
                          (rargname is None or
                           a[1].upper() == rargname.upper()) and
                          (value is None or a[2] == value))
    all_args = (
        (nid, role, val)
        for nid in xmrs.nodeids()
        for role, val in sorted(
            xmrs.args(nid).items(),
            key=lambda i: rargname_sortkey(i[0])
        )
    )
    return list(filter(argmatch, all_args))


def select_links(xmrs, start=None, end=None, rargname=None, post=None):
    """
    Return the list of all [Links] that have the matching *start*,
    *end*, *rargname*, and/or *post* values. If none match, return
    an empty list.
    """
    linkmatch = lambda l: (
        (start is None or l.start == start) and
        (end is None or l.end == end) and
        (rargname is None or l.rargname == rargname) and
        (post is None or l.post == post))
    return list(filter(linkmatch, links(xmrs)))


def select_hcons(xmrs, hi=None, relation=None, lo=None):
    """
    Return the list of all [HandleConstraints] that have the matching
    *hi*, *relation*, and/or *lo* values. If none match, return an
    empty list.
    """
    hcmatch = lambda hc: (
        (hi is None or hc.hi == hi) and
        (relation is None or hc.relation == relation) and
        (lo is None or hc.lo == lo))
    return list(filter(hcmatch, xmrs.hcons()))


def select_icons(xmrs, left=None, relation=None, right=None):
    """
    Return the list of all [IndividualConstraints] that have the
    matching *left*, *relation*, and/or *right* values. If none
    match, return an empty list.
    """
    icmatch = lambda ic: (
        (left is None or ic.left == left) and
        (relation is None or ic.relation == relation) and
        (right is None or ic.right == right))
    return list(filter(icmatch, xmrs.icons()))


def find_argument_target(xmrs, nodeid, rargname):
    """
    Return the target of an argument (rather than just the variable).

    Args:
        xmrs: The [Xmrs] object to use.
        nodeid: The nodeid of the argument.
        rargname: The role-argument name of the argument.
    Returns:
        The object that is the target of the argument. Possible values
        include:

        | Arg value          | e.g.  | Target                        |
        | ------------------ | ----- | ----------------------------- |
        | intrinsic variable | x4    | nodeid; of the EP with the IV |
        | hole variable      | h0    | nodeid; HCONS's labelset head |
        | label              | h1    | nodeid; label's labelset head |
        | unbound variable   | i3    | the variable itself           |
        | constant           | "IBM" | the constant itself           |

    Note:
        If the argument value is an intrinsic variable whose target is
        an EP that has a quantifier, the non-quantifier EP's nodeid
        will be returned. With this nodeid, one can then use
        find_quantifier() to get its quantifier's nodeid.
    """
    tgt = xmrs.args(nodeid)[rargname]
    if tgt in xmrs.variables():
        try:
            return xmrs.nodeid(tgt)
        except KeyError:
            pass

        try:
            tgt = xmrs.hcon(tgt).lo
            return next(iter(xmrs.labelset_heads(tgt)), None)
        except KeyError:
            pass

        try:
            return next(iter(xmrs.labelset_heads(tgt)))
        except (KeyError, StopIteration):
            pass
    return tgt


def find_subgraphs_by_preds(xmrs, preds, connected=None):
    """
    Yield subgraphs matching a list of preds. Because preds may match
    multiple EPs/nodes in the Xmrs, more than one subgraph is
    possible.

    Args:
        xmrs: The [Xmrs] object to use.
        preds: An iterable of [Preds] to include in subgraphs.
        connected: If True, all yielded subgraphs must be connected,
            as determined by Xmrs.is_connected().
    Yields:
        [Xmrs] objects for the found subgraphs.
    """
    preds = list(preds)
    count = len(preds)
    # find all lists of nodeids such that the lists have no repeated nids;
    # keep them as a list (fixme: why not just get sets?)
    nidsets = set(
        tuple(sorted(ns))
        for ns in filter(
            lambda ns: len(set(ns)) == count,
            product(*[select_nodeids(xmrs, pred=p) for p in preds])
        )
    )
    for nidset in nidsets:
        sg = xmrs.subgraph(nidset)
        if connected is None or sg.is_connected() == connected:
            yield sg


# def introduced_variables(xmrs):
#     return [var for var, vd in xmrs._vars.items()
#             if 'iv' in vd or 'LBL' in vd['refs'] or 'hcons' in vd]

def intrinsic_variable(xmrs, nid):
    return xmrs.args(nid).get(IVARG_ROLE, None)

def intrinsic_variables(xmrs):
    ivs = set(
        ep[3][IVARG_ROLE] for ep in xmrs.eps()
        if not ep[1].is_quantifier() and IVARG_ROLE in ep[3]
    )
    return sorted(ivs, key=var_id)

def bound_variables(xmrs):
    bvs = set(
        ep[3][IVARG_ROLE] for ep in xmrs.eps()
        if ep[1].is_quantifier() and IVARG_ROLE in ep[3]
    )
    return sorted(bvs, key=var_id)

def in_labelset(xmrs, nodeids, label=None):
    """
    Test if all nodeids share a label.

    Args:
        nodeids: An iterable of nodeids.
        label: If given, all nodeids must share this label.
    Returns:
        True if all nodeids share a label, otherwise False.
    """
    nodeids = set(nodeids)
    if label is None:
        label = xmrs.ep(next(iter(nodeids))).label
    return nodeids.issubset(xmrs._vars[label]['refs']['LBL'])


#def audit(xmrs): inspect well-formedness and report individual errors

# deprecated

def find_quantifier(xmrs, nodeid):
    warnings.warn(
        'find_quantifier() is deprecated; '
        'try xmrs.nodeid(xmrs.ep(nodeid).iv, quantifier=True)',
        DeprecationWarning
    )
    ep = xmrs.ep(nodeid)
    if not ep.is_quantifier():
        return xmrs.nodeid(xmrs.ep(nodeid).iv, quantifier=True)
    return None

def get_outbound_args(xmrs, nodeid, allow_unbound=True):
    warnings.warn(
        'get_outbound_args() is deprecated; '
        'try xmrs.outgoing_args(nodeid)',
        DeprecationWarning
    )
    tgts = set(intrinsic_variables(xmrs))
    tgts.update(xmrs.labels())
    tgts.update(hc.hi for hc in xmrs.hcons())
    return [
        (nodeid, role, val)
        for role, val in sorted(
            xmrs.outgoing_args(nodeid).items(),
            key=lambda r_v: rargname_sortkey(r_v[0])
        )
        if allow_unbound or val in tgts
    ]

def nodeid(xmrs, iv, quantifier=False):
    warnings.warn(
        'nodeid() is deprecated; '
        'try xmrs.nodeid(iv, quantifier=quantifier)',
        DeprecationWarning
    )
    return xmrs.nodeid(iv, quantifier=quantifier)
