"""
Sony .spi1d LUT Format Input / Output Utilities
===============================================

Define the *Sony* *.spi1d* *LUT* format related input / output utilities
objects:

-   :func:`colour.io.read_LUT_SonySPI1D`
-   :func:`colour.io.write_LUT_SonySPI1D`
"""

from __future__ import annotations

import typing

import numpy as np

if typing.TYPE_CHECKING:
    from colour.hints import PathLike

from colour.io.luts import LUT1D, LUT3x1D, LUTSequence
from colour.io.luts.common import path_to_title
from colour.utilities import (
    as_float_array,
    as_int_scalar,
    attest,
    format_array_as_row,
    usage_warning,
)

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
    "read_LUT_SonySPI1D",
    "write_LUT_SonySPI1D",
]


def read_LUT_SonySPI1D(path: str | PathLike) -> LUT1D | LUT3x1D:
    """
    Read the specified *Sony* *.spi1d* *LUT* file.

    Parameters
    ----------
    path
        *LUT* file path.

    Returns
    -------
    :class:`colour.LUT1D` or :class:`colour.LUT3x1D`
        :class:`LUT1D` or :class:`LUT3x1D` class instance.

    Examples
    --------
    Reading a 1D *Sony* *.spi1d* *LUT*:

    >>> import os
    >>> path = os.path.join(
    ...     os.path.dirname(__file__),
    ...     "tests",
    ...     "resources",
    ...     "sony_spi1d",
    ...     "eotf_sRGB_1D.spi1d",
    ... )
    >>> print(read_LUT_SonySPI1D(path))
    LUT1D - eotf sRGB 1D
    --------------------
    <BLANKLINE>
    Dimensions : 1
    Domain     : [-0.1  1.5]
    Size       : (16,)
    Comment 01 : Generated by "Colour 0.3.11".
    Comment 02 : "colour.models.eotf_sRGB".

    Reading a 3x1D *Sony* *.spi1d* *LUT*:

    >>> path = os.path.join(
    ...     os.path.dirname(__file__),
    ...     "tests",
    ...     "resources",
    ...     "sony_spi1d",
    ...     "eotf_sRGB_3x1D.spi1d",
    ... )
    >>> print(read_LUT_SonySPI1D(path))
    LUT3x1D - eotf sRGB 3x1D
    ------------------------
    <BLANKLINE>
    Dimensions : 2
    Domain     : [[-0.1 -0.1 -0.1]
                  [ 1.5  1.5  1.5]]
    Size       : (16, 3)
    Comment 01 : Generated by "Colour 0.3.11".
    Comment 02 : "colour.models.eotf_sRGB".
    """

    title = path_to_title(path)
    domain_min, domain_max = np.array([0, 1])
    dimensions = 1
    data = []

    comments = []

    with open(path) as spi1d_file:
        lines = filter(None, (line.strip() for line in spi1d_file))
        for line in lines:
            if line.startswith("#"):
                comments.append(line[1:].strip())
                continue

            tokens = line.split()
            if tokens[0] == "Version":
                continue
            if tokens[0] == "From":
                domain_min, domain_max = as_float_array(tokens[1:])
            elif tokens[0] == "Length":
                continue
            elif tokens[0] == "Components":
                component = as_int_scalar(tokens[1])
                attest(
                    component in (1, 3),
                    "Only 1 or 3 components are supported!",
                )

                dimensions = 1 if component == 1 else 2
            elif tokens[0] in ("{", "}"):
                continue
            else:
                data.append(tokens)

    table = as_float_array(data)

    LUT: LUT1D | LUT3x1D
    if dimensions == 1:
        LUT = LUT1D(
            np.squeeze(table),
            title,
            np.array([domain_min, domain_max]),
            comments=comments,
        )
    elif dimensions == 2:
        LUT = LUT3x1D(
            table,
            title,
            np.array(
                [
                    [domain_min, domain_min, domain_min],
                    [domain_max, domain_max, domain_max],
                ]
            ),
            comments=comments,
        )

    return LUT


def write_LUT_SonySPI1D(
    LUT: LUT1D | LUT3x1D | LUTSequence, path: str | PathLike, decimals: int = 7
) -> bool:
    """
    Write the specified *LUT* to the specified *Sony* *.spi1d* *LUT* file.

    Parameters
    ----------
    LUT
        :class:`LUT1D`, :class:`LUT3x1D` or :class:`LUTSequence` class
        instance to write at the specified path.
    path
        *LUT* file path.
    decimals
        Number of decimal places for formatting numeric values.

    Returns
    -------
    :class:`bool`
        Whether the write operation was successful.

    Warnings
    --------
    -   If a :class:`LUTSequence` class instance is passed as ``LUT``,
        the first *LUT* in the *LUT* sequence will be used.

    Examples
    --------
    Writing a 1D *Sony* *.spi1d* *LUT*:

    >>> from colour.algebra import spow
    >>> domain = np.array([-0.1, 1.5])
    >>> LUT = LUT1D(
    ...     spow(LUT1D.linear_table(16), 1 / 2.2),
    ...     "My LUT",
    ...     domain,
    ...     comments=["A first comment.", "A second comment."],
    ... )
    >>> write_LUT_SonySPI1D(LUT, "My_LUT.spi1d")  # doctest: +SKIP

    Writing a 3x1D *Sony* *.spi1d* *LUT*:

    >>> domain = np.array([[-0.1, -0.1, -0.1], [1.5, 1.5, 1.5]])
    >>> LUT = LUT3x1D(
    ...     spow(LUT3x1D.linear_table(16), 1 / 2.2),
    ...     "My LUT",
    ...     domain,
    ...     comments=["A first comment.", "A second comment."],
    ... )
    >>> write_LUT_SonySPI1D(LUT, "My_LUT.spi1d")  # doctest: +SKIP
    """

    path = str(path)

    if isinstance(LUT, LUTSequence):
        usage_warning(
            f'"LUT" is a "LUTSequence" instance was passed, using first '
            f'sequence "LUT":\n{LUT}'
        )
        LUTxD = LUT[0]
    else:
        LUTxD = LUT

    attest(not LUTxD.is_domain_explicit(), '"LUT" domain must be implicit!')

    attest(
        isinstance(LUTxD, (LUT1D, LUT3x1D)),
        '"LUT" must be either a 1D or 3x1D "LUT"!',
    )

    is_1D = isinstance(LUTxD, LUT1D)

    if is_1D:
        domain = LUTxD.domain
    else:
        domain = np.unique(LUTxD.domain)

        attest(len(domain) == 2, 'Non-uniform "LUT" domain is unsupported!')

    with open(path, "w") as spi1d_file:
        spi1d_file.write("Version 1\n")

        spi1d_file.write(f"From {format_array_as_row(domain, decimals)}\n")

        spi1d_file.write(
            f"Length {LUTxD.table.size if is_1D else LUTxD.table.shape[0]}\n"
        )

        spi1d_file.write(f"Components {1 if is_1D else 3}\n")

        spi1d_file.write("{\n")
        spi1d_file.writelines(
            f" {format_array_as_row(array, decimals)}\n" for array in LUTxD.table
        )
        spi1d_file.write("}\n")

        if LUTxD.comments:
            spi1d_file.writelines(f"# {comment}\n" for comment in LUTxD.comments)

    return True
