from flask import session
from functools import wraps
from inspect import signature
import cqi


def cqi_over_socketio(f):
    @wraps(f)
    def wrapped(*args):
        if 'd' not in session:
            return {'code': 424, 'msg': 'Failed Dependency'}
        f_args = {}
        # Check for missing args and if all provided args are of the right type
        for param in signature(f).parameters.values():
            if param.name == 'corpus_name':
                f_args[param.name] = f'NOPAQUE_{session["d"]["corpus_id"]}'
                continue
            if param.name == 'cqi_client':
                f_args[param.name] = session['d']['cqi_client']
                continue
            if param.default is param.empty:
                # args
                if param.name not in args[0]:
                    return {'code': 400, 'msg': 'Bad Request'}
                arg = args[0][param.name]
                if type(arg) is not param.annotation:
                    return {'code': 400, 'msg': 'Bad Request'}
                f_args[param.name] = arg
            else:
                # kwargs
                if param.name not in args[0]:
                    continue
                arg = args[0][param.name]
                if type(arg) is not param.annotation:
                    return {'code': 400, 'msg': 'Bad Request'}
                f_args[param.name] = arg
        session['d']['cqi_client_lock'].acquire()
        try:
            return_value = f(**f_args)
        except BrokenPipeError:
            return_value = {
                'code': 500,
                'msg': 'Internal Server Error'
            }
        except cqi.errors.CQiException as e:
            return_value = {
                'code': 500,
                'msg': 'Internal Server Error',
                'payload': {
                    'code': e.code,
                    'desc': e.description,
                    'msg': e.name
                }
            }
        finally:
            session['d']['cqi_client_lock'].release()
        return return_value
    return wrapped


def lookups_by_cpos(corpus, cpos_list):
    lookups = {}
    lookups['cpos_lookup'] = {cpos: {} for cpos in cpos_list}
    for attr in corpus.positional_attributes.list():
        cpos_attr_values = attr.values_by_cpos(cpos_list)
        for i, cpos in enumerate(cpos_list):
            lookups['cpos_lookup'][cpos][attr.attrs['name']] = \
                cpos_attr_values[i]
    for attr in corpus.structural_attributes.list():
        # We only want to iterate over non subattributes, identifiable by
        # attr.attrs['has_values'] == False
        if attr.attrs['has_values']:
            continue
        cpos_attr_ids = attr.ids_by_cpos(cpos_list)
        for i, cpos in enumerate(cpos_list):
            if cpos_attr_ids[i] == -1:
                continue
            lookups['cpos_lookup'][cpos][attr.attrs['name']] = cpos_attr_ids[i]
        occured_attr_ids = [x for x in set(cpos_attr_ids) if x != -1]
        if not occured_attr_ids:
            continue
        subattrs = corpus.structural_attributes.list(filters={'part_of': attr})
        if not subattrs:
            continue
        lookup_name = f'{attr.attrs["name"]}_lookup'
        lookups[lookup_name] = {}
        for attr_id in occured_attr_ids:
            lookups[lookup_name][attr_id] = {}
        for subattr in subattrs:
            subattr_name = subattr.attrs['name'][(len(attr.attrs['name']) + 1):]  # noqa
            for i, subattr_value in enumerate(subattr.values_by_ids(occured_attr_ids)):  # noqa
                lookups[lookup_name][occured_attr_ids[i]][subattr_name] = subattr_value  # noqa
    return lookups


def partial_export_subcorpus(subcorpus, match_id_list, context=25):
    if subcorpus.attrs['size'] == 0:
        return {"matches": []}
    match_boundaries = []
    for match_id in match_id_list:
        if match_id < 0 or match_id >= subcorpus.attrs['size']:
            continue
        match_boundaries.append(
            (
                match_id,
                subcorpus.dump(subcorpus.attrs['fields']['match'], match_id, match_id)[0],
                subcorpus.dump(subcorpus.attrs['fields']['matchend'], match_id, match_id)[0]
            )
        )
    cpos_set = set()
    matches = []
    for match_boundary in match_boundaries:
        match_num, match_start, match_end = match_boundary
        c = (match_start, match_end)
        if match_start == 0 or context == 0:
            lc = None
            cpos_list_lbound = match_start
        else:
            lc_lbound = max(0, (match_start - context))
            lc_rbound = match_start - 1
            lc = (lc_lbound, lc_rbound)
            cpos_list_lbound = lc_lbound
        if match_end == (subcorpus.collection.corpus.attrs['size'] - 1) or context == 0:
            rc = None
            cpos_list_rbound = match_end
        else:
            rc_lbound = match_end + 1
            rc_rbound = min(
                (match_end + context),
                (subcorpus.collection.corpus.attrs['size'] - 1)
            )
            rc = (rc_lbound, rc_rbound)
            cpos_list_rbound = rc_rbound
        match = {'num': match_num, 'lc': lc, 'c': c, 'rc': rc}
        matches.append(match)
        cpos_set.update(range(cpos_list_lbound, cpos_list_rbound + 1))
    lookups = lookups_by_cpos(subcorpus.collection.corpus, list(cpos_set))
    return {'matches': matches, **lookups}


def export_subcorpus(subcorpus, context=25, cutoff=float('inf'), offset=0):
    if subcorpus.attrs['size'] == 0:
        return {"matches": []}
    first_match = max(0, offset)
    last_match = min((offset + cutoff - 1), (subcorpus.attrs['size'] - 1))
    match_boundaries = zip(
        list(range(first_match, last_match + 1)),
        subcorpus.dump(subcorpus.attrs['fields']['match'], first_match, last_match),
        subcorpus.dump(subcorpus.attrs['fields']['matchend'], first_match, last_match)
    )
    cpos_set = set()
    matches = []
    for match_num, match_start, match_end in match_boundaries:
        c = (match_start, match_end)
        if match_start == 0 or context == 0:
            lc = None
            cpos_list_lbound = match_start
        else:
            lc_lbound = max(0, (match_start - context))
            lc_rbound = match_start - 1
            lc = (lc_lbound, lc_rbound)
            cpos_list_lbound = lc_lbound
        if match_end == (subcorpus.collection.corpus.attrs['size'] - 1) or context == 0:
            rc = None
            cpos_list_rbound = match_end
        else:
            rc_lbound = match_end + 1
            rc_rbound = min(
                (match_end + context),
                (subcorpus.collection.corpus.attrs['size'] - 1)
            )
            rc = (rc_lbound, rc_rbound)
            cpos_list_rbound = rc_rbound
        match = {'num': match_num, 'lc': lc, 'c': c, 'rc': rc}
        matches.append(match)
        cpos_set.update(range(cpos_list_lbound, cpos_list_rbound + 1))
    lookups = lookups_by_cpos(subcorpus.collection.corpus, list(cpos_set))
    return {'matches': matches, **lookups}
