# -*- coding: utf-8 -*-
"""
This model provides the declarative interface to all of the api_*_models
in guitool_ibeis. Each different type of model/view has to register its iders,
getters, and potentially setters (hopefully if guitool_ibeis ever gets off the
ground the delters as well)

Different columns can be hidden / shown by modifying this file

TODO: need to cache the total number of annotations or something about
imagesets on disk to help startuptime.
"""
from __future__ import absolute_import, division, print_function, unicode_literals
import six
from six.moves import zip, map, range
from ibeis import constants as const
import utool as ut
from functools import partial
(print, rrr, profile) = ut.inject2(__name__)

IMAGESET_TABLE  = const.IMAGESET_TABLE
IMAGE_TABLE      = const.IMAGE_TABLE
ANNOTATION_TABLE = const.ANNOTATION_TABLE
IMAGE_GRID       = 'image_grid'
NAME_TABLE       = 'names'
NAMES_TREE       = 'names_tree'
QRES_TABLE       = 'qres'
THUMB_TABLE      = 'thumbs'

#-----------------
# Define the tables
#-----------------


def make_table_declarations(ibs):
    """
    these used to be global variables, hopefully we can make them a little more
    configurable
    """
    # available tables
    TABLENAME_LIST = [
        IMAGE_TABLE,
        ANNOTATION_TABLE,
        #NAME_TABLE,
        IMAGESET_TABLE,
        IMAGE_GRID,
        THUMB_TABLE,
        NAMES_TREE
    ]

    # table nice names
    TABLE_NICE = {
        IMAGE_TABLE      : 'Image Table',
        ANNOTATION_TABLE : 'Annotations Table',
        NAME_TABLE       : 'Name Table',
        QRES_TABLE       : 'Query Results Table',
        IMAGESET_TABLE  : 'ImageSet Table',
        IMAGE_GRID       : 'Thumbnail Grid',
        THUMB_TABLE      : 'Thumbnail Table',
        NAMES_TREE       : 'Tree of Names',
    }

    # COLUMN DEFINITIONS
    # the columns each ibeis table has,
    TABLE_COLNAMES = {
        IMAGE_TABLE     : [
            'gid',
            'thumb',
            #'nAids',
            'img_gname',
            #'ext',
            'reviewed',  # detection reviewed flag is not fullyused
            'datetime',
            'gps',
            'orientation',
            'party_tag',
            'contributor_tag',
            #'gdconf',
            'imgnotes',
            'image_uuid',
        ],

        # debug with
        # --noannottbl
        # --nonametree
        # even just aid seems to be very slow
        ANNOTATION_TABLE       : [
            #'annotation_uuid',
            'aid',
            'thumb',
            'annot_gname',
            'name',
            'exemplar',
            'species',  # <put back in
            'viewpoint',
            'quality_text',
            'age_min',
            'age_max',
            'sex_text',
            #'rdconf',
            #'nGt',  # ## <put back in
            'imagesettext_names',
            'annotnotes',  # ## <put back in
            'tag_text',  # < Hack should have actual tag structure
            #'annot_visual_uuid',
            #'nFeats',
            #'bbox',
            #'theta',
            #'verts',
            #'num_verts',
        ],

        NAME_TABLE      : [
            'nid',
            'name',
            'nAids',
            'namenotes'
        ],

        QRES_TABLE      : [
            'rank',
            'score',
            'name',
            'aid'
        ],

        IMAGESET_TABLE : [
            'imagesettext',
            'nImgs',
            #'num_imgs_reviewed',
            #'num_annotmatch_reviewed',
            #'imageset_end_datetime',
            # 'imageset_processed_flag',
            # 'imageset_shipped_flag',
            'imgsetid',
        ],

        NAMES_TREE      : [
            'name',
            'nAids',
            'thumb',
            'nid',
            # 'exemplar',
            # 'nExAids',
            'aid',
            # 'annot_gname',
            # 'quality_text',
            # 'age_min',
            # 'age_max',
            # 'sex_text',
            # 'imagesettext_names',
            # 'datetime',
            # 'max_hourdiff',
            # 'max_speed',
            # 'has_split',
            # 'namenotes',
        ],

        IMAGE_GRID     : [
            'thumb',
        ],

        # TEST TABLE
        THUMB_TABLE     : [
            'img_gname',
            'thumb',
        ],


    }

    # dynamicly defined headers
    if not const.SIMPLIFY_INTERFACE:
        from ibeis.control import accessor_decors
        if accessor_decors.API_CACHE:
            # Too slow without api cache
            TABLE_COLNAMES[IMAGESET_TABLE].extend([
                'percent_annotmatch_reviewed_str',
                'percent_names_with_exemplar_str',
            ])
        TABLE_COLNAMES[IMAGESET_TABLE].extend([
            #'percent_imgs_reviewed_str',
            'imageset_start_datetime',
            # 'imageset_end_datetime',
            'imageset_duration',
            'imageset_notes',
        ])

    if ibs.cfg.other_cfg.show_shipped_imagesets:
        TABLE_COLNAMES[IMAGESET_TABLE].extend([
            'imageset_processed_flag',
            'imageset_shipped_flag',
        ])

    #THUMB_TABLE     : ['thumb' 'thumb' 'thumb' 'thumb'],
    #NAMES_TREE      : {('name' 'nid' 'nAids') : ['aid' 'bbox' 'thumb']}

    TABLE_TREE_LEVELS = {
        NAMES_TREE :
        {
            'name': 0,
            'namenotes': 0,
            'nid': 0,
            'nAids': 0,
            'nExAids': 0,
            'sex_text': 0,
            'exemplar': 1,
            'thumb': 1,
            'viewpoint': 1,
            'quality_text': 1,
            'age_min': 1,
            'age_max': 1,
            'imagesettext_names': 1,
            'aid': 1,
            'annot_gname': 1,
            'datetime': 1,
            'max_hourdiff': 0,
            'max_speed': 0,
            'has_split': 0,
        },
    }

    # the columns which are editable
    TABLE_EDITSET = {
        IMAGE_TABLE      : set(['reviewed', 'imgnotes', 'gps']),
        ANNOTATION_TABLE : set(['name', 'species', 'annotnotes', 'exemplar', 'viewpoint', 'quality_text', 'age_min', 'age_max', 'sex_text', 'tag_text']),
        NAME_TABLE       : set(['name', 'namenotes']),
        QRES_TABLE       : set(['name']),
        IMAGESET_TABLE   : set(['imagesettext', 'imageset_shipped_flag', 'imageset_processed_flag']),
        IMAGE_GRID       : set([]),
        THUMB_TABLE      : set([]),
        NAMES_TREE       : set(['exemplar', 'name', 'namenotes', 'viewpoint', 'quality_text', 'age_min', 'age_max', 'sex_text']),
    }

    if const.SIMPLIFY_INTERFACE:
        TABLE_EDITSET[NAMES_TREE].remove('name')

    TABLE_HIDDEN_LIST = {
        #IMAGE_TABLE      : [False, True, False, False, False, True, False, False, False, False, False],
        #ANNOTATION_TABLE : [False, False, False, False, False, False, False, True, True, True, True, True, True],
        #NAMES_TREE       : [False, False, False, False, False, False],
        #NAME_TABLE       : [False, False, False, False],
    }

    TABLE_STRIPE_LIST = {
        IMAGE_GRID : 9,
    }

    # Define the valid columns a table could have
    COL_DEF = dict([
        ('annot_visual_uuid',               (str,      'Annot Visual UUID')),
        ('image_uuid',                      (str,      'Image UUID')),
        ('gid',                             (int,      'Image ID')),
        ('aid',                             (int,      'Annotation ID')),
        ('nid',                             (int,      'Name ID')),
        ('imgsetid',                        (int,      'ImageSet ID')),
        ('nAids',                           (int,      '#Annots')),
        ('nExAids',                         (int,      '#Exemplars')),
        ('nGt',                             (int,      '#GT')),
        ('nImgs',                           (int,      '#Imgs')),
        ('nFeats',                          (int,      '#Features')),
        ('quality_text',                    (str,      'Quality')),
        ('imagesettext_names',              (str,      'ImageSet Names')),
        ('age_min',                         (int,      'Age (min)')),
        ('age_max',                         (int,      'Age (max)')),
        ('sex_text',                        (str,      'Sex')),
        ('rank',                            (str,      'Rank')),  # needs to be a string for !Query
        ('unixtime',                        (float,    'unixtime')),
        ('species',                         (str,      'Species')),
        ('viewpoint',                       (str,      'Viewpoint')),
        ('img_gname',                       (str,      'Image Name')),
        ('annot_gname',                     (str,      'Source Image')),
        ('gdconf',                          (str,      'Detection Confidence')),
        ('rdconf',                          (float,    'Detection Confidence')),
        ('name',                            (str,      'Name')),
        ('annotnotes',                      (str,      'Annot Notes')),
        ('namenotes',                       (str,      'Name Notes')),
        ('imgnotes',                        (str,      'Image Notes')),
        ('match_name',                      (str,      'Matching Name')),
        ('bbox',                            (str,      'BBOX (x, y, w, h))')),  # Non editables are safe as strs
        ('num_verts',                       (int,      'NumVerts')),
        ('verts',                           (str,      'Verts')),
        ('score',                           (str,      'Confidence')),
        ('theta',                           (str,      'Theta')),
        ('reviewed',                        (bool,     'Detection Reviewed')),
        ('exemplar',                        (bool,     'Is Exemplar')),
        ('imagesettext',                    (str,      'ImageSet')),
        ('datetime',                        (str,      'Date / Time')),
        ('ext',                             (str,      'EXT')),
        ('thumb',                           ('PIXMAP', 'Thumb')),
        ('gps',                             (str,      'GPS')),
        ('orientation',                     (str,      'Orientation')),
        ('imageset_processed_flag',         (bool,     'Processed')),
        ('imageset_shipped_flag',           (bool,     'Commited')),
        ('imageset_start_datetime',         (str,      'Start Time')),
        ('imageset_end_datetime',           (str,      'End Time')),
        ('imageset_duration',               (str,      'Duration')),
        ('imageset_notes',                  (str,      'Notes')),
        ('party_tag',                       (str,      'Party')),
        ('contributor_tag',                 (str,      'Contributor')),
        ('percent_imgs_reviewed_str',       (str,      '%Imgs Reviewed')),
        ('percent_annotmatch_reviewed_str', (str,      '%Queried')),
        ('num_imgs_reviewed',               (str,      '#Imgs Reviewed')),
        ('num_annotmatch_reviewed',         (str,      '#Matches Reviewed')),
        ('percent_names_with_exemplar_str', (str,      '%Names with Exemplar')),
        ('max_speed',                       (float,    'Max Speed km/h')),
        ('has_split',                       (float,    'Needs Split')),
        ('max_hourdiff',                    (float,    'Max Hour Diff')),
        ('tag_text',                        (str,      'Tags')),
    ])

    declare_tup = (TABLENAME_LIST, TABLE_NICE, TABLE_COLNAMES,
                   TABLE_TREE_LEVELS, TABLE_EDITSET, TABLE_HIDDEN_LIST,
                   TABLE_STRIPE_LIST, COL_DEF)
    return declare_tup

#----


def partial_imap_1to1(func, si_func):
    import functools
    @functools.wraps(si_func)
    def wrapper(input_):
        if not ut.isiterable(input_):
            return func(si_func(input_))
        else:
            return list(map(func, si_func(input_)))
    ut.set_funcname(wrapper, ut.get_callable_name(func) + '_mapper_' +
                    ut.get_funcname(si_func))
    return wrapper


def _tupstr(tuple_):
    """ maps each item in tuple to a string and doesnt include parens """
    return ', '.join(list(map(six.text_type, tuple_)))


def make_ibeis_headers_dict(ibs):
    declare_tup = make_table_declarations(ibs)
    (TABLENAME_LIST, TABLE_NICE, TABLE_COLNAMES,
     TABLE_TREE_LEVELS, TABLE_EDITSET, TABLE_HIDDEN_LIST,
     TABLE_STRIPE_LIST, COL_DEF) = declare_tup

    #
    # Table Iders/Setters/Getters
    iders = {}
    setters = {}
    getters = {}
    widths = {}
    def infer_unspecified_getters(tablename, shortname):
        for colname in TABLE_COLNAMES[tablename]:
            if colname not in getters[tablename]:
                if ut.VERBOSE:
                    print('[guiheaders] infering getter for tablename=%r, colname=%r' % (
                        tablename, colname,))
                    #print('[guiheaders] infering %r' % (getters[tablename][colname],))
                try:
                    getters[tablename][colname] = getattr(
                        ibs, 'get_' + shortname + '_' + colname)
                except AttributeError:
                    # we have inconsistently put in column names
                    # try to "just make things work"
                    getters[tablename][colname] = getattr(ibs, 'get_' + colname)
    # +--------------------------
    # ImageSet Iders/Setters/Getters
    SHOW_SHIPPED_IMAGESETS = ibs.cfg.other_cfg.show_shipped_imagesets
    #SHOW_SHIPPED_IMAGESETS = True
    if SHOW_SHIPPED_IMAGESETS:
        iders[IMAGESET_TABLE]   = [ibs.get_valid_imgsetids]
    else:
        iders[IMAGESET_TABLE]   = [ partial(ibs.get_valid_imgsetids, shipped=False)]

    getters[IMAGESET_TABLE] = {
        'imgsetid'        : lambda imgsetids: imgsetids,
        'nImgs'      : ibs.get_imageset_num_gids,
        'imagesettext'    : ibs.get_imageset_text,
        'imageset_shipped_flag'     : ibs.get_imageset_shipped_flags,
        'imageset_processed_flag'   : ibs.get_imageset_processed_flags,
        #
        'imageset_start_datetime'   : partial_imap_1to1(ut.unixtime_to_datetimestr, ibs.get_imageset_start_time_posix),
        'imageset_end_datetime'     : partial_imap_1to1(ut.unixtime_to_datetimestr, ibs.get_imageset_end_time_posix),
        #
        'imageset_start_time_posix' : ibs.get_imageset_start_time_posix,
        'imageset_end_time_posix'   : ibs.get_imageset_end_time_posix,
        'imageset_duration'         : ibs.get_imageset_duration,
        'imageset_notes'            : ibs.get_imageset_note,
    }
    infer_unspecified_getters(IMAGESET_TABLE, 'imageset')
    setters[IMAGESET_TABLE] = {
        'imagesettext'    : ibs.set_imageset_text,
        'imageset_shipped_flag'    : ibs.set_imageset_shipped_flags,
        'imageset_processed_flag'  : ibs.set_imageset_processed_flags,
    }
    widths[IMAGESET_TABLE] = {
        'nImgs': 55,
    }
    # +--------------------------
    # Image Iders/Setters/Getters
    iders[IMAGE_TABLE]   = [ibs.get_valid_gids]
    getters[IMAGE_TABLE] = {
        'gid'          : ut.identity,
        'imgsetid'     : ibs.get_image_imgsetids,
        'imagesettext' : partial_imap_1to1(_tupstr, ibs.get_image_imagesettext),
        'reviewed'     : ibs.get_image_reviewed,
        'img_gname'    : ibs.get_image_gnames,
        'nAids'        : ibs.get_image_num_annotations,
        'unixtime'     : ibs.get_image_unixtime,
        'datetime'     : ibs.get_image_datetime_str,
        'gdconf'       : ibs.get_image_detect_confidence,
        'imgnotes'     : ibs.get_image_notes,
        'image_uuid'   : ibs.get_image_uuids,
        'ext'          : ibs.get_image_exts,
        'thumb'        : ibs.get_image_thumbtup,
        'gps'          : partial_imap_1to1(_tupstr, ibs.get_image_gps),
        'orientation'  : ibs.get_image_orientation_str,
    }
    infer_unspecified_getters(IMAGE_TABLE, 'image')
    setters[IMAGE_TABLE] = {
        'reviewed'      : ibs.set_image_reviewed,
        'imgnotes'      : ibs.set_image_notes,
        'gps'           : ibs.set_image_gps_str,
    }
    # +--------------------------
    # IMAGE GRID
    iders[IMAGE_GRID]   = [ibs.get_valid_gids]
    getters[IMAGE_GRID] = {
        'thumb'      : ibs.get_image_thumbtup,
        'img_gname'  : ibs.get_image_gnames,
        'aid'        : ibs.get_image_aids,
    }
    setters[IMAGE_GRID] = {
    }
    # +--------------------------
    # ANNOTATION Iders/Setters/Getters
    iders[ANNOTATION_TABLE]   = [ibs.get_valid_aids]
    getters[ANNOTATION_TABLE] = {
        'aid'                 : ut.identity,
        'name'                : ibs.get_annot_names,
        'species'             : ibs.get_annot_species_texts,
        'viewpoint'           : ibs.get_annot_viewpoints,
        'quality_text'        : ibs.get_annot_quality_texts,
        'imagesettext_names'  : ibs.get_annot_image_set_texts,
        'age_min'             : ibs.get_annot_age_months_est_min,
        'age_max'             : ibs.get_annot_age_months_est_max,
        'sex_text'            : ibs.get_annot_sex_texts,
        'annot_gname'         : ibs.get_annot_image_names,
        'nGt'                 : ibs.get_annot_num_groundtruth,
        'theta'               : partial_imap_1to1(ut.theta_str, ibs.get_annot_thetas),
        'bbox'                : partial_imap_1to1(ut.bbox_str,  ibs.get_annot_bboxes),
        'num_verts'           : ibs.get_annot_num_verts,
        'verts'               : partial_imap_1to1(ut.verts_str, ibs.get_annot_verts),
        'nFeats'              : ibs.get_annot_num_feats,
        'rdconf'              : ibs.get_annot_detect_confidence,
        'annotnotes'          : ibs.get_annot_notes,
        'thumb'               : ibs.get_annot_chip_thumbtup,
        'exemplar'            : ibs.get_annot_exemplar_flags,
        'annot_visual_uuid'   : ibs.get_annot_visual_uuids,
        'datetime'            : ibs.get_annot_image_datetime_str,
    }
    infer_unspecified_getters(ANNOTATION_TABLE, 'annot')
    setters[ANNOTATION_TABLE] = {
        'name'       : ibs.set_annot_names,
        'species'    : ibs.set_annot_species_and_notify,
        'viewpoint'  : ibs.set_annot_viewpoints,
        'age_min'    : ibs.set_annot_age_months_est_min,
        'age_max'    : ibs.set_annot_age_months_est_max,
        'sex_text'   : ibs.set_annot_sex_texts,
        'annotnotes' : ibs.set_annot_notes,
        'exemplar'   : ibs.set_annot_exemplar_flags,
        'quality_text'    : ibs.set_annot_quality_texts,
        'tag_text': ibs.set_annot_tag_text,
    }
    # +--------------------------
    # Name Iders/Setters/Getters
    iders[NAME_TABLE]   = [ibs.get_valid_nids]
    getters[NAME_TABLE] = {
        'nid'        : ut.identity,
        'name'       : ibs.get_name_texts,
        'nAids'      : ibs.get_name_num_annotations,
        'namenotes'  : ibs.get_name_notes,
        #'has_split'  : ibs.get_name_has_split,
    }
    setters[NAME_TABLE] = {
        'name'       : ibs.set_name_texts,
        'namenotes'  : ibs.set_name_notes,
    }
    # +--------------------------
    # NAMES TREE
    iders[NAMES_TREE]   = [ibs.get_valid_nids, ibs.get_name_aids]
    getters[NAMES_TREE] = {
        # level 0
        'nid'          : ut.identity,
        'name'         : ibs.get_name_texts,
        'nAids'        : ibs.get_name_num_annotations,
        'nExAids'      : ibs.get_name_num_exemplar_annotations,
        'namenotes'    : ibs.get_name_notes,
        'sex_text'     : ibs.get_name_sex_text,
        # level 1
        'aid'          : ut.identity,
        'exemplar'     : ibs.get_annot_exemplar_flags,
        'thumb'        : ibs.get_annot_chip_thumbtup,
        'annot_gname'  : ibs.get_annot_image_names,
        'age_min'      : ibs.get_annot_age_months_est_min,
        'age_max'      : ibs.get_annot_age_months_est_max,
        'imagesettext_names' : ibs.get_annot_image_set_texts,
        'viewpoint'    : getters[ANNOTATION_TABLE]['viewpoint'],
        'quality_text' : getters[ANNOTATION_TABLE]['quality_text'],
        'datetime'     : getters[ANNOTATION_TABLE]['datetime'],
    }
    setters[NAMES_TREE] = {
        'name'         : ibs.set_name_texts,
        'namenotes'    : ibs.set_name_notes,
        'sex_text'     : ibs.set_name_sex_text,
        'age_min'      : ibs.set_annot_age_months_est_min,
        'age_max'      : ibs.set_annot_age_months_est_max,
        'exemplar'     : setters[ANNOTATION_TABLE]['exemplar'],
        'viewpoint'    : setters[ANNOTATION_TABLE]['viewpoint'],
        'quality_text' : setters[ANNOTATION_TABLE]['quality_text'],
    }
    widths[NAMES_TREE] = {
        'thumb' : lambda: ibs.cfg.other_cfg.thumb_size,
        'nAids': 65,
        'nid': 50,
    }
    infer_unspecified_getters(NAMES_TREE, 'name')
    # +--------------------------
    # THUMB TABLE
    iders[THUMB_TABLE]   = [ibs.get_valid_gids]
    getters[THUMB_TABLE] = {
        'thumb'      : ibs.get_image_thumbtup,
        'img_gname'  : ibs.get_image_gnames,
        'aid'        : ibs.get_image_aids,
    }
    setters[THUMB_TABLE] = {
    }
    # L________________________

    def make_header(tblname):
        """
        Args:
            table_name - the internal table name
        """
        tblnice    = TABLE_NICE[tblname]
        colnames   = TABLE_COLNAMES[tblname]
        editset    = TABLE_EDITSET[tblname]
        tblgetters = getters[tblname]
        tblsetters = setters[tblname]
        #if levels aren't found, we're not dealing with a tree, so everything is at level 0
        collevel_dict = TABLE_TREE_LEVELS.get(tblname, ut.ddict(lambda: 0))
        collevels  = [collevel_dict[colname] for colname in colnames]
        hiddencols = TABLE_HIDDEN_LIST.get(tblname, [False for _ in range(len(colnames))])
        numstripes = TABLE_STRIPE_LIST.get(tblname, 1)

        colwidths_dict = widths.get(tblname, {})
        colwidths = [colwidths_dict.get(colname, 100) for colname in colnames]

        def get_column_data(colname):
            try:
                coldef_tup = COL_DEF[colname]
                coltype, colnice = coldef_tup
            except KeyError as ex:
                strict = False
                ut.printex(ex, 'Need to add type info for colname=%r to COL_DEF'
                           % colname, iswarning=not strict)
                if strict:
                    raise
                else:
                    # default coldef to give a string type and nice=colname
                    coltype, colnice = (str, colname)
            coledit   = colname in editset
            colgetter = tblgetters[colname]
            colsetter = None if not coledit else tblsetters.get(colname, None)
            return (coltype, colnice, coledit, colgetter, colsetter)
        try:
            _tuplist = list(zip(*list(map(get_column_data, colnames))))
            (coltypes, colnices, coledits, colgetters, colsetters) = _tuplist
        except KeyError as ex:
            ut.printex(ex,  key_list=['tblname', 'colnames'])
            raise
        header = {
            'name'            : tblname,
            'nice'            : tblnice,
            'iders'           : iders[tblname],
            'col_name_list'   : colnames,
            'col_type_list'   : coltypes,
            'col_nice_list'   : colnices,
            'col_edit_list'   : coledits,
            'col_getter_list' : colgetters,
            'col_setter_list' : colsetters,
            'col_level_list'  : collevels,
            'col_hidden_list' : hiddencols,
            'num_duplicates'  : numstripes,
            'get_thumb_size'  : lambda: ibs.cfg.other_cfg.thumb_size,
            'col_width_list'  : colwidths,  # TODO
        }
        return header

    header_dict = {tblname: make_header(tblname) for tblname in TABLENAME_LIST}
    return header_dict, declare_tup
