"""
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWWWNNNNNNNWWWWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMWWNNNNXXXXXXXXXXXXXXXKKKXNNWWMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMWWNNXKK0OOO0XNXK0KKXXXXKXNNX0OOOO0KKXXNNWMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMWNXXK0kddxOkxO0KXXKXXXXXXXXXXXNXXXKOxdxkOO00KXWMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMWWNKOxdxxxxxxxO0KNXKXNXKKK0KKKXKKKKKK0kkOkxolodxkk0XWMMMMMMMMMMMMMMM
MMMMMMMMMWNXXKOOkkxxxkOddOXNXKOOO0KKXXXNNWWNXK00OxdkXNX0kdc:coox0WMMMMMMMMMMMMMM
MMMMMMMWNKOOkxxxxxxkOOOOO00kkO0KNNWWNX0OkO0OxxO000KXXNNNWNXOdc:ldkNMMMMMMMMMMMMM
MMMMMMWKOxoc:lddxxxxkO0KKK0OkxkO00Okxlccclooooddooodxxk0KXXNWXkxddxKWMMMMMMMMMMM
MMMMMNklc:;:clooxxxkkkkO00OOOxc,,;;,,,,;;;;::looddxxkdoloxk0KXXNX0xx0WMMMMMMMMMM
MMMMNx:,;coxOXNNWWNNXXXXKOkxdl:,...';:clodkOKXNNNNNNWNXOdoodkkOKNWN0kONMMMMMMMMM
MMMNx:coxO0KKK0OkkkO0KXXKOkxoc;'.',;coxkO0KKKKKKKKKXNWWWX0kdddxkOKNWWKKXWMMMMMMM
MMW0odkOkkkxddxO0XXXXXKK0Odoooc'...,:ccccccccloodxxkOO0KXNNKkxxxxkOKXNXXXNMMMMMM
MNOdxxxdodxk0XNNXKKKXXX0OO00kc......,coxkkOkxxddoooddxxkk0XNNXOxddxkOO0KXXXWMMMM
XxloddoodOXNNXKKKXNWXOkOKX0l'.........;clldkO0KKKKKK0OOkkkO0KNNXOxddxkkO0KXKNWMM
OolllldOKXX0O0XWMWKxoxKNXk;............',;llcldxk0KNWWWWNK0OOOKXNXOxddxkkOKXKXWM
dc:cldk0OkkKNWWWXkox0XXKd'................;oxdlccldxOXNWMMWNX0OOKXNXOxddxxk0XKXM
kc:oxdxddOXWNXKOdx0NNXKl....................lKXKOkdoodxOKXNWMWX0OOKXNKOkxdxOXNXN
Kloxllld0K0KK0doONWNNKl......................:ONWWWNXKOkk0KKXWMNKOOOKNX0kxxk0NXX
W0o:,:xOxdk00xxKWWWWXl........................,o0XNWWMWNXXXKKNWMWKOkkOKNX0Ok0NXX
MWKl:ldlcx0OkOXWMWMXl....,:loxkxdlcc:::::;,'....,lkKXNWNNNXKKXNWMN0kxkOXWX00KNXX
MMMXxc;;cxkxkKWMMMXl..',:okOO0KKKKK00KXXXXK0Okdc,.'lkKKKKXNXKKXNMMXOxxkOXXOO0NXX
MMMMXl,;codx0WMMMXl'',:ldO0KXXNWWNNNNNNXXNNNNNNX0kl,,lk0OO0XXKKXWMWKkxxk0KOOOKXX
MMMMWO:;lookXMWWNd,;loxOXNNNNNKKNWWNXKKXXXNNWWNNNX0d,.'lxkkOKK0KNWMXkxkkOOkOOKKX
MMMMMXoclcxKWWNNk;';:clodk0KNWNXNWNK000kkdoddkKXXXOoc,..,lkkO000XNWKkxkkOkkOO00K
MMMMMXocclOXWWNO:'.',,,'.',:xXWNXOkkko:,''.'':ooloxxol:...cxkOOOKNW0dxxkOkxkO00K
MMMMMXdc:oO0XNXo.,:ccldoc,..,d0KOdodko:;::::;:lxklcdOOkc...:xkkkOXN0dxxkOxxkO0OK
MMMMMWOc;ok0KXKl'::;:oddol;..cOOkdokOd:cccclooooxd:;dKX0l'..:dxdkKX0ddxxkdxkOOON
MMMMMMNx;;oxk00l',;;,..;ddl,.;xOkdodOkdc;,,locxOdldOKWWNO:...:ol:oOkodkxddxOOOKW
MMMMMMMNx;;loxkd;,;;:cldkdc;'lKNN0xOXKOkkk0K000K0OKWNNXX0c.. 'c:';xxlododk0OO0WM
MMMMMMMMNOo:;:colodc:lddl;''cKWWNKkxO0OxxkKXNNNXKXNNNNNXkc.. .,;.'cc::cok0OkONMM
MMMMMMMMMMNOo;,;:xXKOxdoc;,c0WWWNX000KKK0KXXNXXXNWNNXXN0c.....,,..'..;lxxdoONMMM
MMMMMMMMMMMMW0d:;dKXNX0kdll0WMWWNXKXXXXXXXKKKXNNWWWWNXK0o...........;:c::dKWMMMM
MMMMMMMMMMMMMMW0:cOKNWXOllONWWNKK0KXXNNNNNNNXXKKXXXXXKKx:........',,;;:dKWMMMMMM
MMMMMMMMMMMMMMMWx:oO0Kk:,oKNWWXOkO00O0XNWWWWNXK000000Od:'.......'';ld0NMMMMMMMMM
MMMMMMMMMMMMMMMM0;,lxxc',o0XNNKdlOKOxxxOKXNNNNX0Okolxo,....',''cdkKWMMMMMMMMMMMM
MMMMMMMMMMMMMMMXo..:ddc,',lkkxl;,:lldK0xxk0XXXK0Odc',;'''...'',xWMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMNk:.'dko:'',;;;:;',cloxO0K0OO000Odcc:....'....''oNMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMNo..coc;''',;;;:,,,,,,;lxxolodxoc;;c,..;,'.''..oNMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMWO;.';:;:;;:od:cc:;;::,',,;,;;;;,,,c;.'::;'.,,..ckXMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMM0:..,:;:clccl;,:,,,,''...':lc::loclc',::,..'..  .,oKWMMMMMMMMMM
MMMMMMMMMMMMMMMMWk'':lc,,,,coooooddl;......:occlllld:.,:,......     'l0WMMMMMMMM
MMMMMMMMMMMMMMMMNd';loc:;'',:cccclc:,.......:;,cc:cl,'cc;.......      .lONMMMMMM
MMMMMMMMMMMMMMMMXl';cccc;;:,'.. ..''''.....,c;:l:cc,,:c:,'.''.....      .;d0NMMM
MMMMMMMMMMMMMMMMKc'',:loc::;,...'c;''...'',:;';lcc;,c:,,,.''.. ....     ....;d0W
MMMMMMMMMMMMMMWXxlc;',ll:;;:;...;l;''',;:c:,..':;,;;;'.'..''. ............    .:
MMMMMMMMMMMMN0o;:do;',;,;ooc:,',,:;',:::cc;.'',:,.,,,'.....................  .
MMMMMMMMMN0d;...,lc,'.':odl::;;:,';;;clc:;,'..,,''''.'...................... .
MMMMMMWKd;.  ....;cc'.:odollc;::'.';,;l:,,;,.',.'',,.',,,'......................
MMMMWKd,..........;;..,:ooloccol;',:;,lc,,',';:,;:'...''.........''''''''''..'''
MMMW0d:'..........',..';cclooxxdooll::oc;,...:c',:,.,:;'.''''..''',;;,,,,,,,;ccl
MMMWN0d:;,'............';:clodxxxxlc:;:;:,..;;'''::':c,,,;;,',,,;;,;;,'',;ldk00K
MMMMMWN0koc:,,'''..''''.';;;lldkxo::;;;,''.','..,:,''',,,,',,,:::::;;;;;cdKWWMMM
MMMMMMMMMWXKOxlc;,...','...,:;coc;;;;'''''.....,:,...''''',;::clcclddxk0XWMMMMMM
MMMMMMMMMMMMMWNN0d:,........''';;..,,........';;,'','',;;:codxkOO0KNWWMMMMMMMMMM
MMMMMMMMMMMMMMMMMWX0xl;,...........',...,...':;;;;;::codk0XNWWMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMWXOdc,'..',,,,,;;,,,'.,;ccldkOOKXNWMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMWN0xoc:cccccldkxxkOOO0KXNWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMN0xc;;::cxXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM




`7MMF'  `7MMF'
  MM      MM
  MM      MM   ,6"Yb.  M""MMV .gP"Ya  7MMpMMMb.
  MMmmmmmmMM  8)   MM  '  AMV ,M'   Yb  MM    MM
  MM      MM   ,pm9MM    AMV  8M~~~~~'  MM    MM
  MM      MM  8M   MM   AMV  ,YM.    ,  MM    MM
.JMML.  .JMML.`Moo9^Yo.AMMmmmM `Mbmmd'.JMML  JMML.



Welcome to the Hazen Command Line Interface
Usage:
    hazen <task> <folder> [--measured_slice_width=<mm>] [--report] [--output=<path>] [--calc_t1 | --calc_t2] [--plate_number=<n>] [--show_template_fit]
    [--show_relax_fits] [--show_rois] [--log=<lvl>] [--verbose]
    hazen -h|--help
    hazen -v|--version
Options:
    <task>    snr | acr_snr | slice_position | slice_width | spatial_resolution | uniformity | ghosting | relaxometry | snr_map
    <folder>
    --report


"""
import importlib
import inspect
import logging
import sys
import pprint
import os

import numpy as np
import pydicom
from docopt import docopt
from hazenlib.logger import logger
from hazenlib.HazenTask import HazenTask
from hazenlib.tools import is_dicom_file, get_dicom_files

import hazenlib.exceptions

EXCLUDED_FILES = ['.DS_Store']


def rescale_to_byte(array):
    image_histogram, bins = np.histogram(array.flatten(), 255)
    cdf = image_histogram.cumsum()  # cumulative distribution function
    cdf = 255 * cdf / cdf[-1]  # normalize
    # use linear interpolation of cdf to find new pixel values
    image_equalized = np.interp(array.flatten(), bins[:-1], cdf)
    return image_equalized.reshape(array.shape).astype('uint8')


def is_enhanced_dicom(dcm: pydicom.Dataset) -> bool:
    """

    Parameters
    ----------
    dcm

    Returns
    -------
    bool

    Raises
    ------
    Exception
     Unrecognised SOPClassUID

    """

    if dcm.SOPClassUID == '1.2.840.10008.5.1.4.1.1.4.1':
        return True
    elif dcm.SOPClassUID == '1.2.840.10008.5.1.4.1.1.4':
        return False
    else:
        raise Exception('Unrecognised SOPClassUID')


def get_manufacturer(dcm: pydicom.Dataset) -> str:
    supported = ['ge', 'siemens', 'philips', 'toshiba', 'canon']
    manufacturer = dcm.Manufacturer.lower()
    for item in supported:
        if item in manufacturer:
            return item

    raise Exception(f'{manufacturer} not recognised manufacturer')


def get_average(dcm: pydicom.Dataset) -> float:
    if is_enhanced_dicom(dcm):
        averages = dcm.SharedFunctionalGroupsSequence[0].MRAveragesSequence[0].NumberOfAverages
    else:
        averages = dcm.NumberOfAverages

    return averages


def get_bandwidth(dcm: pydicom.Dataset) -> float:
    """
    Returns PixelBandwidth

    Parameters
    ----------
    dcm: pydicom.Dataset

    Returns
    -------
    bandwidth: float
    """
    bandwidth = dcm.PixelBandwidth
    return bandwidth


def get_num_of_frames(dcm: pydicom.Dataset) -> int:
    """
    Returns number of frames of dicom object

    Parameters
    ----------
    dcm: pydicom.Dataset
        DICOM object

    Returns
    -------

    """
    if len(dcm.pixel_array.shape) > 2:
        return dcm.pixel_array.shape[0]
    elif len(dcm.pixel_array.shape) == 2:
        return 1


def get_slice_thickness(dcm: pydicom.Dataset) -> float:
    if is_enhanced_dicom(dcm):
        try:
            slice_thickness = dcm.PerFrameFunctionalGroupsSequence[0].PixelMeasuresSequence[0].SliceThickness
        except AttributeError:
            slice_thickness = dcm.PerFrameFunctionalGroupsSequence[0].Private_2005_140f[0].SliceThickness
        except Exception:
            raise Exception('Unrecognised metadata Field for Slice Thickness')
    else:
        slice_thickness = dcm.SliceThickness

    return slice_thickness


def get_pixel_size(dcm: pydicom.Dataset) -> (float, float):
    manufacturer = get_manufacturer(dcm)
    try:
        if is_enhanced_dicom(dcm):
            dx, dy = dcm.PerFrameFunctionalGroupsSequence[0].PixelMeasuresSequence[0].PixelSpacing
        else:
            dx, dy = dcm.PixelSpacing
    except:
        print('Warning: Could not find PixelSpacing..')
        if 'ge' in manufacturer:
            fov = get_field_of_view(dcm)
            dx = fov / dcm.Columns
            dy = fov / dcm.Rows
        else:
            raise Exception('Manufacturer not recognised')

    return dx, dy


def get_TR(dcm: pydicom.Dataset) -> (float):
    """
    Returns Repetition Time (TR)

    Parameters
    ----------
    dcm: pydicom.Dataset

    Returns
    -------
    TR: float
    """

    try:
        TR = dcm.RepetitionTime
    except:
        print('Warning: Could not find Repetition Time. Using default value of 1000 ms')
        TR = 1000
    return TR


def get_rows(dcm: pydicom.Dataset) -> (float):
    """
    Returns number of image rows (rows)

    Parameters
    ----------
    dcm: pydicom.Dataset

    Returns
    -------
    rows: float
    """
    try:
        rows = dcm.Rows
    except:
        print('Warning: Could not find Number of matrix rows. Using default value of 256')
        rows = 256

    return rows


def get_columns(dcm: pydicom.Dataset) -> (float):
    """
    Returns number of image columns (columns)

    Parameters
    ----------
    dcm: pydicom.Dataset

    Returns
    -------
    columns: float
    """
    try:
        columns = dcm.Columns
    except:
        print('Warning: Could not find matrix size (columns). Using default value of 256.')
        columns = 256
    return columns


def get_field_of_view(dcm: pydicom.Dataset):
    # assumes square pixels
    manufacturer = get_manufacturer(dcm)

    if 'ge' in manufacturer:
        fov = dcm[0x19, 0x101e].value
    elif 'siemens' in manufacturer:
        fov = dcm.Columns * dcm.PixelSpacing[0]
    elif 'philips' in manufacturer:
        if is_enhanced_dicom(dcm):
            fov = dcm.Columns * dcm.PerFrameFunctionalGroupsSequence[0].PixelMeasuresSequence[0].PixelSpacing[0]
        else:
            fov = dcm.Columns * dcm.PixelSpacing[0]
    elif 'toshiba' in manufacturer:
        fov = dcm.Columns * dcm.PixelSpacing[0]
    else:
        raise NotImplementedError('Manufacturer not ge,siemens, toshiba or philips so FOV cannot be calculated.')

    return fov


def parse_relaxometry_data(task, arguments, dicom_objects,
                           report):  # def parse_relaxometry_data(arguments, dicom_objects, report):   #

    # Relaxometry arguments
    relaxometry_cli_args = {'--calc_t1', '--calc_t2', '--plate_number',
                            '--show_template_fit', '--show_relax_fits',
                            '--show_rois', '--verbose'}

    # Pass arguments with dictionary, stripping initial double dash ('--')
    relaxometry_args = {}

    for key in relaxometry_cli_args:
        relaxometry_args[key[2:]] = arguments[key]

    return task.main(dicom_objects, report_path=report,
                     **relaxometry_args)


def main():
    arguments = docopt(__doc__)
    task_module = importlib.import_module(f"hazenlib.tasks.{arguments['<task>']}")

    files = get_dicom_files(arguments['<folder>'])
    pp = pprint.PrettyPrinter(indent=4, depth=1, width=1)

    log_levels = {
        "critical": logging.CRITICAL,
        "debug": logging.DEBUG,
        "info": logging.INFO,
        "warning": logging.WARNING,
        "error": logging.ERROR

    }

    if arguments['--log'] in log_levels.keys():
        level = log_levels[arguments['--log']]
        logging.getLogger().setLevel(level)
    else:
        # logging.basicConfig()
        logging.getLogger().setLevel(logging.INFO)

    class_list = [cls for _, cls in inspect.getmembers(sys.modules[task_module.__name__],
                                                       lambda x: inspect.isclass(x) and (
                                                                   x.__module__ == task_module.__name__))]

    if len(class_list) > 1:
        raise Exception(f'Task {task_module} has multiple class definitions: {class_list}')

    task = getattr(task_module, class_list[0].__name__)(data_paths=files,
                                                        report=arguments['--report'],
                                                        report_dir=[arguments['--output'] if arguments[
                                                            '--output'] else os.path.join(os.getcwd(), 'report')][0])

    if not arguments['<task>'] == 'snr' and arguments['--measured_slice_width']:
        raise Exception("the (--measured_slice_width) option can only be used with snr")
    elif arguments['<task>'] == 'snr' and arguments['--measured_slice_width']:
        measured_slice_width = float(arguments['--measured_slice_width'])
        logger.info(f'Calculating SNR with measured slice width {measured_slice_width}')
        result = task.run(measured_slice_width)
    # Relaxometry not currently converted to HazenTask object - this task accessible in the CLI using the old syntax until it can be refactored
    elif arguments['<task>'] == 'relaxometry':
        task = importlib.import_module(f"hazenlib.{arguments['<task>']}")
        dicom_objects = [pydicom.read_file(x, force=True) for x in files if is_dicom_file(x)]
        result = parse_relaxometry_data(task, arguments, dicom_objects, report=True)
    # Relaxometry not currently converted to HazenTask object - this task accessible in the CLI using the old syntax until it can be refactored
    elif arguments['<task>'] == 'snr_map':
        task = importlib.import_module(f"hazenlib.{arguments['<task>']}")
        dicom_objects = [pydicom.read_file(x, force=True) for x in files if is_dicom_file(x)]
        result = task.main(dicom_objects, report_path=True)
    # Relaxometry not currently converted to HazenTask object - this task accessible in the CLI using the old syntax until it can be refactored
    elif arguments['<task>'] == 'acr_uniformity':
        task = importlib.import_module(f"hazenlib.{arguments['<task>']}")
        dicom_objects = [pydicom.read_file(x, force=True) for x in files if is_dicom_file(x)]
        result = task.main(dicom_objects, report_path=True)
    else:
        result = task.run()

    return pp.pformat(result)


def entry_point():
    result = main()
    print(result)


if __name__ == "__main__":
    entry_point()
