# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/07_utils.ipynb.

# %% ../nbs/07_utils.ipynb 3
from __future__ import annotations
from tqdm import tqdm
import os, sys
from pathlib import Path
from functools import partial
import dill as pickle
import torch
import numpy as np
import pandas as pd
from typing import Callable, Any
from collections.abc import Iterable

# %% auto 0
__all__ = ['HiddenPrints', 'val2iterable', 'create_dir', 'check_and_get_attribute', 'callable_name', 'print_msg',
           'seed_everything', 'save_to_pickle', 'load_pickle', 'check_input_validation', 'check_text_leaking',
           'sigmoid']

# %% ../nbs/07_utils.ipynb 4
class HiddenPrints:
    "To hide print command when called"
    def __enter__(self):
        self._original_stdout = sys.stdout
        sys.stdout = open(os.devnull, 'w')

    def __exit__(self, exc_type, exc_val, exc_tb):
        sys.stdout.close()
        sys.stdout = self._original_stdout

# %% ../nbs/07_utils.ipynb 5
def val2iterable(val,lsize=1,t='list'):
    "Convert an element (nonlist value) to an iterable. Currently support list and nparray"
    if not isinstance(val, Iterable) or isinstance(val,str):
        if t=='list': val=[val for i in range(lsize)]
        elif t=='nparray': val=np.repeat(val,lsize)
        else:
            raise ValueError('Unrecognized iterable to convert to')
    return val

def create_dir(path_dir):
    "Create directory if needed"
    path_dir = Path(path_dir)
    if not path_dir.exists():
        path_dir.mkdir(parents=True)

# %% ../nbs/07_utils.ipynb 6
def check_and_get_attribute(obj,attr_name):
    if hasattr(obj,attr_name): return getattr(obj,attr_name)
    else: raise ValueError(f"Missing required argument: {attr_name}")
            
def callable_name(any_callable: Callable[..., Any]) -> str:
    "To get name of any callable"
    if hasattr(any_callable, '__name__'):
        return any_callable.__name__
    if isinstance(any_callable, partial):
        return any_callable.func.__name__

    return str(any_callable)

def print_msg(msg,dash_num=5):
    print('-'*dash_num+' '+msg+' '+ '-'*dash_num)
    
def seed_everything(seed=42):
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True

# %% ../nbs/07_utils.ipynb 7
def save_to_pickle(my_list,fname,parent='pickle_files'):
    if fname[-4:]!='.pkl': fname+='.pkl'
    p = Path(parent)
    p.mkdir(parents=True, exist_ok=True)
    fname = p/fname
    with open(fname, 'wb') as f:
        pickle.dump(my_list, f)
        
def load_pickle(fname,parent='pickle_files'):
    if fname[-4:]!='.pkl': fname+='.pkl'
    with open(Path(parent)/fname,'rb') as f:
        my_list = pickle.load(f)
    return my_list

# %% ../nbs/07_utils.ipynb 8
def check_input_validation(df:pd.DataFrame):
    print_msg('Input Validation Precheck')
    # check whether index is sorted
    correct_idxs = np.arange(df.shape[0])
    curr_idxs = df.index.values
    if not np.array_equal(correct_idxs,curr_idxs):
        print("DataFrame Index is not RangeIndex, and will be converted")
        df.index = correct_idxs

    # Do a NA check:
    na_check = df.isna().sum(axis=0)
    na_check = na_check[na_check!=0]
    if na_check.shape[0]!=0:
        print("DataFrame contains missing values!")
        print('-----> List of columns and the number of missing values for each')
        print(na_check)

    # Do a row duplication check
    _df = df.copy().astype(str)
    dup_check = _df.value_counts(dropna=False)
    dup_check=dup_check[dup_check>1]
    if dup_check.shape[0]!=0:
        print("DataFrame contains duplicated values!")
        print(f'-----> Number of duplications: {(dup_check.values-1).sum()} rows')
        
    
def check_text_leaking(trn_series:pd.Series,
                       test_series:pd.Series):
    len_before=len(test_series)
    print(f'- Before leak check\nSize: {len_before}')
    test_series_noleak = test_series[~(test_series.str.lower().isin(set(trn_series.str.lower().values)))]
    len_after=len(test_series_noleak)
    len_diff = len_before-len_after
    print(f'- After leak check\nSize: {len_after}')
    print(f'- Number of rows leaked: {len_diff}, or {100*len_diff/len_before:.2f}% of the original validation (or test) data')
    return test_series_noleak

# %% ../nbs/07_utils.ipynb 9
def sigmoid(x):
    """
    A numerically stable version of the logistic sigmoid function.
    
    Source: assignment3 of cs231n
    """
    pos_mask = (x >= 0)
    neg_mask = (x < 0)
    z = np.zeros_like(x)
    z[pos_mask] = np.exp(-x[pos_mask])
    z[neg_mask] = np.exp(x[neg_mask])
    top = np.ones_like(x)
    top[neg_mask] = z[neg_mask]
    return top / (1 + z)
