# -*- coding: utf-8 -*-
"""
CommandLine:
    python -m ibeis.gui.inspect_gui --test-test_review_widget --show
"""
from __future__ import absolute_import, division, print_function, unicode_literals
from functools import partial
from ibeis.viz import viz_helpers as vh
import guitool as gt
import numpy as np
import utool as ut
(print, rrr, profile) = ut.inject2(__name__, '[id_review_api]')


MATCHED_STATUS_TEXT  = 'Matched'
REVIEWED_STATUS_TEXT = 'Reviewed'


REVIEW_CFG_DEFAULTS = {
    'ranks_top': 5,
    'directed': False,
    'name_scoring': True,
    'filter_reviewed': True,
    'filter_photobombs': True,
    'filter_true_matches': True,
    'show_chips': True,
    'filter_duplicate_true_matches': False,
}


@profile
def get_review_edges(cm_list, ibs=None, review_cfg={}):
    r"""
    Needs to be moved to a better file. Maybe something to do with
    identification.

    Returns a list of matches that should be inspected
    This function is more lightweight than orgres or allres.
    Used in id_review_api and interact_qres2

    Args:
        cm_list (list): list of chip match objects
        ranks_top (int): put all ranks less than this number into the graph
        directed (bool):

    Returns:
        tuple: review_edges = (qaid_arr, daid_arr, score_arr, rank_arr)

    CommandLine:
        python -m ibeis.gui.id_review_api get_review_edges:0

    Example0:
        >>> # ENABLE_DOCTEST
        >>> from ibeis.gui.id_review_api import *  # NOQA
        >>> import ibeis
        >>> ibs = ibeis.opendb('PZ_MTEST')
        >>> qreq_ = ibeis.main_helpers.testdata_qreq_()
        >>> cm_list = qreq_.execute()
        >>> review_cfg = dict(ranks_top=5, directed=True, name_scoring=False,
        >>>                   filter_true_matches=True)
        >>> review_edges = get_review_edges(cm_list, ibs=ibs, review_cfg=review_cfg)
        >>> print(review_edges)

    Example1:
        >>> # UNSTABLE_DOCTEST
        >>> from ibeis.gui.id_review_api import *  # NOQA
        >>> import ibeis
        >>> ibs = ibeis.opendb('PZ_MTEST')
        >>> qaid_list = ibs.get_valid_aids()[0:5]
        >>> daid_list = ibs.get_valid_aids()[0:20]
        >>> cm_list = ibs.query_chips(qaid_list, daid_list)
        >>> review_cfg = dict(ranks_top=5, directed=True, name_scoring=False,
        >>>                   filter_reviewed=False, filter_true_matches=True)
        >>> review_edges = get_review_edges(cm_list, review_cfg=review_cfg, ibs=ibs)
        >>> print(review_edges)

    Example3:
        >>> # UNSTABLE_DOCTEST
        >>> from ibeis.gui.id_review_api import *  # NOQA
        >>> import ibeis
        >>> ibs = ibeis.opendb('PZ_MTEST')
        >>> qaid_list = ibs.get_valid_aids()[0:1]
        >>> daid_list = ibs.get_valid_aids()[10:100]
        >>> qaid2_cm = ibs.query_chips(qaid_list, daid_list)
        >>> review_cfg = dict(ranks_top=1, directed=False, name_scoring=False,
        >>>                   filter_reviewed=False, filter_true_matches=True)
        >>> review_edges = get_review_edges(cm_list, review_cfg=review_cfg, ibs=ibs)
        >>> print(review_edges)

    Example4:
        >>> # UNSTABLE_DOCTEST
        >>> from ibeis.gui.id_review_api import *  # NOQA
        >>> import ibeis
        >>> ibs = ibeis.opendb('PZ_MTEST')
        >>> qaid_list = ibs.get_valid_aids()[0:10]
        >>> daid_list = ibs.get_valid_aids()[0:10]
        >>> qres_list = ibs.query_chips(qaid_list, daid_list)
        >>> ranks_top = 3
        >>> review_cfg = dict(ranks_top=3, directed=False, name_scoring=False,
        >>>                   filter_reviewed=False, filter_true_matches=True)
        >>> review_edges = get_review_edges(cm_list, review_cfg=review_cfg, ibs=ibs)
        >>> print(review_edges)
    """
    import vtool as vt
    from ibeis.algo.hots import chip_match
    automatch_kw = REVIEW_CFG_DEFAULTS.copy()
    automatch_kw = ut.update_existing(automatch_kw, review_cfg)
    print('[resorg] get_review_edges(%s)' % (ut.repr2(automatch_kw)))
    print('[resorg] len(cm_list) = %d' % (len(cm_list)))
    qaids_stack  = []
    daids_stack  = []
    ranks_stack  = []
    scores_stack = []

    # For each QueryResult, Extract inspectable candidate matches
    if isinstance(cm_list, dict):
        cm_list = list(cm_list.values())

    if len(cm_list) == 0:
        return ([], [], [], [])

    for cm in cm_list:
        if isinstance(cm, chip_match.ChipMatch):
            daids  = cm.get_top_aids(ntop=automatch_kw['ranks_top'])
            scores = cm.get_top_scores(ntop=automatch_kw['ranks_top'])
            ranks  = np.arange(len(daids))
            qaids  = np.full(daids.shape, cm.qaid, dtype=daids.dtype)
        else:
            (qaids, daids, scores, ranks) = cm.get_match_tbldata(
                ranks_top=automatch_kw['ranks_top'],
                name_scoring=automatch_kw['name_scoring'],
                ibs=ibs)
        qaids_stack.append(qaids)
        daids_stack.append(daids)
        scores_stack.append(scores)
        ranks_stack.append(ranks)

    # Stack them into a giant array
    qaid_arr  = np.hstack(qaids_stack)
    daid_arr  = np.hstack(daids_stack)
    score_arr = np.hstack(scores_stack)
    rank_arr  = np.hstack(ranks_stack)

    # Sort by scores
    sortx = score_arr.argsort()[::-1]
    qaid_arr  = qaid_arr[sortx]
    daid_arr   = daid_arr[sortx]
    score_arr = score_arr[sortx]
    rank_arr  = rank_arr[sortx]

    if automatch_kw['filter_reviewed']:
        _is_reviewed = ibs.get_annot_pair_is_reviewed(qaid_arr.tolist(),
                                                      daid_arr.tolist())
        is_unreviewed = ~np.array(_is_reviewed, dtype=np.bool)
        qaid_arr  = qaid_arr.compress(is_unreviewed)
        daid_arr   = daid_arr.compress(is_unreviewed)
        score_arr = score_arr.compress(is_unreviewed)
        rank_arr  = rank_arr.compress(is_unreviewed)

    # Remove directed edges
    if not automatch_kw['directed']:
        #nodes = np.unique(directed_edges.flatten())
        directed_edges = np.vstack((qaid_arr, daid_arr)).T
        #idx1, idx2 = vt.intersect2d_indices(directed_edges, directed_edges[:, ::-1])

        unique_rowx = vt.find_best_undirected_edge_indexes(directed_edges,
                                                           score_arr)

        qaid_arr  = qaid_arr.take(unique_rowx)
        daid_arr  = daid_arr.take(unique_rowx)
        score_arr = score_arr.take(unique_rowx)
        rank_arr  = rank_arr.take(unique_rowx)

    # Filter Double Name Matches
    if automatch_kw['filter_duplicate_true_matches']:
        # filter_dup_namepairs
        qnid_arr = ibs.get_annot_nids(qaid_arr)
        dnid_arr = ibs.get_annot_nids(daid_arr)
        if not automatch_kw['directed']:
            directed_name_edges = np.vstack((qnid_arr, dnid_arr)).T
            unique_rowx2 = vt.find_best_undirected_edge_indexes(
                directed_name_edges, score_arr)
        else:
            namepair_id_list = np.array(vt.compute_unique_data_ids_(
                list(zip(qnid_arr, dnid_arr))))
            unique_namepair_ids, namepair_groupxs = vt.group_indices(namepair_id_list)
            score_namepair_groups = vt.apply_grouping(score_arr, namepair_groupxs)
            unique_rowx2 = np.array(sorted([
                groupx[score_group.argmax()]
                for groupx, score_group in zip(namepair_groupxs, score_namepair_groups)
            ]), dtype=np.int32)
        qaid_arr  = qaid_arr.take(unique_rowx2)
        daid_arr  = daid_arr.take(unique_rowx2)
        score_arr = score_arr.take(unique_rowx2)
        rank_arr  = rank_arr.take(unique_rowx2)

    # Filter all true matches
    if automatch_kw['filter_true_matches']:
        qnid_arr = ibs.get_annot_nids(qaid_arr)
        dnid_arr = ibs.get_annot_nids(daid_arr)
        valid_flags = qnid_arr != dnid_arr
        qaid_arr  = qaid_arr.compress(valid_flags)
        daid_arr   = daid_arr.compress(valid_flags)
        score_arr = score_arr.compress(valid_flags)
        rank_arr  = rank_arr.compress(valid_flags)

    if automatch_kw['filter_photobombs']:
        unique_aids = ut.unique(ut.flatten([qaid_arr, daid_arr]))
        #grouped_aids, unique_nids = ibs.group_annots_by_name(unique_aids)
        invalid_nid_map = get_photobomber_map(ibs, qaid_arr)

        nid2_aids = ut.group_items(unique_aids, ibs.get_annot_nids(unique_aids))

        expanded_aid_map = ut.ddict(set)
        for nid1, other_nids in invalid_nid_map.items():
            for aid1 in nid2_aids[nid1]:
                for nid2 in other_nids:
                    for aid2 in nid2_aids[nid2]:
                        expanded_aid_map[aid1].add(aid2)
                        expanded_aid_map[aid2].add(aid1)

        valid_flags = [daid not in expanded_aid_map[qaid]
                       for qaid, daid in zip(qaid_arr, daid_arr)]
        qaid_arr  = qaid_arr.compress(valid_flags)
        daid_arr   = daid_arr.compress(valid_flags)
        score_arr = score_arr.compress(valid_flags)
        rank_arr  = rank_arr.compress(valid_flags)

    review_edges = (qaid_arr, daid_arr, score_arr, rank_arr)
    return review_edges


def make_review_api(ibs, cm_list, review_cfg, qreq_=None):
    #ranks_top=None, #name_scoring=False, #filter_reviewed=False, #filter_true_matches=False, #qreq_=None, #):
    """
    Builds columns which are displayable in a ColumnListTableWidget

    CommandLine:
        python -m ibeis.gui.id_review_api --test-test_review_widget --show
        python -m ibeis.gui.id_review_api --test-make_review_api

    Example:
        >>> # ENABLE_DOCTEST
        >>> from ibeis.gui.id_review_api import *  # NOQA
        >>> import ibeis
        >>> import guitool as gt
        >>> from ibeis.gui import id_review_api
        >>> cm_list, qreq_ = ibeis.main_helpers.testdata_cmlist()
        >>> tblname = 'chipmatch'
        >>> name_scoring = False
        >>> ranks_top = 5
        >>> review_cfg = dict(ranks_top=ranks_top, name_scoring=name_scoring)
        >>> review_api = make_review_api(qreq_.ibs, cm_list, review_cfg, qreq_=qreq_)
        >>> print('review_api = %r' % (review_api,))
    """
    # TODO: Add in timedelta to column info
    if ut.VERBOSE:
        print('[inspect] make_review_api')

    review_edges = get_review_edges(cm_list, ibs=ibs, review_cfg=review_cfg)
    # Get extra info
    (qaids, daids, scores, ranks) = review_edges

    RES_THUMB_TEXT = 'ResThumb'  # NOQA
    QUERY_THUMB_TEXT = 'querythumb'
    MATCH_THUMB_TEXT = 'MatchThumb'

    col_name_list = [
        'result_index',
        'score',
        REVIEWED_STATUS_TEXT,
    ]

    if review_cfg.get('show_chips', True):
        col_name_list += [
            MATCHED_STATUS_TEXT,
            QUERY_THUMB_TEXT,
        ]

    col_name_list += [
        RES_THUMB_TEXT,
        'qaid',
        'aid',
        'rank',
        'timedelta',
        'dnGt',
        'qnGt',
        'tags',
        'qname',
        'name',
    ]

    col_types_dict = dict([
        ('qaid',       int),
        ('aid',        int),
        ('dnGt',      int),
        ('qnGt',      int),
        ('timedelta',   float),
        #('review',     'BUTTON'),
        (MATCHED_STATUS_TEXT, str),
        (REVIEWED_STATUS_TEXT, str),
        (QUERY_THUMB_TEXT, 'PIXMAP'),
        (RES_THUMB_TEXT,   'PIXMAP'),
        ('qname',      str),
        ('name',       str),
        ('score',      float),
        ('rank',       int),
        ('truth',      bool),
        ('opt',        int),
        ('result_index',  int),
    ])
    timedelta_list = np.array(ut.take_column(ibs.get_unflat_annots_timedelta_list(list(zip(qaids, daids))), 0))
    # TODO: make a display role
    #timediff_list = [ut.get_posix_timedelta_str(t, year=True, approx=True) for t in (timedelta_list * 60 * 60)]

    def get_pair_tags(edge):
        aid1, aid2 = edge
        assert not ut.isiterable(aid1), 'aid1=%r, aid2=%r' % (aid1, aid2)
        assert not ut.isiterable(aid2), 'aid1=%r, aid2=%r' % (aid1, aid2)
        am_rowids = ibs.get_annotmatch_rowid_from_undirected_superkey(
            [aid1], [aid2])
        tag_text = ibs.get_annotmatch_tag_text(am_rowids)[0]
        if tag_text is None:
            tag_text = ''
        return str(tag_text)

    col_getter_dict = dict([
        ('qaid',       np.array(qaids)),
        ('aid',        np.array(daids)),
        ('dnGt',      ibs.get_annot_num_groundtruth),
        ('qnGt',      ibs.get_annot_num_groundtruth),
        ('timedelta', np.array(timedelta_list)),
        #('review',     lambda rowid: get_buttontup),
        (MATCHED_STATUS_TEXT,  partial(get_match_status, ibs)),
        (REVIEWED_STATUS_TEXT,  partial(get_reviewed_status, ibs)),
        (QUERY_THUMB_TEXT, ibs.get_annot_chip_thumbtup),
        (RES_THUMB_TEXT,   ibs.get_annot_chip_thumbtup),
        ('qname',      ibs.get_annot_names),
        ('name',       ibs.get_annot_names),
        ('score',      np.array(scores)),
        ('rank',       np.array(ranks)),
        ('result_index',       np.arange(len(ranks))),
        ('tags', get_pair_tags),
        #lambda aid_pair: ibs.get_annotmatch_tag_text(ibs.get_annotmatch_rowid_from_undirected_superkey(ut.ensure_iterable(aid_pair[0]), ut.ensure_iterable(aid_pair[1])))[0]),
        #('truth',     truths),
        #('opt',       opts),
    ])

    # default is 100
    col_width_dict = {
        'score': 75,
        REVIEWED_STATUS_TEXT: 75,
        MATCHED_STATUS_TEXT: 75,
        'rank': 42,
        'qaid': 42,
        'aid': 42,
        'result_index': 42,
        'qname': 60,
        'name': 60,
        'dnGt': 50,
        'timedelta': 75,
        'tags': 75,
        'qnGt': 50,
    }

    USE_MATCH_THUMBS = 1
    if USE_MATCH_THUMBS:

        def get_match_thumbtup(ibs, qaid2_cm, qaids, daids, index, qreq_=None,
                               thumbsize=(128, 128), match_thumbtup_cache={}):
            daid = daids[index]
            qaid = qaids[index]
            cm = qaid2_cm[qaid]
            assert cm.qaid == qaid, 'aids do not aggree'

            OLD = False
            if OLD:
                fpath = ensure_match_img(ibs, cm, daid, qreq_=qreq_,
                                         match_thumbtup_cache=match_thumbtup_cache)
                if isinstance(thumbsize, int):
                    thumbsize = (thumbsize, thumbsize)
                thumbtup = (ut.augpath(fpath, 'thumb_%d,%d' % thumbsize), fpath, thumbsize,
                            [], [])
                return thumbtup
            else:
                # Hacky new way of drawing
                fpath, func, func2 = make_ensure_match_img_nosql_func(qreq_, cm, daid)
                #match_thumbdir = ibs.get_match_thumbdir()
                #match_thumb_fname = get_match_thumb_fname(cm, daid, qreq_)
                #fpath = ut.unixjoin(match_thumbdir, match_thumb_fname)
                thumbdat = {
                    'fpath': fpath,
                    'thread_func': func,
                    'main_func': func2,
                    #'args': (ibs, cm, daid),
                    #'kwargs': dict(qreq_=qreq_,
                    #               match_thumbtup_cache=match_thumbtup_cache)
                }
                return thumbdat

        col_name_list.insert(col_name_list.index('qaid'),
                             MATCH_THUMB_TEXT)
        col_types_dict[MATCH_THUMB_TEXT] = 'PIXMAP'
        #col_types_dict[MATCH_THUMB_TEXT] = CustomMatchThumbDelegate
        qaid2_cm = {cm.qaid: cm for cm in cm_list}
        get_match_thumbtup_ = partial(get_match_thumbtup, ibs, qaid2_cm,
                                      qaids, daids, qreq_=qreq_,
                                      match_thumbtup_cache={})
        col_getter_dict[MATCH_THUMB_TEXT] = get_match_thumbtup_

    col_bgrole_dict = {
        MATCHED_STATUS_TEXT : partial(get_match_status_bgrole, ibs),
        REVIEWED_STATUS_TEXT: partial(get_reviewed_status_bgrole, ibs),
    }
    # TODO: remove ider dict.
    # it is massively unuseful
    col_ider_dict = {
        MATCHED_STATUS_TEXT     : ('qaid', 'aid'),
        REVIEWED_STATUS_TEXT    : ('qaid', 'aid'),
        'tags'    : ('qaid', 'aid'),
        QUERY_THUMB_TEXT : ('qaid'),
        RES_THUMB_TEXT : ('aid'),
        'dnGt'      : ('aid'),
        'qnGt'      : ('qaid'),
        'qname'      : ('qaid'),
        'name'       : ('aid'),
    }
    col_setter_dict = {
        'qname': ibs.set_annot_names,
        'name': ibs.set_annot_names
    }
    editable_colnames =  ['truth', 'notes', 'qname', 'name', 'opt']

    sortby = 'score'

    def get_thumb_size():
        return ibs.cfg.other_cfg.thumb_size

    col_display_role_func_dict = {
        'timedelta': ut.partial(ut.get_posix_timedelta_str, year=True, approx=2),
    }

    if not review_cfg.get('show_chips', True):
        del col_getter_dict[QUERY_THUMB_TEXT]
        del col_getter_dict[RES_THUMB_TEXT]
        del col_types_dict[RES_THUMB_TEXT]
        del col_types_dict[QUERY_THUMB_TEXT]
        del col_ider_dict[RES_THUMB_TEXT]
        del col_ider_dict[QUERY_THUMB_TEXT]
        # del col_bgrole_dict[RES_THUMB_TEXT]
        # del col_bgrole_dict[QUERY_THUMB_TEXT]

    # Insert info into dict
    review_api = gt.CustomAPI(
        col_name_list=col_name_list,
        col_types_dict=col_types_dict,
        col_getter_dict=col_getter_dict,
        col_bgrole_dict=col_bgrole_dict,
        col_ider_dict=col_ider_dict,
        col_setter_dict=col_setter_dict,
        editable_colnames=editable_colnames,
        col_display_role_func_dict=col_display_role_func_dict,
        sortby=sortby,
        get_thumb_size=get_thumb_size,
        sort_reverse=True,
        col_width_dict=col_width_dict)
    #review_api.review_edges = review_edges
    return review_api


def get_match_status(ibs, aid_pair):
    """ Data role for status column """
    aid1, aid2 = aid_pair
    assert not ut.isiterable(aid1), 'aid1=%r, aid2=%r' % (aid1, aid2)
    assert not ut.isiterable(aid2), 'aid1=%r, aid2=%r' % (aid1, aid2)
    text = ibs.get_match_text(aid1, aid2)
    if text is None:
        raise AssertionError('impossible state id_review_api')
    return text


def get_reviewed_status(ibs, aid_pair):
    """ Data role for status column """
    aid1, aid2 = aid_pair
    assert not ut.isiterable(aid1), 'aid1=%r, aid2=%r' % (aid1, aid2)
    assert not ut.isiterable(aid2), 'aid1=%r, aid2=%r' % (aid1, aid2)
    state = ibs.get_annot_pair_is_reviewed([aid1], [aid2])[0]
    state_to_text = {
        None: 'Unreviewed',
        2: 'Auto-reviewed',
        1: 'User-reviewed',
    }
    default = '??? unknown mode %r' % (state,)
    text = state_to_text.get(state, default)
    return text


def get_match_status_bgrole(ibs, aid_pair):
    """ Background role for status column """
    aid1, aid2 = aid_pair
    truth = ibs.get_match_truth(aid1, aid2)
    #print('get status bgrole: %r truth=%r' % (aid_pair, truth))
    truth_color = vh.get_truth_color(truth, base255=True, lighten_amount=0.35)
    return truth_color


def get_reviewed_status_bgrole(ibs, aid_pair):
    """ Background role for status column """
    aid1, aid2 = aid_pair
    truth = ibs.get_match_truth(aid1, aid2)
    annotmach_reviewed = ibs.get_annot_pair_is_reviewed([aid1], [aid2])[0]
    #truth = ibs.get_annot_pair_truth([aid1], [aid2])[0]
    #print('get status bgrole: %r truth=%r' % (aid_pair, truth))
    if annotmach_reviewed == 0 or annotmach_reviewed is None:
        lighten_amount = .9
    elif annotmach_reviewed == 2:
        lighten_amount = .7
    else:
        lighten_amount = .35
    truth_color = vh.get_truth_color(truth, base255=True,
                                     lighten_amount=lighten_amount)
    #truth = ibs.get_match_truth(aid1, aid2)
    #print('get status bgrole: %r truth=%r' % (aid_pair, truth))
    #truth_color = vh.get_truth_color(truth, base255=True, lighten_amount=0.35)
    return truth_color


def get_match_thumb_fname(cm, daid, qreq_):
    """
    CommandLine:
        python -m ibeis.gui.id_review_api --exec-get_match_thumb_fname

    Example:
        >>> # DISABLE_DOCTEST
        >>> from ibeis.gui.id_review_api import *  # NOQA
        >>> import ibeis
        >>> cm, qreq_ = ibeis.testdata_cm('PZ_MTEST')
        >>> thumbsize = (128, 128)
        >>> daid = cm.get_top_aids()[0]
        >>> match_thumb_fname = get_match_thumb_fname(cm, daid, qreq_)
        >>> result = match_thumb_fname
        >>> print(result)
        match_aids=1,1_cfgstr=ubpzwu5k54h6xbnr.jpg
    """
    # Make thumbnail name
    config_hash = ut.hashstr27(qreq_.get_cfgstr())
    qaid = cm.qaid
    match_thumb_fname = 'match_aids=%d,%d_cfgstr=%s.jpg' % ((qaid, daid,
                                                             config_hash))
    return match_thumb_fname


def ensure_match_img(ibs, cm, daid, qreq_=None, match_thumbtup_cache={}):
    r"""
    CommandLine:
        python -m ibeis.gui.id_review_api --test-ensure_match_img --show

    Example:
        >>> # ENABLE_DOCTEST
        >>> from ibeis.gui.id_review_api import *  # NOQA
        >>> import ibeis
        >>> # build test data
        >>> cm, qreq_ = ibeis.testdata_cm()
        >>> daid = cm.get_top_aids()[0]
        >>> match_thumbtup_cache = {}
        >>> # execute function
        >>> match_thumb_fpath_ = ensure_match_img(qreq_.ibs, cm, daid, qreq_,
        >>>                                       match_thumbtup_cache)
        >>> # verify results
        >>> result = str(match_thumb_fpath_)
        >>> print(result)
        >>> ut.quit_if_noshow()
        >>> ut.startfile(match_thumb_fpath_, quote=True)
    """
    #from os.path import exists
    match_thumbdir = ibs.get_match_thumbdir()
    match_thumb_fname = get_match_thumb_fname(cm, daid, qreq_)
    match_thumb_fpath_ = ut.unixjoin(match_thumbdir, match_thumb_fname)
    #if exists(match_thumb_fpath_):
    #    return match_thumb_fpath_
    if match_thumb_fpath_ in match_thumbtup_cache:
        fpath = match_thumbtup_cache[match_thumb_fpath_]
    else:
        # TODO: just draw the image at the correct thumbnail size
        # TODO: draw without matplotlib?
        #with ut.Timer('render-1'):
        fpath = cm.imwrite_single_annotmatch(
            qreq_, daid, fpath=match_thumb_fpath_, saveax=True, fnum=32,
            notitle=True, verbose=False)
        #with ut.Timer('render-2'):
        #    img = cm.render_single_annotmatch(qreq_, daid, fnum=32, notitle=True, dpi=30)
        #    cv2.imwrite(match_thumb_fpath_, img)
        #    fpath = match_thumb_fpath_
        #with ut.Timer('render-3'):
        #fpath = match_thumb_fpath_
        #render_config = {
        #    'dpi'              : 60,
        #    'draw_fmatches'    : True,
        #    #'vert'             : view_orientation == 'vertical',
        #    'show_aidstr'      : False,
        #    'show_name'        : False,
        #    'show_exemplar'    : False,
        #    'show_num_gt'      : False,
        #    'show_timedelta'   : False,
        #    'show_name_rank'   : False,
        #    'show_score'       : False,
        #    'show_annot_score' : False,
        #    'show_name_score'  : False,
        #    'draw_lbl'         : False,
        #    'draw_border'      : False,
        #}
        #cm.imwrite_single_annotmatch2(qreq_, daid, fpath, fnum=32, notitle=True, **render_config)
        #print('fpath = %r' % (fpath,))
        match_thumbtup_cache[match_thumb_fpath_] = fpath
    return fpath


def make_ensure_match_img_nosql_func(qreq_, cm, daid):
    r"""
    CommandLine:
        python -m ibeis.gui.id_review_api --test-ensure_match_img --show

    Example:
        >>> # ENABLE_DOCTEST
        >>> from ibeis.gui.id_review_api import *  # NOQA
        >>> import ibeis
        >>> # build test data
        >>> cm, qreq_ = ibeis.testdata_cm()
        >>> ibs = qreq_.ibs
        >>> daid = cm.get_top_aids()[0]
        >>> match_thumbtup_cache = {}
        >>> # execute function
        >>> match_thumb_fpath_ = ensure_match_img(qreq_.ibs, cm, daid, qreq_, match_thumbtup_cache)
        >>> # verify results
        >>> result = str(match_thumb_fpath_)
        >>> print(result)
        >>> ut.quit_if_noshow()
        >>> ut.startfile(match_thumb_fpath_, quote=True)
    """
    #import ibeis.viz
    from ibeis.viz import viz_matches
    import cv2
    import io
    import plottool as pt
    import vtool as vt
    import matplotlib as mpl
    aid1 = cm.qaid
    aid2 = daid

    ibs = qreq_.ibs
    resize_factor = .5

    match_thumbdir = ibs.get_match_thumbdir()
    match_thumb_fname = get_match_thumb_fname(cm, daid, qreq_)
    fpath = ut.unixjoin(match_thumbdir, match_thumb_fname)

    def main_thread_load():
        # This gets executed in the main thread and collects data
        # from sql
        rchip1_fpath, rchip2_fpath, kpts1, kpts2 = viz_matches._get_annot_pair_info(
            ibs, aid1, aid2, qreq_, draw_fmatches=True, as_fpath=True)
        return rchip1_fpath, rchip2_fpath, kpts1, kpts2

    def nosql_draw(check_func, rchip1_fpath, rchip2_fpath, kpts1, kpts2):
        # This gets executed in the child thread and does drawing async style
        #from matplotlib.backends.backend_pdf import FigureCanvasPdf as FigureCanvas
        #from matplotlib.backends.backend_pdf import Figure
        #from matplotlib.backends.backend_svg import FigureCanvas
        #from matplotlib.backends.backend_svg import Figure
        from matplotlib.backends.backend_agg import FigureCanvas
        from matplotlib.backends.backend_agg import Figure

        kpts1_ = vt.offset_kpts(kpts1, (0, 0), (resize_factor, resize_factor))
        kpts2_ = vt.offset_kpts(kpts2, (0, 0), (resize_factor, resize_factor))

        #from matplotlib.figure import Figure
        if check_func is not None and check_func():
            return

        rchip1 = vt.imread(rchip1_fpath)
        rchip1 = vt.resize_image_by_scale(rchip1, resize_factor)
        if check_func is not None and check_func():
            return
        rchip2 = vt.imread(rchip2_fpath)
        rchip2 = vt.resize_image_by_scale(rchip2, resize_factor)
        if check_func is not None and check_func():
            return

        try:
            idx = cm.daid2_idx[daid]
            fm   = cm.fm_list[idx]
            fsv  = None if cm.fsv_list is None else cm.fsv_list[idx]
            fs   = None if fsv is None else fsv.prod(axis=1)
        except KeyError:
            fm = []
            fs = None
            fsv = None

        maxnum = 200
        if fs is not None and len(fs) > maxnum:
            # HACK TO ONLY SHOW TOP MATCHES
            sortx = fs.argsort()[::-1]
            fm = fm.take(sortx[:maxnum], axis=0)
            fs = fs.take(sortx[:maxnum], axis=0)

        was_interactive = mpl.is_interactive()
        if was_interactive:
            mpl.interactive(False)
        #fnum = 32
        fig = Figure()
        canvas = FigureCanvas(fig)  # NOQA
        #fig.clf()
        ax = fig.add_subplot(1, 1, 1)
        if check_func is not None and check_func():
            return
        #fig = pt.plt.figure(fnum)
        #H1 = np.eye(3)
        #H2 = np.eye(3)
        #H1[0, 0] = .5
        #H1[1, 1] = .5
        #H2[0, 0] = .5
        #H2[1, 1] = .5
        ax, xywh1, xywh2 = pt.show_chipmatch2(rchip1, rchip2, kpts1_, kpts2_, fm,
                                              fs=fs, colorbar_=False, ax=ax)
        if check_func is not None and check_func():
            return
        savekw = {
            # 'dpi' : 60,
            'dpi' : 80,
        }
        axes_extents = pt.extract_axes_extents(fig)
        #assert len(axes_extents) == 1, 'more than one axes'
        extent = axes_extents[0]
        with io.BytesIO() as stream:
            # This call takes 23% - 15% of the time depending on settings
            fig.savefig(stream, bbox_inches=extent, **savekw)
            stream.seek(0)
            data = np.fromstring(stream.getvalue(), dtype=np.uint8)
        if check_func is not None and check_func():
            return
        pt.plt.close(fig)
        image = cv2.imdecode(data, 1)
        thumbsize = 221
        max_dsize = (thumbsize, thumbsize)
        dsize, sx, sy = vt.resized_clamped_thumb_dims(vt.get_size(image), max_dsize)
        if check_func is not None and check_func():
            return
        image = vt.resize(image, dsize)
        vt.imwrite(fpath, image)
        if check_func is not None and check_func():
            return
        #fig.savefig(fpath, bbox_inches=extent, **savekw)
    #match_thumbtup_cache[match_thumb_fpath_] = fpath
    return fpath, nosql_draw, main_thread_load


def get_photobomber_map(ibs, aids, aid_to_nid=None):
    """
    Builds map of which names that photobomb other names.

    python -m ibeis.gui.id_review_api --test-test_review_widget --show --db PZ_MTEST -a default:qindex=0

    >>> import ibeis
    >>> dbdir = ut.truepath('~/lev/media/danger/GGR/GGR-IBEIS')
    >>> ibs = ibeis.opendb(dbdir='/home/joncrall/lev/media/danger/GGR/GGR-IBEIS')
    >>> filter_kw = {
    >>>     'multiple': False,
    >>>     'minqual': 'good',
    >>>     'is_known': True,
    >>>     'min_pername': 2,
    >>>     'view': ['right'],
    >>> }
    >>> aids = ibs.filter_annots_general(ibs.get_valid_aids(), filter_kw=filter_kw)
    """
    ams_list = ibs.get_annotmatch_rowids_from_aid(aids)
    flags_list = ibs.unflat_map(ut.partial(ibs.get_annotmatch_prop, 'Photobomb'), ams_list)
    pb_ams = ut.zipcompress(ams_list, flags_list)
    has_pb_ams = [len(ams) > 0 for ams in pb_ams]
    pb_ams_ = ut.compress(pb_ams, has_pb_ams)
    #aids_ = ut.compress(aids, has_pb_ams)
    pb_ams_flat = ut.flatten(pb_ams_)

    pb_aids1_ = ibs.get_annotmatch_aid1(pb_ams_flat)
    pb_aids2_ = ibs.get_annotmatch_aid2(pb_ams_flat)

    pb_aid_pairs_ = list(zip(pb_aids1_, pb_aids2_))
    if aid_to_nid is None:
        pb_nid_pairs_ = ibs.unflat_map(ibs.get_annot_nids, pb_aid_pairs_)
    else:
        pb_nid_pairs_ = ibs.unflat_map(ut.partial(ut.take, aid_to_nid), pb_aid_pairs_)

    #invalid_aid_map = ut.ddict(set)
    #for aid1, aid2 in pb_aid_pairs_:
    #    if aid1 != aid2:
    #        invalid_aid_map[aid1].add(aid2)
    #        invalid_aid_map[aid2].add(aid1)

    invalid_nid_map = ut.ddict(set)
    for nid1, nid2 in pb_nid_pairs_:
        if nid1 != nid2:
            invalid_nid_map[nid1].add(nid2)
            invalid_nid_map[nid2].add(nid1)

    return invalid_nid_map

numpy.full numpy.array numpy.hstack numpy.vstack