from typing import Optional, Union

import abjad

from .. import get
from .extract_trivial_tuplets import (
    extract_trivial_tuplets as extract_trivial_tuplets_function,
)
from .merge_partial_tuplets import (
    merge_partial_tuplets as merge_partial_tuplets_function,
)
from .prettify_rewrite_meter import (
    prettify_rewrite_meter as prettify_rewrite_meter_function,
)


def auto_rewrite_meter(
    container: abjad.Container,
    meter_list: Optional[
        list[
            Union[
                abjad.Meter,
                abjad.TimeSignature,
            ]
        ]
    ] = None,
    *,
    prettify_rewrite_meter: bool = True,
    extract_trivial_tuplets: bool = True,
    fuse_across_groups_of_beats: bool = True,
    fuse_quadruple_meter: bool = True,
    fuse_triple_meter: bool = True,
    boundary_depth: Optional[int] = None,
    maximum_dot_count: Optional[int] = None,
    rewrite_tuplets: bool = True,
    merge_partial_tuplets: bool = True,
    split_quadruple_meter: bool = True,
) -> None:
    r"""Mutates an input container (of type |abjad.Container| or child class)
    in place and has no return value; this function takes every measure of a
    container, detects its time signature, and apply both
    |abjad.Meter.rewrite_meter()| and
    |auxjad.mutate.prettify_rewrite_meter()| to it.

    Basic usage:
        For this example, the following container will be mutated:

        >>> staff = abjad.Staff(
        ...     r"c'16 d'8 e'16 f'8 g'4 a'4 b'8 "
        ...     r"c'16 d'4. e'16 f'8 g'4 a'16 b'16"
        ... )
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                c'16
                d'8
                e'16
                f'8
                g'4
                a'4
                b'8
                c'16
                d'4.
                e'16
                f'8
                g'4
                a'16
                b'16
            }

        ..  figure:: ../_images/auto_rewrite_meter-xyx2wh7ufer.png

        Abjad's |abjad.Meter.rewrite_meter()| mutates an |abjad.Selection|
        of a measure, improving its notation.

        >>> for measure in abjad.select(staff[:]).group_by_measure():
        ...     abjad.Meter.rewrite_meter(measure, abjad.Meter((4, 4)))
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                c'16
                d'16
                ~
                d'16
                e'16
                f'8
                g'8
                ~
                g'8
                a'8
                ~
                a'8
                b'8
                c'16
                d'8.
                ~
                d'8.
                e'16
                f'8
                g'8
                ~
                g'8
                a'16
                b'16
            }

        ..  figure:: ../_images/auto_rewrite_meter-7fn2uj2xupb.png

        This function mutates an |abjad.Container| (or child class),
        identifying the implied meters of each measure and applying both
        |abjad.Meter.rewrite_meter()| and
        |auxjad.mutate.prettify_rewrite_meter()| to it. See the documentation
        of the latter for a detailed explanation of what it does.

        Applying |auxjad.mutate.auto_rewrite_meter()| to the same initial
        container shown in the first figure above outputs:

        >>> staff = abjad.Staff(
        ...     r"c'16 d'8 e'16 f'8 g'4 a'4 b'8 "
        ...     r"c'16 d'4. e'16 f'8 g'4 a'16 b'16"
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(staff)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                c'16
                d'8
                e'16
                f'8
                g'8
                ~
                g'8
                a'4
                b'8
                c'16
                d'8.
                ~
                d'8.
                e'16
                f'8
                g'4
                a'16
                b'16
            }

        ..  figure:: ../_images/auto_rewrite_meter-ahdaggaiqbc.png

    ..  note::

        Auxjad automatically adds this function as an extension function to
        |abjad.mutate|. It can thus be used from either |auxjad.mutate|_ or
        |abjad.mutate| namespaces. Therefore, the two lines below are
        equivalent:

        >>> auxjad.mutate.auto_rewrite_meter(staff)
        >>> abjad.mutate.auto_rewrite_meter(staff)

    Time signature changes:
        It automatically handles time signature changes.

        >>> staff = abjad.Staff(
        ...     r"c'16 d'8 e'16 f'8 g'4 a'4 b'8 "
        ...     r"\time 6/8 b'4 c''4 r4 "
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(staff)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                c'16
                d'8
                e'16
                f'8
                g'8
                ~
                g'8
                a'4
                b'8
                \time 6/8
                b'4
                c''8
                ~
                c''8
                r4
            }

        ..  figure:: ../_images/auto_rewrite_meter-08sckfp19vil.png

    ``prettify_rewrite_meter``:
        By default, this function invokes both |abjad.Meter.rewrite_meter()|
        and |auxjad.mutate.prettify_rewrite_meter()|.

        >>> staff = abjad.Staff(
        ...     r"c'16 d'8 e'16 f'8 g'4 a'4 b'8 "
        ...     r"c'16 d'8 e'16 f'8 g'4 a'4 b'8"
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(staff)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                c'16
                d'8
                e'16
                f'8
                g'8
                ~
                g'8
                a'4
                b'8
                c'16
                d'8
                e'16
                f'8
                g'8
                ~
                g'8
                a'4
                b'8
            }

        ..  figure:: ../_images/auto_rewrite_meter-vbytyszlkng.png

        Set ``prettify_rewrite_meter`` to ``False`` to not invoke
        |auxjad.mutate.prettify_rewrite_meter()|.

        >>> staff = abjad.Staff(
        ...     r"c'16 d'8 e'16 f'8 g'4 a'4 b'8 "
        ...     r"c'16 d'4. e'16 f'8 g'4 a'16 b'16"
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(
        ...     staff,
        ...     prettify_rewrite_meter=False,
        ... )
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                c'16
                d'16
                ~
                d'16
                e'16
                f'8
                g'8
                ~
                g'8
                a'8
                ~
                a'8
                b'8
                c'16
                d'8.
                ~
                d'8.
                e'16
                f'8
                g'8
                ~
                g'8
                a'16
                b'16
            }

        ..  figure:: ../_images/auto_rewrite_meter-64wse58hvko.png

    ``meter_list``:
        When no ``meter_list`` is supplied, this function detects the time
        signature of each measure and uses those when rewritting it:

        >>> staff = abjad.Staff(
        ...     r"\time 7/4 c'8 d'4 e'4 f'4 g'4 a'4 b'4 c''8 "
        ...     r"\time 5/4 d''8 e''4 f''4 g''4 a''4 b''8"
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(staff)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                \time 7/4
                c'8
                d'4
                e'4
                f'8
                ~
                f'8
                g'4
                a'8
                ~
                a'8
                b'4
                c''8
                \time 5/4
                d''8
                e''4
                f''4
                g''8
                ~
                g''8
                a''4
                b''8
            }

        ..  figure:: ../_images/auto_rewrite_meter-l4xnpevp3z.png

        To use a custom list of meters (one for each measure), set
        ``meter_list`` to a :obj:`list` of |abjad.Meter|'s or
        |abjad.TimeSignature|'s.

        >>> staff = abjad.Staff(
        ...     r"\time 7/4 c'8 d'4 e'4 f'4 g'4 a'4 b'4 c''8 "
        ...     r"\time 5/4 d''8 e''4 f''4 g''4 a''4 b''8"
        ... )
        >>> meter_list = [abjad.Meter((7, 4), increase_monotonic=True),
        ...               abjad.Meter((5, 4), increase_monotonic=True),
        ...               ]
        >>> auxjad.mutate.auto_rewrite_meter(staff, meter_list=meter_list)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                \time 7/4
                c'8
                d'4
                e'8
                ~
                e'8
                f'4
                g'8
                ~
                g'8
                a'4
                b'4
                c''8
                \time 5/4
                d''8
                e''4
                f''8
                ~
                f''8
                g''4
                a''4
                b''8
            }

        ..  figure:: ../_images/auto_rewrite_meter-uqif4i8tqxk.png

    Number of measures:
        This function handles a container with any number of measures and any
        number of time signature changes:

        >>> staff = abjad.Staff(
        ...     r"\time 3/4 c'8 d'4 e'4 f'8 "
        ...     r"\time 5/8 g'4 a'4 r8 "
        ...     r"\time 6/8 b'4 c''4 r4 "
        ...     r"\time 4/4 d''8 e''4 f''8 g''16 a''4 r8."
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(staff)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                \time 3/4
                c'8
                d'4
                e'4
                f'8
                \time 5/8
                g'4
                a'8
                ~
                a'8
                r8
                \time 6/8
                b'4
                c''8
                ~
                c''8
                r4
                \time 4/4
                d''8
                e''4
                f''8
                g''16
                a''8.
                ~
                a''16
                r8.
            }

        ..  figure:: ../_images/auto_rewrite_meter-hkhtqnqita.png

    ``extract_trivial_tuplets``:
        By default, tuplets filled with rests or tied notes or chords are
        extracted:

        >>> staff = abjad.Staff(
        ...     r"\times 2/3 {c'4 ~ c'8} \times 2/3 {d'8 r4} "
        ...     r"\times 2/3 {r8 r8 r8} \times 2/3 {<e' g'>8 ~ <e' g'>4}"
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(staff)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                c'4
                \times 2/3
                {
                    d'8
                    r4
                }
                r4
                <e' g'>4
            }

        ..  figure:: ../_images/auto_rewrite_meter-nq6t6qwka7a.png

        Set ``extract_trivial_tuplets`` to ``False`` to disable this behaviour.

        >>> staff = abjad.Staff(
        ...     r"\times 2/3 {c'4 ~ c'8} \times 2/3 {d'8 r4} "
        ...     r"\times 2/3 {r8 r8 r8} \times 2/3 {<e' g'>8 ~ <e' g'>4}"
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(
        ...     staff,
        ...     extract_trivial_tuplets=False,
        ... )
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                \times 2/3
                {
                    c'4.
                }
                \times 2/3
                {
                    d'8
                    r4
                }
                \times 2/3
                {
                    r4.
                }
                \times 2/3
                {
                    <e' g'>4.
                }
            }

        ..  figure:: ../_images/auto_rewrite_meter-ssnsui7o9cc.png

    ``merge_partial_tuplets``:
        By default, consecutive partial tuplets with the same ratio that sum up
        to an assignable duration will be merged together:

        >>> staff = abjad.Staff(
        ...     r"\times 2/3 {c'2 d'1}"
        ...     r"\times 2/3 {e'2} \times 2/3 {f'1}"
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(staff)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                \times 2/3
                {
                    c'2
                    d'1
                }
                \times 2/3
                {
                    e'2
                    f'1
                }
            }

        ..  figure:: ../_images/auto_rewrite_meter-ty72t5wvc1.png

        Set ``merge_partial_tuplets`` to ``False`` to disable this behaviour.

        >>> staff = abjad.Staff(
        ...     r"\times 2/3 {c'2 d'1}"
        ...     r"\times 2/3 {e'2} \times 2/3 {f'1}"
        ... )
        >>> auxjad.mutate.auto_rewrite_meter(
        ...     staff,
        ...     merge_partial_tuplets=False,
        ... )
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                \times 2/3
                {
                    c'2
                    d'1
                }
                \tweak edge-height #'(0.7 . 0)
                \times 2/3
                {
                    e'2
                }
                \tweak edge-height #'(0.7 . 0)
                \times 2/3
                {
                    f'1
                }
            }

        ..  figure:: ../_images/auto_rewrite_meter-4rouf819bjb.png

    ..  note::

        This function also accepts the arguments ``boundary_depth``,
        ``maximum_dot_count``, and ``rewrite_tuplets``, which are passed on to
        |abjad.Meter.rewrite_meter()|, and ``fuse_across_groups_of_beats``,
        ``fuse_quadruple_meter``, ``fuse_triple_meter``, and
        ``split_quadruple_meter``, which are passed on to
        |auxjad.mutate.prettify_rewrite_meter()|. ``merge_partial_tuplets``
        is used to invoke |auxjad.mutate.merge_partial_tuplets()| See the
        documentation of these functions for more details on these arguments.

    ..  warning::

        Setting ``boundary_depth`` to a value equal to or larger than ``1``
        will automatically disable ``fuse_across_groups_of_beats``,
        ``fuse_quadruple_meter``, and ``fuse_triple_meter``, regardless of
        their values. This is because when any of those arguments is ``True``,
        |auxjad.mutate.prettify_rewrite_meter()| will fuse across beats,
        which goes against the purpose of using ``boundary_depth``. Compare the
        results below. In the first case, simply applying
        |auxjad.mutate.prettify_rewrite_meter()| with no arguments results in
        some logical ties being tied across beats.

        >>> staff = abjad.Staff(r"\time 4/4 c'4. d'4. e'4 f'8 g'4 a'4 b'4.")
        >>> meter = abjad.Meter((4, 4))
        >>> for measure in abjad.select(staff[:]).group_by_measure():
        ...     abjad.mutate.rewrite_meter(measure, meter, boundary_depth=1)
        >>> for measure in abjad.select(staff[:]).group_by_measure():
        ...     auxjad.mutate.prettify_rewrite_meter(measure, meter)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                \time 4/4
                c'4
                ~
                c'8
                d'8
                ~
                d'4
                e'4
                f'8
                g'4
                a'8
                ~
                a'8
                b'8
                ~
                b'4
            }

        ..  figure:: ../_images/auto_rewrite_meter-cf09ysj16fo.png

        By automatically setting all ``fuse_across_groups_of_beats``,
        ``fuse_quadruple_meter``, and  ``fuse_triple_meter` to ``False`` when
        ``boundary_depth`` is equal to or larger than ``1``, this function will
        not fuse those leaves against the required boundary depth.

        >>> staff = abjad.Staff(r"\time 4/4 c'4. d'4. e'4 f'8 g'4 a'4 b'4.")
        >>> auxjad.mutate.auto_rewrite_meter(staff, boundary_depth=1)
        >>> abjad.show(staff)

        ..  docs::

            \new Staff
            {
                \time 4/4
                c'4
                ~
                c'8
                d'8
                ~
                d'4
                e'4
                f'8
                g'8
                ~
                g'8
                a'8
                ~
                a'8
                b'8
                ~
                b'4
            }

        ..  figure:: ../_images/auto_rewrite_meter-mm9xvmaqwfj.png
    """
    if not isinstance(container, abjad.Container):
        raise TypeError("first positional argument must be 'abjad.Container' or child class")
    if meter_list is not None:
        if not isinstance(meter_list, list):
            raise TypeError(
                "'meter_list' must be a 'list' of 'abjad.Meter' or 'abjad.TimeSignature'"
            )
        else:
            for meter in meter_list:
                if not isinstance(meter, (abjad.Meter, abjad.TimeSignature)):
                    raise TypeError(
                        "elements of 'meter_list' must be 'abjad.Meter' or 'abjad.TimeSignature'"
                    )
                if isinstance(meter, abjad.TimeSignature):
                    meter = abjad.Meter(meter.pair)
    if not isinstance(prettify_rewrite_meter, bool):
        raise TypeError("'prettify_rewrite_meter' must be 'bool'")
    if not isinstance(fuse_across_groups_of_beats, bool):
        raise TypeError("'fuse_across_groups_of_beats' must be 'bool'")
    if not isinstance(fuse_quadruple_meter, bool):
        raise TypeError("'fuse_quadruple_meter' must be 'bool'")
    if not isinstance(fuse_triple_meter, bool):
        raise TypeError("'fuse_triple_meter' must be 'bool'")
    if boundary_depth is not None:
        if not isinstance(boundary_depth, int):
            raise TypeError("'boundary_depth' must be 'int'")
    if maximum_dot_count is not None:
        if not isinstance(maximum_dot_count, int):
            raise TypeError("'maximum_dot_count' must be 'int'")
    if not isinstance(rewrite_tuplets, bool):
        raise TypeError("'rewrite_tuplets' must be 'bool'")
    if not isinstance(merge_partial_tuplets, bool):
        raise TypeError("'merge_partial_tuplets' must be 'bool'")
    if not isinstance(split_quadruple_meter, bool):
        raise TypeError("'split_quadruple_meter' must be 'bool'")

    if merge_partial_tuplets:
        merge_partial_tuplets_function(container[:])
    if extract_trivial_tuplets:
        extract_trivial_tuplets_function(container[:])

    if meter_list is None:
        time_signatures = get.time_signature_list(
            container,
            do_not_use_none=True,
        )
        meter_list = [abjad.Meter(ts.pair) for ts in time_signatures]
    measures = abjad.select(container[:]).group_by_measure()
    for meter, measure in zip(meter_list, measures):
        if isinstance(measure[0], abjad.MultimeasureRest):
            continue
        abjad.Meter.rewrite_meter(
            measure,
            meter,
            boundary_depth=boundary_depth,
            maximum_dot_count=maximum_dot_count,
            rewrite_tuplets=rewrite_tuplets,
        )
    if prettify_rewrite_meter:
        measures = abjad.select(container[:]).group_by_measure()
        for meter, measure in zip(meter_list, measures):
            if boundary_depth is None or boundary_depth < 1:
                prettify_rewrite_meter_function(
                    measure,
                    meter,
                    fuse_across_groups_of_beats=fuse_across_groups_of_beats,
                    fuse_quadruple_meter=fuse_quadruple_meter,
                    fuse_triple_meter=fuse_triple_meter,
                    extract_trivial_tuplets=False,
                    split_quadruple_meter=split_quadruple_meter,
                )
            else:
                prettify_rewrite_meter_function(
                    measure,
                    meter,
                    fuse_across_groups_of_beats=False,
                    fuse_quadruple_meter=False,
                    fuse_triple_meter=False,
                    extract_trivial_tuplets=False,
                    split_quadruple_meter=False,
                )
