"""`fetch_beyeler2019`"""
from os.path import join, isfile
import numpy as np

from .base import get_data_dir, fetch_url

try:
    import pandas as pd
    has_pandas = True
except ImportError:
    has_pandas = False

try:
    import h5py
    has_h5py = True
except ImportError:
    has_h5py = False


def fetch_beyeler2019(subjects=None, electrodes=None, data_path=None,
                      shuffle=False, random_state=0, download_if_missing=True):
    """Load the phosphene drawing dataset from [Beyeler2019]_

    Download the phosphene drawing dataset described in [Beyeler2019]_ from
    https://osf.io/28uqg (66MB) to ``data_path``. By default, all datasets are
    stored in '~/pulse2percept_data/', but a different path can be specified.

    ===================   =====================
    Retinal implants:         Argus I, Argus II
    Subjects:                                 4
    Number of samples:                      400
    Number of features:                      16
    ===================   =====================

    The dataset includes the following features:

    ====================  ================================================
    subject               Subject ID, S1-S4
    electrode             Electrode ID, A1-F10
    image                 Phosphene drawing
    img_shape             x,y shape of the phosphene drawing
    date                  Experiment date (YYYY/mm/dd)
    stim_class            Stimulus type used to stimulate the array
    amp                   Pulse amplitude used (x Threshold)
    freq                  Pulse frequency used (Hz)
    pdur                  Pulse duration used (ms)
    area                  Phosphene area (see [Beyeler2019]_ for details)
    orientation           Phosphene orientation (see [Beyeler2019]_)
    eccentricity          Phosphene elongation (see [Beyeler2019]_)
    compactness           Phosphene compactness (see [Beyeler2019]_)
    x_center, y_center    Phosphene center of mass (see [Beyeler2019]_)
    xrange, yrange        Screen size in deg (see [Beyeler2019]_)
    ====================  ================================================

    .. versionadded:: 0.6

    .. versionchanged:: 0.7

        Redirected download to 66MB version of the dataset that includes
        the fields ``x_center`` and ``y_center``.

    Parameters
    ----------
    subjects : str | list of strings | None, optional
        Select data from a subject or list of subjects. By default, all
        subjects are selected.
    electrodes : str | list of strings | None, optional
        Select data from a single electrode or a list of electrodes.
        By default, all electrodes are selected.
    data_path: string, optional
        Specify another download and cache folder for the dataset. By default
        all pulse2percept data is stored in '~/pulse2percept_data' subfolders.
    shuffle : boolean, optional
        If True, the rows of the DataFrame are shuffled.
    random_state : int | numpy.random.RandomState | None, optional, default: 0
        Determines random number generation for dataset shuffling. Pass an int
        for reproducible output across multiple function calls.
    download_if_missing : optional
        If False, raise an IOError if the data is not locally available
        instead of trying to download it from the source site.

    Returns
    -------
    data: pd.DataFrame
        The whole dataset is returned in a 400x16 Pandas DataFrame

    """
    if not has_h5py:
        raise ImportError("You do not have h5py installed. "
                          "You can install it via $ pip install h5py.")
    if not has_pandas:
        raise ImportError("You do not have pandas installed. "
                          "You can install it via $ pip install pandas.")
    # Create the local data directory if it doesn't already exist:
    data_path = get_data_dir(data_path)

    # Download the dataset if it doesn't already exist:
    file_path = join(data_path, 'beyeler2019.h5')
    if not isfile(file_path):
        if download_if_missing:
            url = 'https://osf.io/28uqg/download'
            checksum = '19817990a615d289cdc93b928c138f71977ea2cab54fd1b35d186f3b5a3c4ff5'
            fetch_url(url, file_path, remote_checksum=checksum)
        else:
            raise IOError("No local file %s found" % file_path)

    # Open the HDF5 file:
    f = h5py.File(file_path, 'r')

    # Create a DataFrame for every subject, then concatenate:
    dfs = []
    # Fields names are 'subject.field_name', so we split by '.' to find the
    # subject ID:
    for subject in np.unique([k.split('.')[0] for k in f.keys()]):
        df = pd.DataFrame()
        df['subject'] = subject
        for key in f.keys():
            if subject not in key:
                continue
            # Find the field name - that's the DataFrame column:
            col = key.split('.')[1]
            if col == 'image':
                # Images need special treatment:
                # - Direct assign confuses Pandas, need a loop
                # - Save as black/white boolean
                df['image'] = [img.astype(np.bool) for img in f[key]]
            else:
                df[col] = f[key]
        dfs.append(df)
    df = pd.concat(dfs, ignore_index=True)
    f.close()

    # Combine 'img_shape_x' and 'img_shape_y' into 'img_shape' tuple
    df['img_shape'] = df.apply(lambda x: (x['img_shape_x'], x['img_shape_y']),
                               axis=1)
    df['xrange'] = df.apply(lambda x: (x['xrange_x'], x['xrange_y']), axis=1)
    df['yrange'] = df.apply(lambda x: (x['yrange_x'], x['yrange_y']), axis=1)
    df.drop(columns=['img_shape_x', 'img_shape_y', 'xrange_x', 'xrange_y',
                     'yrange_x', 'yrange_y'], inplace=True)

    # Verify integrity of the dataset:
    if len(df) != 400:
        raise ValueError("Try reloading the dataset: only %d/400 rows "
                         "found" % len(df))
    # Convert byte string to regular string, otherwise subset selection won't
    # work:
    try:
        df['subject'] = df['subject'].apply(lambda x: x.decode("utf-8"))
    except (UnicodeDecodeError, AttributeError):
        pass
    try:
        df['electrode'] = df['electrode'].apply(lambda x: x.decode("utf-8"))
    except (UnicodeDecodeError, AttributeError):
        pass

    # Select subset of data:
    idx = np.ones_like(df.index, dtype=np.bool)
    if subjects is not None:
        if isinstance(subjects, str):
            subjects = [subjects]
        idx_subject = np.zeros_like(df.index, dtype=np.bool)
        for subject in subjects:
            idx_subject |= df.subject == subject
        idx &= idx_subject
    if electrodes is not None:
        if isinstance(electrodes, str):
            electrodes = [electrodes]
        idx_electrode = np.zeros_like(df.index, dtype=np.bool)
        for electrode in electrodes:
            idx_electrode |= df.electrode == electrode
        idx &= idx_electrode
    df = df[idx]

    if shuffle:
        df = df.sample(n=len(df), random_state=random_state)

    return df.reset_index(drop=True)
