import re


def _naive_search_for_run(session, ts, query, num_results, default_machine):
    """
    This 'naive' search doesn't rely on any indexes so can be used without
    full-text search enabled. This does make it less clever however.

    It is able to match queries for machine names and order numbers
    (specifically llvm_project_revision numbers). The revision numbers may be
    partial and may be preceded by '#' or 'r'. Any other non-integer tokens are
    considered to be partial matches for a machine name; any machine that
    contains ALL of the tokens will be searched.
    """

    order_re = re.compile(r'[r#]?(\d+)')
    machine_queries = []
    order_queries = []

    # First, tokenize the query string.
    for q in query.split(' '):
        if not q:
            # Prune zero-length tokens
            continue
        m = order_re.match(q)
        if m:
            order_queries.append(int(m.group(1)))
        else:
            machine_queries.append(q)

    if not machine_queries and not default_machine:
        # No machines to query: no matches. We can't query all machines, we'd
        # end up doing a full table scan and that is not scalable.
        return []

    machines = []
    if not machine_queries:
        machines = [default_machine]
    else:
        for m in session.query(ts.Machine).all():
            if all(q in m.name for q in machine_queries):
                machines.append(m.id)

    if not machines:
        return []

    llvm_project_revision_idx = [i
                                 for i, f in enumerate(ts.Order.fields)
                                 if f.name == 'llvm_project_revision'][0]
    llvm_project_revision_col = \
        ts.Order.fields[llvm_project_revision_idx].column

    q = session.query(ts.Run) \
        .filter(ts.Run.machine_id.in_(machines)) \
        .filter(ts.Run.order_id == ts.Order.id) \
        .filter(llvm_project_revision_col.isnot(None))
    if order_queries:
        oq = '%' + str(order_queries[0]) + '%'
        q = q.filter(llvm_project_revision_col.like(oq))

    return q.order_by(ts.Run.id.desc()).limit(num_results).all()


def search(session, ts, query,
           num_results=8, default_machine=None):
    """
    Performs a textual search for a run. The exact syntax supported depends on
    the engine used to perform the search; see _naive_search_for_run for the
    minimum supported syntax.

    ts: TestSuite object
    query: Textual query string
    num_results: Number of results to return
    default_machine: If no machines were specified (only orders), return
    results from this machine.

    Returns a list of Run objects.
    """

    return _naive_search_for_run(session, ts, query,
                                 num_results, default_machine)
