"""
#lookup.py

This file contains all the mappings between hickle/HDF5 metadata and python types.
There are four dictionaries and one set that are populated here:

1) types_dict
types_dict: mapping between python types and dataset creation functions, e.g.
    types_dict = {
        list:        create_listlike_dataset,
        int:         create_python_dtype_dataset,
        np.ndarray:  create_np_array_dataset
        }

2) hkl_types_dict
hkl_types_dict: mapping between hickle metadata and dataset loading functions, e.g.
    hkl_types_dict = {
        "<type 'list'>"  : load_list_dataset,
        "<type 'tuple'>" : load_tuple_dataset
        }

3) container_types_dict
container_types_dict: mapping required to convert the PyContainer object in hickle.py
                      back into the required native type. PyContainer is required as
                      some iterable types are immutable (do not have an append() function).
                      Here is an example:
    container_types_dict = {
        "<type 'list'>": list,
        "<type 'tuple'>": tuple
        }

4) container_key_types_dict
container_key_types_dict: mapping specifically for converting hickled dict data back into
                          a dictionary with the same key type. While python dictionary keys
                          can be any hashable object, in HDF5 a unicode/string is required
                          for a dataset name. Example:
    container_key_types_dict = {
        "<type 'str'>": str,
        "<type 'unicode'>": unicode
        }

5) types_not_to_sort
type_not_to_sort is a list of hickle type attributes that may be hierarchical,
but don't require sorting by integer index.

## Extending hickle to add support for other classes and types

The process to add new load/dump capabilities is as follows:

1) Create a file called load_[newstuff].py in loaders/
2) In the load_[newstuff].py file, define your create_dataset and load_dataset functions,
   along with all required mapping dictionaries.
3) Add an import call here, and populate the lookup dictionaries with update() calls:
    # Add loaders for [newstuff]
    try:
        from .loaders.load_[newstuff[ import types_dict as ns_types_dict
        from .loaders.load_[newstuff[ import hkl_types_dict as ns_hkl_types_dict
        types_dict.update(ns_types_dict)
        hkl_types_dict.update(ns_hkl_types_dict)
        ... (Add container_types_dict etc if required)
    except ImportError:
        raise
"""

import six
import pkg_resources


def return_first(x):
    """ Return first element of a list """
    return x[0]


def load_nothing(h_hode):
    pass


types_dict = {}
hkl_types_dict = {}
types_not_to_sort = [b'dict', b'csr_matrix', b'csc_matrix', b'bsr_matrix']

container_types_dict = {
    b"<type 'list'>": list,
    b"<type 'tuple'>": tuple,
    b"<type 'set'>": set,
    b"<class 'list'>": list,
    b"<class 'tuple'>": tuple,
    b"<class 'set'>": set,
    b"csr_matrix":  return_first,
    b"csc_matrix": return_first,
    b"bsr_matrix": return_first
    }

# Technically, any hashable object can be used, for now sticking with built-in types
container_key_types_dict = {
    b"<type 'str'>": str,
    b"<type 'float'>": float,
    b"<type 'bool'>": bool,
    b"<type 'int'>": int,
    b"<type 'complex'>": complex,
    b"<class 'str'>": str,
    b"<class 'float'>": float,
    b"<class 'bool'>": bool,
    b"<class 'int'>": int,
    b"<class 'complex'>": complex
    }

if six.PY2:
    container_key_types_dict[b"<type 'unicode'>"] = unicode
    container_key_types_dict[b"<type 'long'>"] = long

# Add loaders for built-in python types
if six.PY2:
    from .loaders.load_python import types_dict as py_types_dict
    from .loaders.load_python import hkl_types_dict as py_hkl_types_dict
else:
    from .loaders.load_python3 import types_dict as py_types_dict
    from .loaders.load_python3 import hkl_types_dict as py_hkl_types_dict

types_dict.update(py_types_dict)
hkl_types_dict.update(py_hkl_types_dict)

# Add loaders for numpy types
from .loaders.load_numpy import types_dict as np_types_dict
from .loaders.load_numpy import hkl_types_dict as np_hkl_types_dict
from .loaders.load_numpy import check_is_numpy_array
types_dict.update(np_types_dict)
hkl_types_dict.update(np_hkl_types_dict)


#####################
# ND-ARRAY checking #
#####################

ndarray_like_check_fns = [
    check_is_numpy_array
]


def check_is_ndarray_like(py_obj):
    is_ndarray_like = False
    for ii, check_fn in enumerate(ndarray_like_check_fns):
        is_ndarray_like = check_fn(py_obj)
        if is_ndarray_like:
            break
    return is_ndarray_like


#####################
# loading optional  #
#####################

def register_class(myclass_type, hkl_str, dump_function, load_function,
                   to_sort=True, ndarray_check_fn=None):
    """ Register a new hickle class.

    Args:
        myclass_type type(class): type of class
        dump_function (function def): function to write data to HDF5
        load_function (function def): function to load data from HDF5
        hkl_str (str): String to write to HDF5 file to describe class
        to_sort (bool): If the item is iterable, does it require sorting?
        ndarray_check_fn (function def): function to use to check if

    """
    types_dict.update({myclass_type: dump_function})
    hkl_types_dict.update({hkl_str: load_function})
    if to_sort == False:
        types_not_to_sort.append(hkl_str)
    if ndarray_check_fn is not None:
        ndarray_like_check_fns.append(ndarray_check_fn)


def register_class_list(class_list):
    """ Register multiple classes in a list

    Args:
        class_list (list): A list, where each item is an argument to
                           the register_class() function.

    Notes: This just runs the code:
            for item in mylist:
                register_class(*item)
    """
    for class_item in class_list:
        register_class(*class_item)


def register_class_exclude(hkl_str_to_ignore):
    """ Tell loading funciton to ignore any HDF5 dataset with attribute 'type=XYZ'

    Args:
        hkl_str_to_ignore (str): attribute type=string to ignore and exclude from loading.
    """
    hkl_types_dict[hkl_str_to_ignore] = load_nothing


def register_exclude_list(exclude_list):
    """ Ignore HDF5 datasets with attribute type='XYZ' from loading

    ArgsL
        exclude_list (list): List of strings, which correspond to hdf5/hickle
                             type= attributes not to load.
    """
    for hkl_str in exclude_list:
        register_class_exclude(hkl_str)


######################
# Scipy sparse array #
######################

try:
    pkg_resources.require('hickle[scipy]')
    from .loaders.load_scipy import class_register, exclude_register
    register_class_list(class_register)
    register_exclude_list(exclude_register)
except pkg_resources.DistributionNotFound:
    pass

##################
# Astropy  stuff #
##################

try:
    pkg_resources.require('hickle[astropy]')
    from .loaders.load_astropy import class_register
    register_class_list(class_register)
except pkg_resources.DistributionNotFound:
    pass

################
# Pandas stuff #
################

try:
    pkg_resources.require('hickle[pandas]')
    from .loaders.load_pandas import class_register
    register_class_list(class_register)
except pkg_resources.DistributionNotFound:
    pass
