"""
Provide chromatic adaptation models that predict how colours appear under
different illumination conditions.

References
----------
-   :cite:`CIETC1-321994b` : CIE TC 1-32. (1994). CIE 109-1994 A Method of
    Predicting Corresponding Colours under Different Chromatic and Illuminance
    Adaptations. Commission Internationale de l'Eclairage.
    ISBN:978-3-900734-51-0
-   :cite:`Fairchild1991a` : Fairchild, M. D. (1991). Formulation and testing
    of an incomplete-chromatic-adaptation model. Color Research & Application,
    16(4), 243-250. doi:10.1002/col.5080160406
-   :cite:`Fairchild2013s` : Fairchild, M. D. (2013). FAIRCHILD'S 1990 MODEL.
    In Color Appearance Models (3rd ed., pp. 4418-4495). Wiley. ISBN:B00DAYO8E2
-   :cite:`Fairchild2013t` : Fairchild, M. D. (2013). Chromatic Adaptation
    Models. In Color Appearance Models (3rd ed., pp. 4179-4252). Wiley.
    ISBN:B00DAYO8E2
-   :cite:`Fairchild2020` : Fairchild, M. D. (2020). Von Kries 2020: Evolution
    of degree of chromatic adaptation. Color and Imaging Conference, 28(1),
    252-257. doi:10.2352/issn.2169-2629.2020.28.40
-   :cite:`Li2002a` : Li, C., Luo, M. R., Rigg, B., & Hunt, R. W. G. (2002).
    CMC 2000 chromatic adaptation transform: CMCCAT2000. Color Research &
    Application, 27(1), 49-58. doi:10.1002/col.10005
-   :cite:`Li2025` : Li, M. (2025). One Step CAT16 Chromatic Adaptation
    Transform. https://github.com/colour-science/colour/pull/1349\
#issuecomment-3058339414
-   :cite:`Westland2012k` : Westland, S., Ripamonti, C., & Cheung, V. (2012).
    CMCCAT2000. In Computational Colour Science Using MATLAB (2nd ed., pp.
    83-86). ISBN:978-0-470-66569-5
-   :cite:`Zhai2018` : Zhai, Q., & Luo, M. R. (2018). Study of chromatic
    adaptation via neutral white matches on different viewing media. Optics
    Express, 26(6), 7724. doi:10.1364/OE.26.007724
"""

from __future__ import annotations

import typing

if typing.TYPE_CHECKING:
    from colour.hints import Any, ArrayLike, Literal, NDArrayFloat

# isort: split

from colour.utilities import (
    CanonicalMapping,
    as_float_array,
    filter_kwargs,
    get_domain_range_scale,
    validate_method,
)

# isort: split

from .datasets import (
    CAT_BIANCO2010,
    CAT_BRADFORD,
    CAT_CAT02,
    CAT_CAT02_BRILL2008,
    CAT_CAT16,
    CAT_CMCCAT97,
    CAT_CMCCAT2000,
    CAT_FAIRCHILD,
    CAT_PC_BIANCO2010,
    CAT_SHARP,
    CAT_VON_KRIES,
    CAT_XYZ_SCALING,
    CHROMATIC_ADAPTATION_TRANSFORMS,
)

# isort: split

from .vonkries import (
    chromatic_adaptation_VonKries,
    matrix_chromatic_adaptation_VonKries,
)

# isort: split

from .fairchild1990 import chromatic_adaptation_Fairchild1990
from .fairchild2020 import (
    CONDITIONS_DEGREE_OF_ADAPTATION_VK20,
    chromatic_adaptation_vK20,
    matrix_chromatic_adaptation_vk20,
)

# isort: split

from .cie1994 import chromatic_adaptation_CIE1994
from .cmccat2000 import (
    VIEWING_CONDITIONS_CMCCAT2000,
    InductionFactors_CMCCAT2000,
    chromatic_adaptation_CMCCAT2000,
    chromatic_adaptation_forward_CMCCAT2000,
    chromatic_adaptation_inverse_CMCCAT2000,
)
from .zhai2018 import chromatic_adaptation_Zhai2018

# isort: split

from .li2025 import CAT_CAT16_INVERSE, chromatic_adaptation_Li2025

__all__ = [
    "CAT_BIANCO2010",
    "CAT_BRADFORD",
    "CAT_CAT02",
    "CAT_CAT02_BRILL2008",
    "CAT_CAT16",
    "CAT_CMCCAT97",
    "CAT_CMCCAT2000",
    "CAT_FAIRCHILD",
    "CAT_PC_BIANCO2010",
    "CAT_SHARP",
    "CAT_VON_KRIES",
    "CAT_XYZ_SCALING",
    "CHROMATIC_ADAPTATION_TRANSFORMS",
]
__all__ += [
    "chromatic_adaptation_VonKries",
    "matrix_chromatic_adaptation_VonKries",
]
__all__ += [
    "chromatic_adaptation_Fairchild1990",
]
__all__ += [
    "CONDITIONS_DEGREE_OF_ADAPTATION_VK20",
    "chromatic_adaptation_vK20",
    "matrix_chromatic_adaptation_vk20",
]
__all__ += [
    "chromatic_adaptation_CIE1994",
]
__all__ += [
    "VIEWING_CONDITIONS_CMCCAT2000",
    "InductionFactors_CMCCAT2000",
    "chromatic_adaptation_CMCCAT2000",
    "chromatic_adaptation_forward_CMCCAT2000",
    "chromatic_adaptation_inverse_CMCCAT2000",
]
__all__ += [
    "chromatic_adaptation_Zhai2018",
]
__all__ += [
    "CAT_CAT16_INVERSE",
    "chromatic_adaptation_Li2025",
]

CHROMATIC_ADAPTATION_METHODS: CanonicalMapping = CanonicalMapping(
    {
        "CIE 1994": chromatic_adaptation_CIE1994,
        "CMCCAT2000": chromatic_adaptation_CMCCAT2000,
        "Fairchild 1990": chromatic_adaptation_Fairchild1990,
        "Li 2025": chromatic_adaptation_Li2025,
        "Von Kries": chromatic_adaptation_VonKries,
        "Zhai 2018": chromatic_adaptation_Zhai2018,
        "vK20": chromatic_adaptation_vK20,
    }
)
CHROMATIC_ADAPTATION_METHODS.__doc__ = """
Supported chromatic adaptation methods.

References
----------
:cite:`CIETC1-321994b`, :cite:`Fairchild1991a`, :cite:`Fairchild2013s`,
:cite:`Fairchild2013t`, :cite:`Fairchild2020`, :cite:`Li2002a`,
:cite:`Li2025`, :cite:`Westland2012k`, :cite:`Zhai2018`
"""


def chromatic_adaptation(
    XYZ: ArrayLike,
    XYZ_w: ArrayLike,
    XYZ_wr: ArrayLike,
    method: (
        Literal[
            "CIE 1994",
            "CMCCAT2000",
            "Fairchild 1990",
            "Li 2025",
            "Von Kries",
            "Zhai 2018",
            "vK20",
        ]
        | str
    ) = "Von Kries",
    **kwargs: Any,
) -> NDArrayFloat:
    """
    Adapt the specified stimulus *CIE XYZ* tristimulus values from test
    viewing conditions to reference viewing conditions using the specified
    chromatic adaptation method.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values of stimulus to adapt.
    XYZ_w
        Test viewing condition *CIE XYZ* tristimulus values of the
        whitepoint.
    XYZ_wr
        Reference viewing condition *CIE XYZ* tristimulus values of the
        whitepoint.
    method
        Computation method.

    Other Parameters
    ----------------
    E_o1
        {:func:`colour.adaptation.chromatic_adaptation_CIE1994`},
        Test illuminance :math:`E_{o1}` in :math:`lux`.
    E_o2
        {:func:`colour.adaptation.chromatic_adaptation_CIE1994`},
        Reference illuminance :math:`E_{o2}` in :math:`lux`.
    n
        {:func:`colour.adaptation.chromatic_adaptation_CIE1994`},
        Noise component in fundamental primary system.
    Y_o
        {:func:`colour.adaptation.chromatic_adaptation_CIE1994`},
        Luminance factor :math:`Y_o` of achromatic background normalised
        to domain [0.18, 1] in **'Reference'** domain-range scale.
    direction
        {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`},
        Chromatic adaptation direction.
    L_A1
        {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`},
        Luminance of test adapting field :math:`L_{A1}` in
        :math:`cd/m^2`.
    L_A2
        {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`},
        Luminance of reference adapting field :math:`L_{A2}` in
        :math:`cd/m^2`.
    surround
        {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`},
        Surround viewing conditions induction factors.
    discount_illuminant
        {:func:`colour.adaptation.chromatic_adaptation_Fairchild1990`},
        Truth value indicating if the illuminant should be discounted.
    Y_n
        {:func:`colour.adaptation.chromatic_adaptation_Fairchild1990`},
        Luminance :math:`Y_n` of test adapting stimulus in
        :math:`cd/m^2`.
    L_A
        {:func:`colour.adaptation.chromatic_adaptation_Li2025`},
        Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`.
    F_surround
        {:func:`colour.adaptation.chromatic_adaptation_Li2025`},
        Maximum degree of adaptation :math:`F` from surround viewing
        conditions.
    discount_illuminant
        {:func:`colour.adaptation.chromatic_adaptation_Li2025`},
        Truth value indicating if the illuminant should be discounted.
    D_b
        {:func:`colour.adaptation.chromatic_adaptation_Zhai2018`},
        Degree of adaptation :math:`D_\\beta` of input illuminant
        :math:`\\beta`.
    D_d
        {:func:`colour.adaptation.chromatic_adaptation_Zhai2018`},
        Degree of adaptation :math:`D_\\Delta` of output illuminant
        :math:`\\Delta`.
    XYZ_r
        {:func:`colour.adaptation.chromatic_adaptation_vK20`},
        Reference viewing conditions *CIE XYZ* tristimulus values of
        whitepoint.
    coefficients
        {:func:`colour.adaptation.chromatic_adaptation_vK20`},
        *vK20* degree of adaptation coefficients.
    transform
        {:func:`colour.adaptation.chromatic_adaptation_VonKries`,
        :func:`colour.adaptation.chromatic_adaptation_vK20`,
        :func:`colour.adaptation.chromatic_adaptation_Zhai2018`},
        Chromatic adaptation transform.
    XYZ_wo
        {:func:`colour.adaptation.chromatic_adaptation_Zhai2018`},
        Baseline illuminant (:math:`BI`) :math:`o`.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE XYZ* tristimulus values of the stimulus corresponding colour.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | 1                     | 1             |
    +------------+-----------------------+---------------+
    | ``XYZ_w``  | 1                     | 1             |
    +------------+-----------------------+---------------+
    | ``XYZ_wr`` | 1                     | 1             |
    +------------+-----------------------+---------------+
    | ``XYZ_wo`` | 1                     | 1             |
    +------------+-----------------------+---------------+
    | ``Y_o``    | 1                     | 1             |
    +------------+-----------------------+---------------+

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

    References
    ----------
    :cite:`CIETC1-321994b`, :cite:`Fairchild1991a`,
    :cite:`Fairchild2013s`, :cite:`Fairchild2013t`, :cite:`Li2002a`,
    :cite:`Li2025`, :cite:`Westland2012k`

    Examples
    --------
    *Von Kries* chromatic adaptation:

    >>> import numpy as np
    >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
    >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775])
    >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460])
    >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr)
    ... # doctest: +ELLIPSIS
    array([ 0.2163881...,  0.1257    ,  0.0384749...])

    *vK2020* chromatic adaptation:

    >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775])
    >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460])
    >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method="vK20")
    ... # doctest: +ELLIPSIS
    array([ 0.2146884...,  0.1245616...,  0.0466255...])

    *CIE 1994* chromatic adaptation, requires extra *kwargs*:

    >>> XYZ = np.array([0.2800, 0.2126, 0.0527])
    >>> XYZ_w = np.array([1.09867452, 1.00000000, 0.35591556])
    >>> XYZ_wr = np.array([0.95045593, 1.00000000, 1.08905775])
    >>> Y_o = 0.20
    >>> E_o = 1000
    >>> chromatic_adaptation(
    ...     XYZ, XYZ_w, XYZ_wr, method="CIE 1994", Y_o=Y_o, E_o1=E_o, E_o2=E_o
    ... )
    ... # doctest: +ELLIPSIS
    array([ 0.2403379...,  0.2115621...,  0.1764301...])

    *CMCCAT2000* chromatic adaptation, requires extra *kwargs*:

    >>> XYZ = np.array([0.2248, 0.2274, 0.0854])
    >>> XYZ_w = np.array([1.1115, 1.0000, 0.3520])
    >>> XYZ_wr = np.array([0.9481, 1.0000, 1.0730])
    >>> L_A = 200
    >>> chromatic_adaptation(
    ...     XYZ, XYZ_w, XYZ_wr, method="CMCCAT2000", L_A1=L_A, L_A2=L_A
    ... )
    ... # doctest: +ELLIPSIS
    array([ 0.1952698...,  0.2306834...,  0.2497175...])

    *Fairchild (1990)* chromatic adaptation, requires extra *kwargs*:

    >>> XYZ = np.array([0.1953, 0.2307, 0.2497])
    >>> Y_n = 200
    >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method="Fairchild 1990", Y_n=Y_n)
    ... # doctest: +ELLIPSIS
    array([ 0.2332526...,  0.2332455...,  0.7611593...])

    *Li (2025)* chromatic adaptation:

    >>> XYZ = np.array([0.1953, 0.2307, 0.2497])
    >>> L_A = 64
    >>> F_surround = 1.0
    >>> chromatic_adaptation(
    ...     XYZ, XYZ_w, XYZ_wr, method="Li 2025", L_A=L_A, F_surround=F_surround
    ... )
    ... # doctest: +ELLIPSIS
    array([ 0.2039701...,  0.2304747...,  0.6783065...])

    *Zhai and Luo (2018)* chromatic adaptation:

    >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
    >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775])
    >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460])
    >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method="Zhai 2018")
    ... # doctest: +ELLIPSIS
    array([ 0.2163881...,  0.1257    ,  0.0384749...])
    >>> chromatic_adaptation(
    ...     XYZ,
    ...     XYZ_w,
    ...     XYZ_wr,
    ...     method="Zhai 2018",
    ...     D_b=0.9,
    ...     XYZ_wo=np.array([100, 100, 100]),
    ... )
    ... # doctest: +ELLIPSIS
    array([ 0.2152436...,  0.1253522...,  0.0388406...])
    """

    method = validate_method(method, tuple(CHROMATIC_ADAPTATION_METHODS))

    function = CHROMATIC_ADAPTATION_METHODS[method]

    domain_range_reference = get_domain_range_scale() == "reference"
    domain_100 = (
        chromatic_adaptation_CIE1994,
        chromatic_adaptation_CMCCAT2000,
        chromatic_adaptation_Fairchild1990,
        chromatic_adaptation_Li2025,
        chromatic_adaptation_Zhai2018,
    )

    if function in domain_100 and domain_range_reference:
        XYZ = as_float_array(XYZ) * 100
        XYZ_w = as_float_array(XYZ_w) * 100
        XYZ_wr = as_float_array(XYZ_wr) * 100

        if "Y_o" in kwargs:
            kwargs["Y_o"] = kwargs["Y_o"] * 100

        if "XYZ_wo" in kwargs:
            kwargs["XYZ_wo"] = kwargs["XYZ_wo"] * 100

    kwargs.update({"XYZ_w": XYZ_w, "XYZ_wr": XYZ_wr})

    if function is chromatic_adaptation_CIE1994:
        from colour import XYZ_to_xy  # noqa: PLC0415

        kwargs.update({"xy_o1": XYZ_to_xy(XYZ_w), "xy_o2": XYZ_to_xy(XYZ_wr)})
    elif function is chromatic_adaptation_Fairchild1990:
        kwargs.update({"XYZ_n": XYZ_w, "XYZ_r": XYZ_wr})
    elif function is chromatic_adaptation_Li2025:
        kwargs.update({"XYZ_ws": XYZ_w, "XYZ_wd": XYZ_wr})
    elif function is chromatic_adaptation_Zhai2018:
        kwargs.update({"XYZ_wb": XYZ_w, "XYZ_wd": XYZ_wr})
    elif function is chromatic_adaptation_vK20:
        kwargs.update({"XYZ_p": XYZ_w, "XYZ_n": XYZ_wr})

    XYZ_c = function(XYZ, **filter_kwargs(function, **kwargs))

    if function in domain_100 and domain_range_reference:
        XYZ_c /= 100

    return XYZ_c


__all__ += [
    "CHROMATIC_ADAPTATION_METHODS",
    "chromatic_adaptation",
]
