"""
Compositing and RGB blend modes.

https://www.w3.org/TR/compositing/
"""
from __future__ import annotations
from .. spaces import RGBish
from . import porter_duff
from . import blend_modes
from .. import algebra as alg
from ..types import Vector, ColorInput, AnyColor
from typing import Sequence


def apply_compositing(
    color1: Vector,
    color2: Vector,
    blender: blend_modes.Blend | None,
    operator: type[porter_duff.PorterDuff] | None
) -> Vector:
    """Perform the actual blending."""

    # Get the color coordinates
    cra = csa = color1[-1]
    cba = color2[-1]
    coords2 = color2[:-1]
    # Blend color channels if given a blender and both colors are not fully transparent
    coords1 = blender.blend(coords2, color1[:-1]) if blender and csa and cba else color1[:-1]

    # Setup alpha compositing with the current opacity values.
    # Calculate the new opacity.
    # Browsers auto clamp the alpha channel, as does ColorAide,
    # so result alpha must be clamped to undo premultiplication like the browser.
    compositor = None  # type: porter_duff.PorterDuff | None
    if operator is not None:
        compositor = operator(cba, csa)
        cra = alg.clamp(compositor.ao(), 0.0, 1.0)

    # Apply alpha compositing
    i = 0
    for cb, cr in zip(coords2, coords1):
        if compositor:
            color1[i] = compositor.co(cb, cr)
            if cra not in (0, 1):
                color1[i] /= cra
        else:
            color1[i] = cr
        i += 1
    color1[-1] = cra

    return color1


def compose(
    color_cls: type[AnyColor],
    colors: Sequence[ColorInput],
    blend: str | None = 'normal',
    operator: str | None = 'source-over',
    space: str | None = None,
    out_space: str | None = None
) -> AnyColor:
    """Blend colors using the specified blend mode."""

    if not colors:  # pragma: no cover
        raise ValueError('At least one color is required for compositing.')

    # If we are doing non-separable, we are converting to a special space that
    # can only be done from sRGB, so we have to force sRGB anyway.
    if space is None:
        space = 'srgb'
    if out_space is None:
        out_space = space

    if not isinstance(color_cls.CS_MAP[space], RGBish):
        raise ValueError(f"Can only compose in an RGBish color space, not {type(color_cls.CS_MAP[space])}")

    # Setup the blender
    blender = blend if blend is None else blend_modes.get_blender(blend)

    # Setup the Porter Duff operator
    op = operator if operator is None else porter_duff.compositor(operator)

    # Apply blending and alpha compositing to the colors from right to left
    dest = color_cls._handle_color_input(colors[-1]).convert(space).normalize(nans=False)[:]
    for x in range(len(colors) - 2, -1, -1):
        src = color_cls._handle_color_input(colors[x]).convert(space).normalize(nans=False)[:]
        dest = apply_compositing(src, dest, blender, op)

    return color_cls(space, dest[:-1], dest[-1]).convert(out_space, in_place=True)
