# c(c) cavaliba.com - data - dataview.py

import re

import yaml
from app_data.data import Instance
from app_data.models import DataEAV
from django.utils.translation import gettext as _


# ---------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------
def get_dataview_dict(schemaname):
    '''
    get available dataviews for schema
    use DataEAV cache

    IN:  schema name (string
    OUT: dict { keyname:displayname , ... }
    '''
    reply = {}

    eav_entries = DataEAV.objects\
        .filter(classname="_dataview", fieldname="target_class", value=schemaname)\
        .values("keyname", "displayname")

    # <QuerySet [{'keyname': 'aaaaa', 'displayname': 'bbbb'},  ... ]

    for adict in eav_entries:
        try:
            k = adict['keyname']
            v = adict['displayname']
            reply[k] = v
        except Exception:
            pass

    return reply


def get_dataview_content_by_name(dataview_name):

    reply = None

    instance = Instance.from_keyname(classname="_dataview", keyname=dataview_name)
    if not instance:
        return

    content = {}
    try:
        content = instance.fields["content"].value[0]
        reply = yaml.safe_load(content)
    except Exception as e:
        print(f"ERR - can't access dataview content ({dataview_name}): {e}")
        return

    return reply



def dataview_for_request(request=None, classname=None, update_session=False):
    ''' 
    get requested dataview (request, session, default)
    provide dataview object
    update session

    return: dataview or None
    '''
    if not classname:
        return

    if not request:
        return

    # DATAVIEW
    # --------
    # Transform to a DATAVIEW structure (subset/superset of columns, widgets, ...)
    # 1. get available dataviews (default, global, per user)
    # 2. get requested dataview (or default dataview)
    # 3. loop over instances / extract columns / build structure


    # 1. Get Dataview :  request > session > single > CLASS_default > DEFAULT built-in
    dataview_name = None
    dv_found = False
    dataview_default = classname + "_default"

    # available dataviews : dict of keyname=>Instance()
    #dataview_selector = get_dataview_dict(target_class = classname)
    dataview_selector = get_dataview_dict(classname)


    #  request
    dataview_name = request.GET.get("dv", None)
    if not dataview_name:
        dataview_name = request.POST.get("dv",None)

    if dataview_name:
        if dataview_name == dataview_default:
            dv_found = True
        else:
            m = re.compile(r'[a-zA-Z0-9()_/.-]*$')
            if m.match(dataview_name):
                if dataview_name in dataview_selector:
                    dv_found = True

    #  in session ?
    if not dv_found:
        try:
            dataview_name = request.session["dataviews"][classname]
            if dataview_name:
                m = re.compile(r'[a-zA-Z0-9()_/.-]*$')
                if m.match(dataview_name):
                    if dataview_name in dataview_selector:
                        dv_found = True
        except Exception:
            pass

    # single entry in selector ?
    if not dv_found:
        if len(dataview_selector) == 1:
            #dataview_name = dataview_selector[0]
            dataview_name = next(iter(dataview_selector))
            dv_found = True

    # CLASSNAME_default
    if not dv_found:
        if dataview_default in dataview_selector:
            dataview_name = dataview_default
            dv_found = True

    #  get or built-in default
    if dv_found:
        dataview = DataView(keyname = dataview_name)
    else:
        dataview = DataView()

    if dataview.target_class != classname:
        # wrong class, use a default view
        dataview = DataView()


    #  store dataview in user session
    if update_session:
        if dataview_name:
            if "dataviews" not in request.session:
                request.session["dataviews"] = {classname: dataview_name}
                #request.session.modified = True
            else:
                request.session["dataviews"][classname] = dataview_name
                request.session.modified = True


    # store request specifics in dataview object
    if dataview_default not in dataview_selector:
        dataview.default = dataview_default
    #dataview.selector = {}
    dataview.selector = dataview_selector
    # for k,v in dataview_selector.items():
    #     #dataview.selector[k] = v.displayname
    #     dataview.selector[k] = v

    return dataview

# -------------------------------------------
# DataView CLASS
# -------------------------------------------

class DataView:

    def __init__(self, keyname=None):

        self.iobj = None
        self.target_class = None
        self.keyname = keyname
        self.displayname = _("Default View")
        self.is_enabled = True
        self.content = {'columns':['keyname', 'displayname','last_update']}
        self.columns = ['keyname', 'displayname','last_update']

        # request specifics
        self.default = None               # name of default if not in selector
        self.selector = None              # list of available dataviews for target_class

        if not keyname:
            return

        # query _dataview Schema for keyname
        instance = Instance.from_keyname(classname="_dataview", keyname=keyname)
        if not instance:
            return
        if not instance.is_bound:
            return


        self.iobj = instance.iobj
        try:
            self.target_class = instance.fields["target_class"].value[0]
        except Exception:
            self.target_class = None
        self.displayname = instance.displayname
        self.is_enabled = instance.is_enabled
        try:
            raw = instance.fields["content"].value[0]
        except Exception:
            raw = ""

        try:
            self.content = yaml.safe_load(raw)
        except Exception as e:
            print(f"ERR - can't parse dataview content ({keyname}): {e}")
            return

        # Extract columns
        # columns:
        #   - nice name:
        #       from: keyname
        #   - col1
        #    - site:
        #        from: mysitename_col2
        #
        yaml_columns = self.content.get("columns", None)
        computed_columns = []
        if yaml_columns:
            for head in yaml_columns:
                if type(head) is str:
                    computed_columns.append(head)
                elif type(head) is dict:
                    for col, nouse in head.items():
                        computed_columns.append(col)
        self.columns = computed_columns

        # NEXT: if no keyname/displayname , add keyname ?


    def get_columns(self):

        reply = []
        raw = self.content.get("columns")
        for entry in raw:
            if type(entry) is str:
                reply.append(entry)
            elif type(entry) is dict:
                reply.append( list(entry.keys())[0] )
            else:
                reply.append("")
        return reply





    def filter(self, instance=None, mode='datapoint', value_only=False):
        '''
        
        IN: 
          * dataview() object, instance() object
          * mode: datapoint|value|csv

        OUT: 
          * array ["val1","val2", {}, {},{} ...] 
            
        values:
            * string if keyname/displayname/...
            * mode=datapoint : datapoint dict {} 
            * mode=value
                {'fieldname': 'mystring', 
                'displayname': 'MyString', 
                'description': 'A string', 
                'dataformat': 'string', 
                'dataformat_ext': None, 
                'is_multi': False, 
                'bigset': False, 
                'value':  'data for test1-01'  
                        {} for more complex fields like enumerate, user
                }
            * mode=csv : value or val1^val2^val3 ... if multi
        '''

        if not instance:
            return

        data = []

        # get dataview requested columns
        columns_array = self.content.get("columns")
        if not columns_array:
            return

        for head in columns_array:

            # no operator, no rename, direct column name
            if type(head) is str:

                if head == 'keyname':
                    data.append(instance.keyname)
                elif head == 'displayname':
                    data.append(instance.displayname)
                elif head == 'is_enabled':
                    if instance.is_enabled:
                        data.append('X')
                    else:
                        data.append('')
                elif head == 'last_update':
                    data.append(instance.last_update)
                else:
                    # existing column
                    if head in instance.fields:
                        cell = ''
                        if mode == 'value':
                            cell = instance.fields[head].get_value()
                        elif mode == 'datapoint':
                            cell = instance.fields[head].get_datapoint_ui_detail()
                        elif mode == 'csv':
                            cell = instance.fields[head].get_csv_cell()
                        data.append(cell)

                    # computed column
                    else:
                        # Strange : non-existent column but no compute primitive ; add anyway (empty)
                        data.append('')

            # operator, rename, ...
            # - newcol:              <= head is dict (col==head)
            #     from: old_col      <= operator_line = operator: operator_value

            elif type(head) is dict:

                cell = ''

                #  apply all operators
                for col, operator_line in head.items():

                    if operator_line:

                        for operator, operator_value in operator_line.items():

                            if operator == "from":

                                if operator_value in instance.fields:
                                    if mode=='value':
                                        cell = instance.fields[operator_value].get_value()
                                    elif mode=='datapoint':
                                        cell = instance.fields[operator_value].get_datapoint_ui_detail()
                                    elif mode=='csv':
                                        cell = instance.fields[operator_value].get_csv_cell()
                                elif operator_value == "displayname":
                                    cell = instance.displayname

                                elif operator_value == "keyname":
                                    cell = instance.keyname

                                elif operator_value == "last_update":
                                    cell = instance.last_update


                data.append(cell)

            else:
                data.append('')

        return data


    def print(self):
        print("_dataview")
        print(f"    keyname:        {self.keyname}")
        print(f"    iobj:           {self.iobj}")
        print(f"    target_class:   {self.target_class}")
        print(f"    is_enabled:     {self.is_enabled}")
        print(f"    displayname:    {self.displayname}")
        print(f"    columns:        {self.columns}")
        print(f"    content:        {self.content}")



