# -*- coding: utf-8 -*-
"""
CSV Tabular Data Input / Output
===============================

Defines various input / output objects for *CSV* tabular data files:

-   :func:`colour.read_spectral_data_from_csv_file`
-   :func:`colour.read_sds_from_csv_file`
-   :func:`colour.write_sds_to_csv_file`
"""

from __future__ import division, unicode_literals

from collections import OrderedDict
import csv

from colour.colorimetry import SpectralDistribution
from colour.constants import DEFAULT_FLOAT_DTYPE

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2020 - Colour Developers'
__license__ = 'New BSD License - https://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-developers@colour-science.org'
__status__ = 'Production'

__all__ = [
    'read_spectral_data_from_csv_file', 'read_sds_from_csv_file',
    'write_sds_to_csv_file'
]


def read_spectral_data_from_csv_file(path,
                                     delimiter=',',
                                     fields=None,
                                     default=0):
    """
    Reads the spectral data from given *CSV* file in the following form:

    390,  4.15003E-04,  3.68349E-04,  9.54729E-03
    395,  1.05192E-03,  9.58658E-04,  2.38250E-02
    400,  2.40836E-03,  2.26991E-03,  5.66498E-02
    ...
    830,  9.74306E-07,  9.53411E-08,  0.00000

    and returns it as an *OrderedDict* of *dict* as follows:

    OrderedDict([
    ('field', {'wavelength': 'value', ..., 'wavelength': 'value'}),
    ...,
    ('field', {'wavelength': 'value', ..., 'wavelength': 'value'})])

    Parameters
    ----------
    path : unicode
        Absolute *CSV* file path.
    delimiter : unicode, optional
        *CSV* file content delimiter.
    fields : array_like, optional
        *CSV* file spectral data fields names. If no value is provided the
        first line of the file will be used as spectral data fields names.
    default : numeric, optional
        Default value for fields row with missing value.

    Returns
    -------
    OrderedDict
        *CSV* file content.

    Raises
    ------
    RuntimeError
        If the *CSV* spectral data file doesn't define the appropriate fields.

    Notes
    -----
    -   A *CSV* spectral data file should define at least define two fields:
        one for the wavelengths and one for the associated values of one
        spectral distribution.
    -   If no value is provided for the fields names, the first line of the
        file will be used as spectral data fields names.

    Examples
    --------
    >>> import os
    >>> from pprint import pprint
    >>> csv_file = os.path.join(os.path.dirname(__file__), 'tests',
    ...                         'resources', 'colorchecker_n_ohta.csv')
    >>> sds_data = read_spectral_data_from_csv_file(csv_file)
    >>> pprint(list(sds_data.keys()))
    ['1',
     '2',
     '3',
     '4',
     '5',
     '6',
     '7',
     '8',
     '9',
     '10',
     '11',
     '12',
     '13',
     '14',
     '15',
     '16',
     '17',
     '18',
     '19',
     '20',
     '21',
     '22',
     '23',
     '24']
    """

    with open(path, 'rU') as csv_file:
        reader = csv.DictReader(
            csv_file, delimiter=str(delimiter), fieldnames=fields)
        if len(reader.fieldnames) == 1:
            raise RuntimeError(('A "CSV" spectral data file should define '
                                'the following fields: '
                                '("wavelength", "field 1", ..., "field n")!'))

        wavelength = reader.fieldnames[0]
        fields = reader.fieldnames[1:]

        data = OrderedDict(zip(fields, ({} for _ in range(len(fields)))))
        for line in reader:
            for field in fields:
                try:
                    value = DEFAULT_FLOAT_DTYPE(line[field])
                except ValueError:
                    value = default

                data[field][DEFAULT_FLOAT_DTYPE(line[wavelength])] = value
        return data


def read_sds_from_csv_file(path, delimiter=',', fields=None, default=0):
    """
    Reads the spectral data from given *CSV* file and return its content as an
    *OrderedDict* of :class:`colour.SpectralDistribution` classes.

    Parameters
    ----------
    path : unicode
        Absolute *CSV* file path.
    delimiter : unicode, optional
        *CSV* file content delimiter.
    fields : array_like, optional
        *CSV* file spectral data fields names. If no value is provided the
        first line of the file will be used for as spectral data fields names.
    default : numeric
        Default value for fields row with missing value.

    Returns
    -------
    OrderedDict
        :class:`colour.SpectralDistribution` classes of given *CSV* file.

    Examples
    --------
    >>> from colour.utilities import numpy_print_options
    >>> import os
    >>> csv_file = os.path.join(os.path.dirname(__file__), 'tests',
    ...                         'resources', 'colorchecker_n_ohta.csv')
    >>> sds = read_sds_from_csv_file(csv_file)
    >>> print(tuple(sds.keys()))
    ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', \
'14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24')
    >>> with numpy_print_options(suppress=True):
    ...     sds['1']  # doctest: +ELLIPSIS
    SpectralDistribution([[ 380.   ,    0.048],
                          [ 385.   ,    0.051],
                          [ 390.   ,    0.055],
                          [ 395.   ,    0.06 ],
                          [ 400.   ,    0.065],
                          [ 405.   ,    0.068],
                          [ 410.   ,    0.068],
                          [ 415.   ,    0.067],
                          [ 420.   ,    0.064],
                          [ 425.   ,    0.062],
                          [ 430.   ,    0.059],
                          [ 435.   ,    0.057],
                          [ 440.   ,    0.055],
                          [ 445.   ,    0.054],
                          [ 450.   ,    0.053],
                          [ 455.   ,    0.053],
                          [ 460.   ,    0.052],
                          [ 465.   ,    0.052],
                          [ 470.   ,    0.052],
                          [ 475.   ,    0.053],
                          [ 480.   ,    0.054],
                          [ 485.   ,    0.055],
                          [ 490.   ,    0.057],
                          [ 495.   ,    0.059],
                          [ 500.   ,    0.061],
                          [ 505.   ,    0.062],
                          [ 510.   ,    0.065],
                          [ 515.   ,    0.067],
                          [ 520.   ,    0.07 ],
                          [ 525.   ,    0.072],
                          [ 530.   ,    0.074],
                          [ 535.   ,    0.075],
                          [ 540.   ,    0.076],
                          [ 545.   ,    0.078],
                          [ 550.   ,    0.079],
                          [ 555.   ,    0.082],
                          [ 560.   ,    0.087],
                          [ 565.   ,    0.092],
                          [ 570.   ,    0.1  ],
                          [ 575.   ,    0.107],
                          [ 580.   ,    0.115],
                          [ 585.   ,    0.122],
                          [ 590.   ,    0.129],
                          [ 595.   ,    0.134],
                          [ 600.   ,    0.138],
                          [ 605.   ,    0.142],
                          [ 610.   ,    0.146],
                          [ 615.   ,    0.15 ],
                          [ 620.   ,    0.154],
                          [ 625.   ,    0.158],
                          [ 630.   ,    0.163],
                          [ 635.   ,    0.167],
                          [ 640.   ,    0.173],
                          [ 645.   ,    0.18 ],
                          [ 650.   ,    0.188],
                          [ 655.   ,    0.196],
                          [ 660.   ,    0.204],
                          [ 665.   ,    0.213],
                          [ 670.   ,    0.222],
                          [ 675.   ,    0.231],
                          [ 680.   ,    0.242],
                          [ 685.   ,    0.251],
                          [ 690.   ,    0.261],
                          [ 695.   ,    0.271],
                          [ 700.   ,    0.282],
                          [ 705.   ,    0.294],
                          [ 710.   ,    0.305],
                          [ 715.   ,    0.318],
                          [ 720.   ,    0.334],
                          [ 725.   ,    0.354],
                          [ 730.   ,    0.372],
                          [ 735.   ,    0.392],
                          [ 740.   ,    0.409],
                          [ 745.   ,    0.42 ],
                          [ 750.   ,    0.436],
                          [ 755.   ,    0.45 ],
                          [ 760.   ,    0.462],
                          [ 765.   ,    0.465],
                          [ 770.   ,    0.448],
                          [ 775.   ,    0.432],
                          [ 780.   ,    0.421]],
                         interpolator=SpragueInterpolator,
                         interpolator_args={},
                         extrapolator=Extrapolator,
                         extrapolator_args={...})
    """

    data = read_spectral_data_from_csv_file(path, delimiter, fields, default)

    sds = OrderedDict(((key, SpectralDistribution(value, name=key))
                       for key, value in data.items()))
    return sds


def write_sds_to_csv_file(sds, path, delimiter=',', fields=None):
    """
    Writes the given spectral distributions to given *CSV* file.

    Parameters
    ----------
    sds : dict
        Spectral distributions to write.
    path : unicode
        Absolute *CSV* file path.
    delimiter : unicode, optional
        *CSV* file content delimiter.
    fields : array_like, optional
        *CSV* file spectral data fields names. If no value is provided the
        order of fields will be the one defined by the sorted spectral
        distributions *dict*.

    Returns
    -------
    bool
        Definition success.

    Raises
    ------
    RuntimeError
        If the given spectral distributions have different shapes.
    """

    if len(sds) != 1:
        shapes = [sd.shape for sd in sds.values()]
        if not all(shape == shapes[0] for shape in shapes):
            raise RuntimeError(('Cannot write spectral distributions '
                                'with different shapes to "CSV" file!'))

    wavelengths = tuple(sds.values())[0].wavelengths
    with open(path, 'w') as csv_file:
        fields = list(fields) if fields is not None else sorted(sds.keys())
        writer = csv.DictWriter(
            csv_file,
            delimiter=str(delimiter),
            fieldnames=['wavelength'] + fields,
            lineterminator='\n')

        writer.writeheader()

        for wavelength in wavelengths:
            row = {'wavelength': wavelength}
            row.update(
                dict((field, sds[field][wavelength]) for field in fields))
            writer.writerow(row)

    return True
