#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" :mod:`java2python.lang.selector` -- Declarative AST node selection
=======================================================================

.. module:: java2python.lang.selector
    :synopsis: Declarative AST node selection.
.. moduleauthor:: Troy Melhase <troy@gci.net>

This module provides classes for simple AST node selection that can be
easily combined to form complex, declarative rules for retrieving AST
nodes.

The classes are similar to CSS selectors, with a nod to Python parsing
libraries like LEPL and PyParsing.  At the moment, only a few very
basic selector types are implemented, but those that are here already
provide all of the functionality necessary for use within the package.

Projects using java2python should regard this subpackage as
experimental.  While the interfaces are not expected to change, the
semantics may.  Use with caution.
"""
from java2python.lang import tokens


class Selector(object):
    """ Base class for concrete selectors; provides operator methods. """

    def __add__(self, other):
        """ E + F

        Like CSS "E + F": an F element immediately preceded by an E element
        """
        return AdjacentSibling(self, other)

    def __and__(self, other):
        """ E & F

        Like CSS "E F":  an F element descendant of an E element
        """
        return Descendant(self, other)

    def __call__(self, *args, **kwds):
        """ Subclasses must implement. """
        raise NotImplementedError('Selector class cannot be called.')

    def __getitem__(self, key):
        """ E[n]

        Like CSS "E:nth-child(n)": an E element, the n-th child of its parent
        """
        return Nth(self, key)

    def __gt__(self, other):
        """ E > F

        Like CSS: "E > F": an F element child of an E element
        """
        return Child(self, other)

    def __div__(self, other):
        """ E / F

        Produces a AnySibling.
        """
        return AnySibling(self, other)

    def walk(self, tree):
        """ Select items from the tree and from the tree children. """
        for item in self(tree):
            yield item
        for child in tree.children:
            for item in self.walk(child):
                yield item


class Token(Selector):
    """ Token(...) -> select tokens by matching attributes.

    Token is the most generic and flexible Selector; using it,
    arbitrary nodes of any type, line number, position, and/or text
    can be selected.

    Calling Token() without any keywords is equivalent to:

        Token(channel=None, index=None, input=None, line=None,
              start=None, stop=None, text=None, type=None)

    Note that the value of each keyword can be a constant or a
    callable.  When callables are specified, they are passed a the
    token and should return a bool.
    """

    def __init__(self, **attrs):
        self.attrs = attrs
        ## we support strings so that the client can refer to the
        ## token name that way instead of via lookup or worse, integer.
        if isinstance(attrs.get('type'), (basestring, )):
            self.attrs['type'] = getattr(tokens, attrs.get('type'))

    def __call__(self, tree):
        items = self.attrs.items()
        token = tree.token

        def match_or_call(k, v):
            if callable(v):
                return v(token)
            return getattr(token, k)==v

        if all(match_or_call(k, v) for k, v in items if v is not None):
            yield tree

    def __str__(self):
        items = self.attrs.items()
        keys = ('{}={}'.format(k, v) for k, v in items if v is not None)
        return 'Token({})'.format(', '.join(keys))


class Nth(Selector):
    """ E[n] ->  match any slice n of E

    Similar to the :nth-child pseudo selector in CSS, but without the
    support for keywords like 'odd', 'even', etc.
    """
    def __init__(self, e, key):
        self.e, self.key = e, key

    def __call__(self, tree):
        for etree in self.e(tree):
            try:
                matches = tree.children[self.key]
            except (IndexError, ):
                return
            if not isinstance(matches, (list, )):
                matches = [matches]
            for child in matches:
                yield child

    def __str__(self):
        return 'Nth({0})[{1}]'.format(self.e, self.key)


class Child(Selector):
    """ E > F    select any F that is a child of E

    """
    def __init__(self, e, f):
        self.e, self.f = e, f

    def __call__(self, tree):
        for ftree in self.f(tree):
            for etree in self.e(tree.parent):
                yield ftree

    def __str__(self):
        return 'Child({0} > {1})'.format(self.e, self.f)


class Type(Selector):
    """ Type(T)    select any token of type T

    Similar to the type selector in CSS.
    """
    def __init__(self, key, value=None):
        self.key = key if isinstance(key, int) else getattr(tokens, key)
        self.value = value

    def __call__(self, tree):
        if tree.token.type == self.key:
            if self.value is None or self.value == tree.token.text:
                yield tree

    def __str__(self):
        val = '' if self.value is None else '={0}'.format(self.value)
        return 'Type({0}{1}:{2})'.format(tokens.map[self.key], val, self.key)


class Star(Selector):
    """ *    select any

    Similar to the * selector in CSS.
    """
    def __call__(self, tree):
        yield tree

    def __str__(self):
        return 'Star(*)'


class Descendant(Selector):
    """ E & F    select any F that is a descendant of E

    """
    def __init__(self, e, f):
        self.e, self.f = e, f

    def __call__(self, tree):
        for ftree in self.f(tree):
            root, ftree = ftree, ftree.parent
            while ftree:
                for etree in self.e(ftree):
                    yield root
                ftree = ftree.parent

    def __str__(self):
        return 'Descendant({0} & {1})'.format(self.e, self.f)


class AdjacentSibling(Selector):
    """ E + F    select any F immediately preceded by a sibling E

    """
    def __init__(self, e, f):
        self.e, self.f = e, f

    def __call__(self, node):
        if not node.parent:
            return
        for ftree in self.f(node):
            index = node.parent.children.index(ftree)
            if not index:
                return
            previous = node.parent.children[index-1]
            for child in self.e(previous):
                yield ftree

    def __str__(self):
        return 'AdjacentSibling({} + {})'.format(self.e, self.f)


class AnySibling(Selector):
    """ E / F    select any F preceded by a sibling E

    """
    def __init__(self, e, f):
        self.e, self.f = e, f

    def __call__(self, node):
        if not node.parent:
            return
        for ftree in self.f(node):
            index = node.parent.children.index(ftree)
            if not index:
                return
            for prev in node.parent.children[:index]:
                for child in self.e(prev):
                    yield ftree

    def __str__(self):
        return 'AnySibling({} / {})'.format(self.e, self.f)
