"""Instantly find and access all your Nextcloud data files"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../notebooks/10_exploring-your-remote-data-in-breeze.ipynb.

# %% auto 0
__all__ = ['to_iframe', 'DataViewer']

# %% ../notebooks/10_exploring-your-remote-data-in-breeze.ipynb 17
import panel as pn
import param
import pandas as pd
import nc_py_api
import re
import os
from panel.viewable import Viewer
from humanize import naturalsize
from pathlib import Path
import time 
import ipynb_path 
from IPython.display import Markdown

pn.extension("tabulator")

# %% ../notebooks/10_exploring-your-remote-data-in-breeze.ipynb 18
def to_iframe(obj, html_file, height='500px'): 
    '''Save panel-like object  as full HTML page `html_file` in *_iframes* subfolder. 

    Returns: `iframe` 
    
    In this way it should be possible to preserve rich interactive visualizations directly in web pages.

    See: https://panel.holoviz.org/reference/panes/HTML.html#html-documents  
    '''
    
    # Create iframes subfolder 
    notebooks_dir = os.path.dirname(ipynb_path.get())
    iframes_dir = os.path.join(notebooks_dir, '_iframes')
    os.makedirs(iframes_dir, exist_ok=True)
    
    # Save the plot as html file to iframes subfolder 
    html_path = os.path.join(iframes_dir, html_file)
    
    pn.panel(obj).save(html_path)



class DataViewer(Viewer):
    
    # DataFrames
    data = param.DataFrame()
    filtered_data = param.DataFrame() 
    
    # list filters
    columns = param.ListSelector(default=["path", "size", "modified"])
    extensions = param.ListSelector(default=[])
    
    # typed filters
    search = param.String(default="")
    show_directories = param.Boolean(default=False)
    show_filters = param.Boolean(default=False)
    use_regex = param.Boolean(default=False)
    
    # param attributes 
    bytes_amount = param.Integer()
    
    #non param attributes
    nc_py_api.options.NPA_NC_CERT = False 

    def __init__(self, configuration, subdir=None, **params):
        'Initalize a DataViewer instance.'
        
        super().__init__(**params)
        self.nc = self._create_connector(configuration)
        # load all param attributes where necessary
        self.data = self._load_dataframe(subdir)
        self.param.columns.objects = self.data.columns.to_list()
        self.param.extensions.objects = sorted(self.data['ext'].unique())

    def _create_connector(self, configuration):
        # parse configuration 
        m = re.match('(^https://[^/]+/)(.*)', configuration['url'])
        nextcloud_url, self.cache_dir = m.groups()
        nc_auth_user = configuration['user']
        nc_auth_pass = configuration['password']
        
        return nc_py_api.Nextcloud(nextcloud_url=nextcloud_url,
                                   nc_auth_user=nc_auth_user,
                                   nc_auth_pass=nc_auth_pass)

    @pn.cache()
    def _load_dataframe(self, subdir):
        if subdir is None:
            subdir = self.cache_dir

        fsnodes = self.nc.files.listdir(subdir, depth=-1, exclude_self=False)

        data = [{'path': fsnode.user_path,
                'size': naturalsize(fsnode.info.size, True),
                'ext': os.path.splitext(fsnode.user_path)[1].lower(),
                'byte_size': fsnode.info.size,
                'modified': fsnode.info.last_modified,
                'isdir': fsnode.is_dir
                } for fsnode in fsnodes]
        
        return pd.DataFrame(data)
       
    def download_selected(self, cache_dir=None):
        '''Download selected files (blue rows) from `table` to default local cache directory. 
        
        A custom `cache_dir` can be specified. '''
        
        # create cache path 
        if cache_dir is None: 
            cache_path = Path.home().joinpath('.cache', 'fairdatanow')
        else: 
            cache_path = Path.home().joinpath('.cache', cache_dir)
    
        os.makedirs(cache_path, exist_ok=True)

        remote_data = self.data.iloc[self._file_table.selected_dataframe.index.tolist()]

        # obtain remote paths and remote timestamps 
        local_path_list = []
       
        for i, [remote_path, remote_modified, remote_isdir] in enumerate(zip(remote_data['path'].tolist(), remote_data['modified'].tolist(), remote_data['isdir'].tolist())): 
    
            # only download actual files 
            if not remote_isdir:   
                remote_directory = os.path.dirname(remote_path)
                local_directory = cache_path.joinpath(remote_directory) # I guess this will not yet work for Windows
                
                # create directory structure inside cache 
                os.makedirs(local_directory, exist_ok=True) 
            
                # get remote epoch time  
                remote_modified_epoch_time = remote_modified.timestamp()
            
                # construct corresponding local path 
                local_path = cache_path.joinpath(remote_path) 
                local_path_list.append(str(local_path))
                
            
                # check if local file exists and if modification times are similar 
                is_local = local_path.exists()  
            
                is_similar = False 
                local_modified_epoch_time = None 
                if is_local: 
                    local_modified_epoch_time = os.stat(local_path).st_mtime
                    if local_modified_epoch_time == remote_modified_epoch_time: 
                        is_similar = True 
                        
                # download from nextcloud 
                if not is_similar: 
                    print(f'[{i+1}/{len(remote_data)}] Timestamps do no match: {remote_modified_epoch_time} vs {local_modified_epoch_time}', end='\r')
                    print(f'[{i+1}/{len(remote_data)}] Downloading to: {local_path}                                                       ' , end='\r')
                      
                    # write to cache 
                    with open(local_path, 'bw') as fh: 
                        self.nc.files.download2stream(remote_path, fh) 
                        
                    # adjust last modified timestamp 
                    now = int(time.time())
                    os.utime(local_path, (now, remote_modified_epoch_time)) 
                    
        print(f"Ready with downloading {len(remote_data)} selected remote files to local cache: {cache_path}                                                                      ")

        return local_path_list
    
    @param.depends("data", "columns", "search", "extensions", "show_directories", "use_regex", watch=True)
    def _update_filtered_data(self):
        # set the base df for readability and a non-watched variable
        df = self.data
        # search the dataframe in column path if it contains the search string
        if self.use_regex: 
            df = df[df['path'].str.match(self.search)]    
        else: 
            df = df[df["path"].str.lower().str.contains(self.search.lower())]
        # filter to only include the extensions that are selected
        if self.extensions:
            df = df[df["ext"].isin(self.extensions)]
        # save the total bytes_size in the bytes_amount variable
        self.bytes_amount = df[df["isdir"] == False]["byte_size"].sum()
        # if show_directories is turned off exclude them from the df
        if not self.show_directories:
            df = df[df["isdir"] == False]
        # only select the columns from the column selector
        self.filtered_data = df[self.columns]
    
    @param.depends("filtered_data")
    def number_of_rows(self):
        return f"Showing {len(self.filtered_data)} out of {len(self.data)} rows | Total size: {naturalsize(self.bytes_amount, True)}"

    def export_filters(self):
        return {"columns" : self.columns,
                "extensions" : self.extensions,
                "search" : self.search,
                "show_directories" : self.show_directories,
                "show_filters" : self.show_filters, 
                "use_regex" : self.use_regex
                }
        
    def clear_filters(self, event):
        self.columns = ["path", "size", "modified"]
        self.extensions = []
        self.search = ""
        self.show_directories = True
        self.show_filters = True
        self.use_regex = False 

    @param.depends("show_filters")
    def make_widgetbox(self):
        if self.show_filters:
            button = pn.widgets.Button(name='Clear filters', button_type='primary')
            button.on_click(self.clear_filters)
            self.filter_menu = pn.WidgetBox('# Filters',
                                            pn.widgets.MultiChoice.from_param(self.param.columns),
                                            pn.widgets.MultiChoice.from_param(self.param.extensions),
                                            pn.widgets.Checkbox.from_param(self.param.show_directories),
                                            button
                                           )
            return self.filter_menu
        self.filter_menu = None
        
    
    def __panel__(self):
        self._file_table = pn.widgets.Tabulator(self.param.filtered_data, height=350, pagination=None, show_index=False, selectable=True, disabled=True)
        return pn.Column(
            pn.Row(pn.widgets.TextInput.from_param(self.param.search), 
                   pn.widgets.Checkbox.from_param(self.param.use_regex), 
                   pn.widgets.Checkbox.from_param(self.param.show_filters),
                   self.make_widgetbox
                  ),
            self._file_table,
            self.number_of_rows
        )

# dirty hack to avoid nbdev + panel docstring problems 
DataViewer.__doc__ = 'Create connection with a Nextcloud server and list the contents of a folder.'

