#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# title           : most.py
# description     : MOST datasets readers
# copyright       : (c) 2017 I3MTO laboratory. All Rights Reserved
# author(s)       : Thomas Janvier
# creation        : 13 December 2017
# modification    : 19 December 2017

import os
import pydicom  # requires pydicom >= 1.x
import warnings

from pandas import DataFrame, read_csv

__all__ = ['MOST']


class MOST():
    """Abstract class providing methods to read the Multicenter Osteoarthritis Study (MOST) datasets.

    MOST team provided the data along with .txt files containing:
        - "MOSTV0ENROLL.txt" clinical covariates at baseline
        - "MOSTV1ENROLL.txt" clinical covariates at 1 year
        - "MOSTV1ENROLL.txt" clinical covariates at 2 years
        - "MOSTV2ENROLL.txt" clinical covariates at 3 years
        - "MOSTV2ENROLL.txt" clinical covariates at 4 years
        - "MOSTV5ENROLL.txt" clinical covariates at 5 years
        - "MOSTOUTCOMES.txt" the registered outcomes
        - "MOSTV01235XRAY.txt" the XR data readings
        - "MOSTV02WORMS_INCIDENTROA.txt" the MRI data readings
    the details of these files can be found in the documentation included or directly from the website [1]_.
    **These files are also available from [1]_, consider to download the latest version !**

    Notes
    -----
    This is an abstract class intented for a 'script style' use without object-oriented notions (see Examples)

    References
    ----------
    .. [1] Multicenter Osteoarthritis Study website,
           http://most.ucsf.edu/datadocs.asp

    Examples
    --------
    >>> V0 = MOST.readDataset("baseline")
    >>> out = MOST.readDataset("outcomes")
    >>> XR = MOST.readDataset("XR")
    """

    def _provided(set):
        """Simplify the provided dataset names.

        Parameters
        ----------
        set : str
            The simplified dataset names (see the code below).

        Returns
        -------
        normalized : str
            The normalized dataset name if found.
        """
        known = {"V0": "MOSTV0ENROLL.txt",
                 "V1": "MOSTV1ENROLL.txt",
                 "V2": "MOSTV1ENROLL.txt",
                 "V3": "MOSTV2ENROLL.txt",
                 "V4": "MOSTV2ENROLL.txt",
                 "V5": "MOSTV5ENROLL.txt",
                 "OUT": "MOSTOUTCOMES.txt",
                 "XR": "MOSTV01235XRAY.txt",
                 "MRI": "MOSTV02WORMS_INCIDENTROA.txt"}
        if set.upper() in known.keys():
            return known[set.upper()]
        else:
            return None

    @staticmethod
    def _readDicomHeader(file):
        """Wrapper for DICOM metadata extraction using pydicom.

        Read a DICOM dataset stored in accordance with the DICOM File Format (DICOM Standard Part 10 Section 7).

        Parameters
        ----------
        path : str or file-like
            Either a file-like object, or a string containing the file name. If a file-like object, the caller is responsible for closing it.

        Returns
        -------
        dcm : pydicom.dataset.FileDataset
            An instance of FileDataset that represents a parsed DICOM file.

        See Also
        --------
        pydicom : Read, modify and write DICOM files with python code
        pydicom.read_file : [deprecated] pydicom built-in DICOM reader
        pydicom.dcmread : pydicom built-in DICOM reader
        pydicom.dataset.FileDataset : pydicom built-in DICOM metadata
        """
        return pydicom.dcmread(file, stop_before_pixels=True, force=True)

    @staticmethod
    def _dictify(dataset):
        """Turn a pydicom Dataset into a dict with keys derived from the Element keywords.

        Parameters
        ----------
        dataset : pydicom.dataset.Dataset
            The Dataset to dictify.

        Returns
        -------
        values : dict
            The dictionnary with DICOM tag as keys

        tags : dict
            The names and types for DICOM tags
        """
        values = dict()
        tags = dict()
        try:
            for el in dataset:
                tag = str(el.tag)
                if el.VR != 'SQ':
                    values[tag] = el.value
                else:
                    values[tag] = [OAI._dictify(it) for it in el]
                tags[tag] = (el.name, el.VR)
        except LookupError as err:
            warnings.warn(format(err), UnicodeWarning)

        return values, tags

    @staticmethod
    def readDataset(dataset='XR', out=''):
        """Read the content of the MOST dataset from .txt file.

        Parameters
        ----------
        dataset : str
            The dataset filename (abbreviation or complete 'path/name.txt').

        out : str
            Complete name of the out csv file (path + filename).
            Force empty ('') or `None` to avoid serialization.

        Returns
        -------
        dataset : pandas.DataFrame
            Table contaning the OAI files list and associated data
        """
        print("------------------------------------------------------")
        print("Reading the Multicenter Osteoarthritis Study dataset :")
        print("------------------------------------------------------")
        # check if 'dataset' is a file
        if not os.path.isfile(dataset):
            dataset = MOST._provided(dataset)
        # convert paths to Unix style
        file = dataset.replace('\\', '/')
        # read the dataset
        dataset = read_csv(file, sep='\t', header=0)
        # store results if needed
        if not not out:
            # export in csv file
            dataset.to_csv(out, sep='|', na_rep='NaN')
            print("[log] Results stored in: {}".format(out))
        # return dataset
        print("[log] Completed")
        return dataset

    @staticmethod
    def readXR(root='XR', out='./MOST_XRcontents.csv'):
        """List the XR images in the MOST database.

        Parameters
        ----------
        root : str
            The database root path ('MOST/Image Datasets/XR').

        out : str
            Complete name of the out csv file (path + filename).
            Force empty ('') or `None` to avoid serialization.

        Returns
        -------
        dataset : pandas.DataFrame
            Table contaning the MOST XR files list and extrapolated data
        """
        print("------------------------------------------------------")
        print("Reading the Multicenter Osteoarthritis Study dataset :")
        print("------------------------------------------------------")
        # convert paths to Unix style
        root = root.replace('\\', '/')
        # check if 'root' is a directory
        if not os.path.isdir(root):
            raise FileNotFoundError("No such file or directory: {}".format(input))
        # create the empty list of files
        files = []
        # recursively list all files in 'root'
        print("[log] Running through : \'{}\'".format(root))
        for path, folders, names in os.walk(root):
            files += [os.path.join(path,f) for f in names]
        print("[log] Parsing filenames")
        # convert paths to Unix style
        files = [f.replace('\\', '/') for f in files]
        # truncate 'root' prefix from files paths
        files = [f.replace(os.path.normpath(root)+'/','') for f in files]
        # extract the 'MOSTID_ACROSTIC' part
        patterns = [f.split('/')[0] for f in files]
        # parse filepaths from MOST database naming pattern
        contents = DataFrame()
        contents['FILE'] = files
        contents['MOSTID'] = [p.split('_')[0] for p in patterns]
        contents['ID'] = [int(x[1:]) for x in contents['MOSTID']]
        contents['ACROSTIC'] = [p.split('_')[1] for p in patterns]
        contents['VISIT'] = [f.split('/')[1] for f in files]
        contents['EXAM'] = [f.split('/')[3] for f in files]
        # store results if needed
        if not not out:
            # export in csv file
            print("[log] Writing results in: {}".format(out))
            contents.to_csv(out, sep='|', na_rep='NaN')
        print("[log] Completed")
        return dataset

@staticmethod
def readDCM(input, prefix='', out='./MOST_XRdicoms.csv'):
    """Read the MOST DICOM files metadata.

    Data are extracted directly from the DICOM files metadata.

    Notes
    -----
    Tested only on XR dataset, should work with MRI too but has to be tested.

    Parameters
    ----------
    input : list or pandas.DataFrame
        Contains the DICOM files as a list of strings.
        If 'input' is a pandas.DataFrame, use the column `input['FILE']` as list.

    prefix : str, optional
        Prefix (root path) for input files.

    out : str
        Complete name of the out csv file (path + filename).

    Returns
    -------
    dicoms : pandas.DataFrame
        Table contaning the DICOM files metadata.

    tags : pandas.DataFrame
        Table containing the DICOM tags details.
    """
    # extract 'Path' column
    if isinstance(input, DataFrame):
        files = contents['FILES']
    # nothing to do
    elif isinstance(input, list):
        files = input
    # wrong input
    else:
        raise TypeError(
            "input should be a 'string', 'list' or 'DataFrame' not : {}".format(type(input)))
    # read DICOM metadata
    print("----------------------------")
    print("Reading the DICOM metadata :")
    print("----------------------------")
    # add prefix if needed
    if isinstance(prefix, str) and bool(prefix):
        files = [os.path.join(prefix, f) for f in files]
        print("[log] Files are read from: {}".format(prefix))
    # convert paths to Unix style
    files = [f.replace('\\', '/') for f in files]
    # read all dicom files metadata (as a list of
    # `pydicom.dataset.Dataset`)
    metadata = [OAI._readDicomHeader(f) for f in files]
    print("[log] Metadata extraction completed")
    # cast the list of `pydicom.dataset.Dataset` to a Python tuple of
    # dictionaries
    metadata, tags = [ii for ii in zip(
        *[OAI._dictify(x) for x in metadata])]
    # convert the tuple of dictionaries to a `pandas.DataFrame`
    metadata = DataFrame(list(metadata))
    print("[log] Metadata parsing completed")
    # unify (recursively merge) the DICOM tags dictionary
    tags = {key: val for dct in tags for key, val in dct.items()}
    # convert the DICOM tags dictionnary to a `pandas.DataFrame`
    tags = DataFrame(tags)
    tags.set_axis(['Name', 'Type'], inplace=True)
    # store results if needed
    if not not out:
        # export in csv files
        metadata.to_csv(fname, sep='|', na_rep='NaN')
        tags.to_csv(os.path.splitext(out)[0] + '(tags).csv',
                    sep='|', na_rep='NaN')
        print("[log] Results stored in: {}".format(out))
    print("... XR reading finished !\n")
    # return contents
    print("[log] Completed")
    return metadata, tags
