"""
:math:`IC_TC_P` Colour Encoding
===============================

Define the :math:`IC_TC_P` colour encoding related transformations:

-   :func:`colour.RGB_to_ICtCp`
-   :func:`colour.ICtCp_to_RGB`
-   :func:`colour.XYZ_to_ICtCp`
-   :func:`colour.ICtCp_to_XYZ`

References
----------
-   :cite:`Dolby2016a` : Dolby. (2016). WHAT IS ICtCp? - INTRODUCTION.
    https://www.dolby.com/us/en/technologies/dolby-vision/ICtCp-white-paper.pdf
-   :cite:`InternationalTelecommunicationUnion2018` : International
    Telecommunication Union. (2018). Recommendation ITU-R BT.2100-2 - Image
    parameter values for high dynamic range television for use in production
    and international programme exchange.
    https://www.itu.int/dms_pubrec/itu-r/rec/bt/\
R-REC-BT.2100-2-201807-I!!PDF-E.pdf
-   :cite:`Lu2016c` : Lu, T., Pu, F., Yin, P., Chen, T., Husak, W., Pytlarz,
    J., Atkins, R., Froehlich, J., & Su, G.-M. (2016). ITP Colour Space and Its
    Compression Performance for High Dynamic Range and Wide Colour Gamut Video
    Distribution. ZTE Communications, 14(1), 32-38.
"""

from __future__ import annotations

import typing

import numpy as np

from colour.algebra import vecmul
from colour.colorimetry import CCS_ILLUMINANTS

if typing.TYPE_CHECKING:
    from colour.hints import Literal, LiteralChromaticAdaptationTransform

from colour.hints import (  # noqa: TC001
    ArrayLike,
    Domain1,
    NDArrayFloat,
    Range1,
)
from colour.models.rgb import RGB_COLOURSPACES, RGB_to_XYZ, XYZ_to_RGB
from colour.models.rgb.transfer_functions import (
    eotf_inverse_ST2084,
    eotf_ST2084,
    oetf_BT2100_HLG,
    oetf_inverse_BT2100_HLG,
)
from colour.utilities import as_float_array, domain_range_scale, validate_method

__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__ = [
    "MATRIX_ICTCP_RGB_TO_LMS",
    "MATRIX_ICTCP_LMS_TO_RGB",
    "MATRIX_ICTCP_LMS_P_TO_ICTCP",
    "MATRIX_ICTCP_ICTCP_TO_LMS_P",
    "MATRIX_ICTCP_LMS_P_TO_ICTCP_BT2100_HLG_2",
    "MATRIX_ICTCP_ICTCP_TO_LMS_P_BT2100_HLG_2",
    "RGB_to_ICtCp",
    "ICtCp_to_RGB",
    "XYZ_to_ICtCp",
    "ICtCp_to_XYZ",
]

MATRIX_ICTCP_RGB_TO_LMS: NDArrayFloat = (
    np.array(
        [
            [1688, 2146, 262],
            [683, 2951, 462],
            [99, 309, 3688],
        ]
    )
    / 4096
)
"""*ITU-R BT.2020* colourspace to normalised cone responses matrix."""

MATRIX_ICTCP_LMS_TO_RGB: NDArrayFloat = np.linalg.inv(MATRIX_ICTCP_RGB_TO_LMS)
"""
:math:`IC_TC_P` colourspace normalised cone responses to *ITU-R BT.2020*
colourspace matrix.
"""

MATRIX_ICTCP_LMS_P_TO_ICTCP: NDArrayFloat = (
    np.array(
        [
            [2048, 2048, 0],
            [6610, -13613, 7003],
            [17933, -17390, -543],
        ]
    )
    / 4096
)
"""
:math:`LMS_p` *SMPTE ST 2084:2014* encoded normalised cone responses to
:math:`IC_TC_P` colour encoding matrix.
"""

MATRIX_ICTCP_ICTCP_TO_LMS_P: NDArrayFloat = np.linalg.inv(MATRIX_ICTCP_LMS_P_TO_ICTCP)
"""
:math:`IC_TC_P` colour encoding to :math:`LMS_p` *SMPTE ST 2084:2014* encoded
normalised cone responses matrix.
"""

MATRIX_ICTCP_LMS_P_TO_ICTCP_BT2100_HLG_2: NDArrayFloat = (
    np.array(
        [
            [2048, 2048, 0],
            [3625, -7465, 3840],
            [9500, -9212, -288],
        ]
    )
    / 4096
)
"""
:math:`LMS_p` *SMPTE ST 2084:2014* encoded normalised cone responses to
:math:`IC_TC_P` colour encoding matrix as specified in *ITU-R BT.2100-2*.
"""

MATRIX_ICTCP_ICTCP_TO_LMS_P_BT2100_HLG_2: NDArrayFloat = np.linalg.inv(
    MATRIX_ICTCP_LMS_P_TO_ICTCP_BT2100_HLG_2
)
"""
:math:`IC_TC_P` colour encoding to :math:`LMS_p` *SMPTE ST 2084:2014* encoded
normalised cone responses matrix as specified in *ITU-R BT.2100-2*.
"""


def RGB_to_ICtCp(
    RGB: ArrayLike,
    method: (
        Literal[
            "Dolby 2016",
            "ITU-R BT.2100-1 HLG",
            "ITU-R BT.2100-1 PQ",
            "ITU-R BT.2100-2 HLG",
            "ITU-R BT.2100-2 PQ",
        ]
        | str
    ) = "Dolby 2016",
    L_p: float = 10000,
) -> Range1:
    """
    Convert from *ITU-R BT.2020* colourspace to :math:`IC_TC_P` colour
    encoding.

    Parameters
    ----------
    RGB
        *ITU-R BT.2020* colourspace array.
    method
        Computation method. *Recommendation ITU-R BT.2100* defines multiple
        variants of the :math:`IC_TC_P` colour encoding:

        -   *ITU-R BT.2100-1*

            -   *SMPTE ST 2084:2014* inverse electro-optical transfer
                function (EOTF) and the :math:`IC_TC_P` matrix from
                :cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
                *ITU-R BT.2100-2 PQ* methods.
            -   *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
                transfer function (OETF) and the :math:`IC_TC_P` matrix from
                :cite:`Dolby2016a`: *ITU-R BT.2100-1 HLG* method.

        -   *ITU-R BT.2100-2*

            -   *SMPTE ST 2084:2014* inverse electro-optical transfer
                function (EOTF) and the :math:`IC_TC_P` matrix from
                :cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
                *ITU-R BT.2100-2 PQ* methods.
            -   *Recommendation ITU-R BT.2100* *Reference HLG* opto-electrical
                transfer function (OETF) and a custom :math:`IC_TC_P` matrix
                from :cite:`InternationalTelecommunicationUnion2018`:
                *ITU-R BT.2100-2 HLG* method.

    L_p
        Display peak luminance :math:`cd/m^2` for *SMPTE ST 2084:2014*
        non-linear encoding. This parameter should remain at its default
        :math:`10000 cd/m^2` value for practical applications. It is exposed
        to enable the definition to be used as a fitting function.

    Returns
    -------
    :class:`numpy.ndarray`
        :math:`IC_TC_P` colour encoding array.

    Warnings
    --------
    The underlying *SMPTE ST 2084:2014* transfer function is an absolute
    transfer function.

    Notes
    -----
    -   The *ITU-R BT.2100-1 PQ* and *ITU-R BT.2100-2 PQ* methods are
        aliases for the *Dolby 2016* method.
    -   The underlying *SMPTE ST 2084:2014* transfer function is an absolute
        transfer function, thus the domain and range values for the
        *Reference* and *1* scales are only indicative that the data is not
        affected by scale transformations. The effective domain of
        *SMPTE ST 2084:2014* inverse electro-optical transfer function (EOTF)
        is [0.0001, 10000].

    +------------+-----------------------+------------------+
    | **Domain** | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``RGB``    | ``UN``                | ``UN``           |
    +------------+-----------------------+------------------+

    +------------+-----------------------+------------------+
    | **Range**  | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``ICtCp``  | 1                     | 1                |
    +------------+-----------------------+------------------+

    References
    ----------
    :cite:`Dolby2016a`, :cite:`Lu2016c`

    Examples
    --------
    >>> RGB = np.array([0.45620519, 0.03081071, 0.04091952])
    >>> RGB_to_ICtCp(RGB)  # doctest: +ELLIPSIS
    array([ 0.0735136...,  0.0047525...,  0.0935159...])
    >>> RGB_to_ICtCp(RGB, method="ITU-R BT.2100-2 HLG")  # doctest: +ELLIPSIS
    array([ 0.6256789..., -0.0198449...,  0.3591125...])
    """

    RGB = as_float_array(RGB)
    method = validate_method(
        method,
        (
            "Dolby 2016",
            "ITU-R BT.2100-1 HLG",
            "ITU-R BT.2100-1 PQ",
            "ITU-R BT.2100-2 HLG",
            "ITU-R BT.2100-2 PQ",
        ),
    )

    is_hlg_method = "hlg" in method
    is_BT2100_2_method = "2100-2" in method

    LMS = vecmul(MATRIX_ICTCP_RGB_TO_LMS, RGB)

    with domain_range_scale("ignore"):
        LMS_p = oetf_BT2100_HLG(LMS) if is_hlg_method else eotf_inverse_ST2084(LMS, L_p)

    return (
        vecmul(MATRIX_ICTCP_LMS_P_TO_ICTCP_BT2100_HLG_2, LMS_p)
        if (is_hlg_method and is_BT2100_2_method)
        else vecmul(MATRIX_ICTCP_LMS_P_TO_ICTCP, LMS_p)
    )


def ICtCp_to_RGB(
    ICtCp: Domain1,
    method: (
        Literal[
            "Dolby 2016",
            "ITU-R BT.2100-1 HLG",
            "ITU-R BT.2100-1 PQ",
            "ITU-R BT.2100-2 HLG",
            "ITU-R BT.2100-2 PQ",
        ]
        | str
    ) = "Dolby 2016",
    L_p: float = 10000,
) -> NDArrayFloat:
    """
    Convert from :math:`IC_TC_P` colour encoding to *ITU-R BT.2020*
    colourspace.

    Parameters
    ----------
    ICtCp
        :math:`IC_TC_P` colour encoding array.
    method
        Computation method. *Recommendation ITU-R BT.2100* defines multiple
        variants of the :math:`IC_TC_P` colour encoding:

        -   *ITU-R BT.2100-1*

            -   *SMPTE ST 2084:2014* inverse electro-optical transfer
                function (EOTF) and the :math:`IC_TC_P` matrix from
                :cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
                *ITU-R BT.2100-2 PQ* methods.
            -   *Recommendation ITU-R BT.2100* *Reference HLG*
                opto-electrical transfer function (OETF) and the
                :math:`IC_TC_P` matrix from :cite:`Dolby2016a`:
                *ITU-R BT.2100-1 HLG* method.

        -   *ITU-R BT.2100-2*

            -   *SMPTE ST 2084:2014* inverse electro-optical transfer
                function (EOTF) and the :math:`IC_TC_P` matrix from
                :cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
                *ITU-R BT.2100-2 PQ* methods.
            -   *Recommendation ITU-R BT.2100* *Reference HLG*
                opto-electrical transfer function (OETF) and a custom
                :math:`IC_TC_P` matrix from
                :cite:`InternationalTelecommunicationUnion2018`:
                *ITU-R BT.2100-2 HLG* method.

    L_p
        Display peak luminance :math:`cd/m^2` for *SMPTE ST 2084:2014*
        non-linear encoding. This parameter should remain at its default
        :math:`10000 cd/m^2` value for practical applications. It is exposed
        to enable the definition to be used as a fitting function.

    Returns
    -------
    :class:`numpy.ndarray`
        *ITU-R BT.2020* colourspace array.

    Warnings
    --------
    The underlying *SMPTE ST 2084:2014* transfer function is an absolute
    transfer function.

    Notes
    -----
    -   The *ITU-R BT.2100-1 PQ* and *ITU-R BT.2100-2 PQ* methods are
        aliases for the *Dolby 2016* method.
    -   The underlying *SMPTE ST 2084:2014* transfer function is an
        absolute transfer function, thus the domain and range values for
        the *Reference* and *1* scales are only indicative that the data is
        not affected by scale transformations.

    +------------+-----------------------+------------------+
    | **Domain** | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``ICtCp``  | 1                     | 1                |
    +------------+-----------------------+------------------+

    +------------+-----------------------+------------------+
    | **Range**  | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``RGB``    | ``UN``                | ``UN``           |
    +------------+-----------------------+------------------+

    References
    ----------
    :cite:`Dolby2016a`, :cite:`Lu2016c`

    Examples
    --------
    >>> ICtCp = np.array([0.07351364, 0.00475253, 0.09351596])
    >>> ICtCp_to_RGB(ICtCp)  # doctest: +ELLIPSIS
    array([ 0.4562052...,  0.0308107...,  0.0409195...])
    >>> ICtCp = np.array([0.62567899, -0.01984490, 0.35911259])
    >>> ICtCp_to_RGB(ICtCp, method="ITU-R BT.2100-2 HLG")  # doctest: +ELLIPSIS
    array([ 0.4562052...,  0.0308107...,  0.0409195...])
    """

    ICtCp = as_float_array(ICtCp)
    method = validate_method(
        method,
        (
            "Dolby 2016",
            "ITU-R BT.2100-1 HLG",
            "ITU-R BT.2100-1 PQ",
            "ITU-R BT.2100-2 HLG",
            "ITU-R BT.2100-2 PQ",
        ),
    )

    is_hlg_method = "hlg" in method
    is_BT2100_2_method = "2100-2" in method

    LMS_p = (
        vecmul(MATRIX_ICTCP_ICTCP_TO_LMS_P_BT2100_HLG_2, ICtCp)
        if (is_hlg_method and is_BT2100_2_method)
        else vecmul(MATRIX_ICTCP_ICTCP_TO_LMS_P, ICtCp)
    )

    with domain_range_scale("ignore"):
        LMS = (
            oetf_inverse_BT2100_HLG(LMS_p) if is_hlg_method else eotf_ST2084(LMS_p, L_p)
        )

    return vecmul(MATRIX_ICTCP_LMS_TO_RGB, LMS)


def XYZ_to_ICtCp(
    XYZ: ArrayLike,
    illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
        "D65"
    ],
    chromatic_adaptation_transform: (
        LiteralChromaticAdaptationTransform | str | None
    ) = "CAT02",
    method: (
        Literal[
            "Dolby 2016",
            "ITU-R BT.2100-1 HLG",
            "ITU-R BT.2100-1 PQ",
            "ITU-R BT.2100-2 HLG",
            "ITU-R BT.2100-2 PQ",
        ]
        | str
    ) = "Dolby 2016",
    L_p: float = 10000,
) -> Range1:
    """
    Convert from *CIE XYZ* tristimulus values to :math:`IC_TC_P` colour
    encoding.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values.
    illuminant
        Source illuminant chromaticity coordinates.
    chromatic_adaptation_transform
        *Chromatic adaptation* transform.
    method
        Computation method. *Recommendation ITU-R BT.2100* defines multiple
        variants of the :math:`IC_TC_P` colour encoding:

        -   *ITU-R BT.2100-1*

            -   *SMPTE ST 2084:2014* inverse electro-optical transfer
                function (EOTF) and the :math:`IC_TC_P` matrix from
                :cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
                *ITU-R BT.2100-2 PQ* methods.
            -   *Recommendation ITU-R BT.2100* *Reference HLG*
                opto-electrical transfer function (OETF) and the
                :math:`IC_TC_P` matrix from :cite:`Dolby2016a`:
                *ITU-R BT.2100-1 HLG* method.

        -   *ITU-R BT.2100-2*

            -   *SMPTE ST 2084:2014* inverse electro-optical transfer
                function (EOTF) and the :math:`IC_TC_P` matrix from
                :cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
                *ITU-R BT.2100-2 PQ* methods.
            -   *Recommendation ITU-R BT.2100* *Reference HLG*
                opto-electrical transfer function (OETF) and a custom
                :math:`IC_TC_P` matrix from
                :cite:`InternationalTelecommunicationUnion2018`:
                *ITU-R BT.2100-2 HLG* method.

    L_p
        Display peak luminance :math:`cd/m^2` for *SMPTE ST 2084:2014*
        non-linear encoding. This parameter should remain at its default
        :math:`10000 cd/m^2` value for practical applications. It is exposed
        to enable the definition to be used as a fitting function.

    Returns
    -------
    :class:`numpy.ndarray`
        :math:`IC_TC_P` colour encoding array.

    Warnings
    --------
    The underlying *SMPTE ST 2084:2014* transfer function is an absolute
    transfer function.

    Notes
    -----
    -   The *ITU-R BT.2100-1 PQ* and *ITU-R BT.2100-2 PQ* methods are
        aliases for the *Dolby 2016* method.
    -   The underlying *SMPTE ST 2084:2014* transfer function is an
        absolute transfer function, thus the domain and range values for
        the *Reference* and *1* scales are only indicative that the data
        is not affected by scale transformations. The effective domain of
        *SMPTE ST 2084:2014* inverse electro-optical transfer function
        (EOTF) is [0.0001, 10000].

    +------------+-----------------------+------------------+
    | **Domain** | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``XYZ``    | ``UN``                | ``UN``           |
    +------------+-----------------------+------------------+

    +------------+-----------------------+------------------+
    | **Range**  | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``ICtCp``  | 1                     | 1                |
    +------------+-----------------------+------------------+

    References
    ----------
    :cite:`Dolby2016a`, :cite:`Lu2016c`

    Examples
    --------
    >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
    >>> XYZ_to_ICtCp(XYZ)  # doctest: +ELLIPSIS
    array([ 0.0685809..., -0.0028384...,  0.0602098...])
    >>> XYZ_to_ICtCp(XYZ, method="ITU-R BT.2100-2 HLG")  # doctest: +ELLIPSIS
    array([ 0.5924279..., -0.0374073...,  0.2512267...])
    """

    RGB = XYZ_to_RGB(
        XYZ,
        RGB_COLOURSPACES["ITU-R BT.2020"],
        illuminant,
        chromatic_adaptation_transform,
    )

    return RGB_to_ICtCp(RGB, method, L_p)


def ICtCp_to_XYZ(
    ICtCp: Domain1,
    illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
        "D65"
    ],
    chromatic_adaptation_transform: (
        LiteralChromaticAdaptationTransform | str | None
    ) = "CAT02",
    method: (
        Literal[
            "Dolby 2016",
            "ITU-R BT.2100-1 HLG",
            "ITU-R BT.2100-1 PQ",
            "ITU-R BT.2100-2 HLG",
            "ITU-R BT.2100-2 PQ",
        ]
        | str
    ) = "Dolby 2016",
    L_p: float = 10000,
) -> NDArrayFloat:
    """
    Convert from :math:`IC_TC_P` colour encoding to *CIE XYZ* tristimulus
    values.

    Parameters
    ----------
    ICtCp
        :math:`IC_TC_P` colour encoding array.
    illuminant
        Source illuminant chromaticity coordinates.
    chromatic_adaptation_transform
        *Chromatic adaptation* transform.
    method
        Computation method. *Recommendation ITU-R BT.2100* defines multiple
        variants of the :math:`IC_TC_P` colour encoding:

        -   *ITU-R BT.2100-1*

            -   *SMPTE ST 2084:2014* inverse electro-optical transfer
                function (EOTF) and the :math:`IC_TC_P` matrix from
                :cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
                *ITU-R BT.2100-2 PQ* methods.
            -   *Recommendation ITU-R BT.2100* *Reference HLG*
                opto-electrical transfer function (OETF) and the
                :math:`IC_TC_P` matrix from :cite:`Dolby2016a`:
                *ITU-R BT.2100-1 HLG* method.

        -   *ITU-R BT.2100-2*

            -   *SMPTE ST 2084:2014* inverse electro-optical transfer
                function (EOTF) and the :math:`IC_TC_P` matrix from
                :cite:`Dolby2016a`: *Dolby 2016*, *ITU-R BT.2100-1 PQ*,
                *ITU-R BT.2100-2 PQ* methods.
            -   *Recommendation ITU-R BT.2100* *Reference HLG*
                opto-electrical transfer function (OETF) and a custom
                :math:`IC_TC_P` matrix from
                :cite:`InternationalTelecommunicationUnion2018`:
                *ITU-R BT.2100-2 HLG* method.

    L_p
        Display peak luminance :math:`cd/m^2` for *SMPTE ST 2084:2014*
        non-linear encoding. This parameter should remain at its default
        :math:`10000 cd/m^2` value for practical applications. It is exposed
        to enable the definition to be used as a fitting function.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE XYZ* tristimulus values.

    Warnings
    --------
    The underlying *SMPTE ST 2084:2014* transfer function is an absolute
    transfer function.

    Notes
    -----
    -   The *ITU-R BT.2100-1 PQ* and *ITU-R BT.2100-2 PQ* methods are
        aliases for the *Dolby 2016* method.
    -   The underlying *SMPTE ST 2084:2014* transfer function is an absolute
        transfer function, thus the domain and range values for the
        *Reference* and *1* scales are only indicative that the data is not
        affected by scale transformations.

    +------------+-----------------------+------------------+
    | **Domain** | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``ICtCp``  | 1                     | 1                |
    +------------+-----------------------+------------------+

    +------------+-----------------------+------------------+
    | **Range**  | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``XYZ``    | ``UN``                | ``UN``           |
    +------------+-----------------------+------------------+

    References
    ----------
    :cite:`Dolby2016a`, :cite:`Lu2016c`

    Examples
    --------
    >>> ICtCp = np.array([0.06858097, -0.00283842, 0.06020983])
    >>> ICtCp_to_XYZ(ICtCp)  # doctest: +ELLIPSIS
    array([ 0.2065400...,  0.1219722...,  0.0513695...])
    >>> ICtCp = np.array([0.59242792, -0.03740730, 0.25122675])
    >>> ICtCp_to_XYZ(ICtCp, method="ITU-R BT.2100-2 HLG")  # doctest: +ELLIPSIS
    array([ 0.2065400...,  0.1219722...,  0.0513695...])
    """

    RGB = ICtCp_to_RGB(ICtCp, method, L_p)

    return RGB_to_XYZ(
        RGB,
        RGB_COLOURSPACES["ITU-R BT.2020"],
        illuminant,
        chromatic_adaptation_transform,
    )
