import re

from sqlalchemy import func
from sqlalchemy.orm import joinedload

import pokedex.db.tables as t


def _parse_range(value):
    v = int(value)
    return lambda x: x == v


CRITERION_RX = re.compile(r"""
    \s*
    (?: (?P<field>[-_a-zA-Z0-9]+): )?
    (?P<pattern>
        (?:
            [^\s"]+?
        )+
    )
""", re.VERBOSE)
def parse_search_string(string):
    """Parses a search string!"""
    criteria = {}
    for match in CRITERION_RX.finditer(string):
        # TODO what if there are several of the same match!
        # TODO the cli needs to do append too
        field = match.group('field') or '*'
        criteria[field] = match.group('pattern')
    return criteria


def search(session, **criteria):
    query = (
        session.query(t.Pokemon)
        .options(
            joinedload(t.Pokemon.species)
        )
    )

    stat_query = (
        session.query(t.PokemonStat.pokemon_id)
        .join(t.PokemonStat.stat)
    )
    do_stat = False

    if criteria.get('name') is not None:
        query = query.filter(t.Pokemon.species.has(func.lower(t.PokemonSpecies.name) == criteria['name'].lower()))

    for stat_ident in (u'attack', u'defense', u'special-attack', u'special-defense', u'speed', u'hp'):
        criterion = criteria.get(stat_ident)
        if criterion is None:
            continue

        do_stat = True
        stat_query = stat_query.filter(
            (t.Stat.identifier == stat_ident)
            & _parse_range(criterion)(t.PokemonStat.base_stat)
        )

    if do_stat:
        query = query.filter(t.Pokemon.id.in_(stat_query.subquery()))

    return query.all()