from floof.model import Art, ArtUser, ArtUserType, Tag, TagText, User
import elixir
from dbhelpers import find_or_create
import re

def parse(search_string, me):
    """Parses a search query, and returns a query object on Art.

    Queries can contain:
    - Regular tags: foo
    - User relations: by:kalu, of:eevee, for:ootachi
    - User relations relative to some user: by:me
      Note that this necessitates a `me` parameter.

    Later:
    - Negative versions of anything above: -by:eevee, -dongs
    """

    # XXX doesn't do negative querying yet.
    # XXX could use some sane limits.

    # We'll be building this as we go.
    q = Art.query

    terms = search_string.split()
    for tag in terms:
        if ':' in tag:
            # This is a special tag; at the moment, by/for/of to indicate
            # related users
            prefix, tag = tag.split(':', 1)

            if tag == 'me':
                target_user = me
            else:
                # XXX what to do if this fails?  abort?  return empty query?
                target_user = User.get_by(name=tag)

            if prefix == 'by':
                rel = ArtUserType.BY
            elif prefix == 'for':
                rel = ArtUserType.FOR
            elif prefix == 'of':
                rel = ArtUserType.OF
            else:
                # Bogus tag.  Not sure what to do here, so for the moment,
                # ignore it
                continue

            # Inner join to the ArtUser table
            q = q.join(ArtUser, aliased=True) \
                 .filter(ArtUser.user == target_user) \
                 .filter(ArtUser.type == rel)

        else:
            # Regular ol' tag
            q = q.join(Tag, TagText, aliased=True) \
                 .filter(TagText.text == tag)

    return q

def add_tags(art, tag_string, adding_user, me):
    """Takes a string that looks like a tag query, and effectively modifies the
    art's tags to match it.
    """

    # XXX what to do with invalid tags?  just return them and let caller fix?
    bad_tags = []
    for tag_text in tag_string.split():
        original_tag_text = tag_text
        tag_text = tag_text.lower()

        # Adding or removing a tag?
        if tag_text[0] == '-':
            add = False
            tag_text = tag_text[1:]
        else:
            # Allow "+foo" to mean "add foo"
            if tag_text[0] == '+':
                tag_text = tag_text[1:]
            add = True

        # Check for special namespaces
        prefix = None
        if ':' in tag_text:
            prefix, tag_text = tag_text.split(':', 1)
            if prefix not in ['by', 'for', 'of']:
                # This is bogus.  Skip it.
                bad_tags.append(original_tag_text)
                continue

            if prefix == 'by':
                # XXX this needs supporting.  silently ignore for now
                continue

        # Must be 3-50 alphanumeric characters
        if not re.match('^[a-z0-9]{3,50}$', tag_text):
            bad_tags.append(original_tag_text)
            continue

        # Do work!
        if prefix:
            if tag_text == 'me':
                target_user = me
            else:
                target_user = User.get_by(name=tag_text)

            # Special tag; at the moment, just a relationship
            if prefix == 'by':
                rel = ArtUserType.BY
            elif prefix == 'for':
                rel = ArtUserType.FOR
            elif prefix == 'of':
                rel = ArtUserType.OF

            user_assoc_data = dict(art=art, user=target_user, type=rel)
            if add:
                find_or_create(ArtUser, **user_assoc_data)

            else:
                # XXX this will die for nonassociations
                user_assoc = ArtUser.get_by(**user_assoc_data)
                user_assoc.delete()

        else:
            # Regular tag
            if add:
                tag = find_or_create(TagText, text=tag_text)
                find_or_create(Tag, art=art, tagger=adding_user, tagtext=tag)

            else:
                tag = TagText.get_by(text=tag_text)
                if tag:
                    # XXX this will die
                    tag_assoc = Tag.get_by(art=art, tagger=adding_user, tagtext=tag)
                    tag_assoc.delete()

    elixir.session.commit()
