"""
Components/Spinner
==================

.. rubric:: Circular progress indicator in Google's Material Design.

Usage
-----

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp

    KV = '''
    Screen:

        MDSpinner:
            size_hint: None, None
            size: dp(46), dp(46)
            pos_hint: {'center_x': .5, 'center_y': .5}
            active: True if check.active else False

        MDCheckbox:
            id: check
            size_hint: None, None
            size: dp(48), dp(48)
            pos_hint: {'center_x': .5, 'center_y': .4}
            active: True
    '''


    class Test(MDApp):
        def build(self):
            return Builder.load_string(KV)


    Test().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner.gif
    :align: center

Spinner palette
---------------

.. code-block:: kv

    MDSpinner:
        # The number of color values ​​can be any.
        palette:
            [0.28627450980392155, 0.8431372549019608, 0.596078431372549, 1], \
            [0.3568627450980392, 0.3215686274509804, 0.8666666666666667, 1], \
            [0.8862745098039215, 0.36470588235294116, 0.592156862745098, 1], \
            [0.8784313725490196, 0.9058823529411765, 0.40784313725490196, 1],

.. code-block:: python

    MDSpinner(
        size_hint=(None, None),
        size=(dp(46), dp(46)),
        pos_hint={'center_x': .5, 'center_y': .5},
        active=True,
        palette=[
            [0.28627450980392155, 0.8431372549019608, 0.596078431372549, 1],
            [0.3568627450980392, 0.3215686274509804, 0.8666666666666667, 1],
            [0.8862745098039215, 0.36470588235294116, 0.592156862745098, 1],
            [0.8784313725490196, 0.9058823529411765, 0.40784313725490196, 1],
        ]
    )

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner-palette.gif
    :align: center
"""

__all__ = ("MDSpinner",)

from kivy.animation import Animation
from kivy.lang import Builder
from kivy.properties import BooleanProperty, ListProperty, NumericProperty
from kivy.uix.widget import Widget

from kivymd.theming import ThemableBehavior

Builder.load_string(
    """
<MDSpinner>
    canvas.before:
        PushMatrix
        Rotate:
            angle: self._rotation_angle
            origin: self.center
    canvas:
        Color:
            rgba: self.color
            a: self._alpha
        SmoothLine:
            circle: self.center_x, self.center_y, self.width / 2,\
            self._angle_start, self._angle_end
            cap: 'square'
            width: dp(2.25)
    canvas.after:
        PopMatrix

"""
)


class MDSpinner(ThemableBehavior, Widget):
    """:class:`MDSpinner` is an implementation of the circular progress
    indicator in `Google's Material Design`.

    It can be used either as an indeterminate indicator that loops while
    the user waits for something to happen, or as a determinate indicator.

    Set :attr:`determinate` to **True** to activate determinate mode, and
    :attr:`determinate_time` to set the duration of the animation.
    """

    determinate = BooleanProperty(False)
    """
    Determinate value.

    :attr:`determinate` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    determinate_time = NumericProperty(2)
    """
    Determinate time value.

    :attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `2`.
    """

    active = BooleanProperty(True)
    """Use :attr:`active` to start or stop the spinner.

    :attr:`active` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `True`.
    """

    color = ListProperty([0, 0, 0, 0])
    """
    Spinner color.

    :attr:`color` is a :class:`~kivy.properties.ListProperty`
    and defaults to ``self.theme_cls.primary_color``.
    """

    palette = ListProperty()
    """
    A set of colors. Changes with each completed spinner cycle.

    :attr:`palette` is a :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    _alpha = NumericProperty(0)
    _rotation_angle = NumericProperty(360)
    _angle_start = NumericProperty(0)
    _angle_end = NumericProperty(0)
    _palette = []

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.color = self.theme_cls.primary_color
        self._alpha_anim_in = Animation(_alpha=1, duration=0.8, t="out_quad")
        self._alpha_anim_out = Animation(_alpha=0, duration=0.3, t="out_quad")
        self._alpha_anim_out.bind(on_complete=self._reset)
        self.theme_cls.bind(primary_color=self._update_color)

        if self.determinate:
            self._start_determinate()
        else:
            self._start_loop()

    def _update_color(self, *args):
        self.color = self.theme_cls.primary_color

    def _start_determinate(self, *args):
        self._alpha_anim_in.start(self)

        _rot_anim = Animation(
            _rotation_angle=0,
            duration=self.determinate_time * 0.7,
            t="out_quad",
        )
        _rot_anim.start(self)

        _angle_start_anim = Animation(
            _angle_end=360, duration=self.determinate_time, t="in_out_quad"
        )
        _angle_start_anim.bind(
            on_complete=lambda *x: self._alpha_anim_out.start(self)
        )

        _angle_start_anim.start(self)

    def _start_loop(self, *args):
        if self._alpha == 0:
            _rot_anim = Animation(_rotation_angle=0, duration=2, t="linear")
            _rot_anim.start(self)

        self._alpha = 1
        self._alpha_anim_in.start(self)
        _angle_start_anim = Animation(
            _angle_end=self._angle_end + 270, duration=0.6, t="in_out_cubic"
        )
        _angle_start_anim.bind(on_complete=self._anim_back)
        _angle_start_anim.start(self)

    def _anim_back(self, *args):
        _angle_back_anim = Animation(
            _angle_start=self._angle_end - 8, duration=0.6, t="in_out_cubic"
        )
        _angle_back_anim.bind(on_complete=self._start_loop)

        _angle_back_anim.start(self)

    def on__rotation_angle(self, *args):
        if self._rotation_angle == 0:
            self._rotation_angle = 360
            if not self.determinate:
                _rot_anim = Animation(_rotation_angle=0, duration=2)
                _rot_anim.start(self)
        elif self._rotation_angle == 360:
            if self._palette:
                try:
                    Animation(color=next(self._palette), duration=2).start(self)
                except StopIteration:
                    self._palette = iter(self.palette)
                    Animation(color=next(self._palette), duration=2).start(self)

    def _reset(self, *args):
        Animation.cancel_all(
            self,
            "_angle_start",
            "_rotation_angle",
            "_angle_end",
            "_alpha",
            "color",
        )
        self._angle_start = 0
        self._angle_end = 0
        self._rotation_angle = 360
        self._alpha = 0
        self.active = False

    def on_palette(self, instance, value):
        self._palette = iter(value)

    def on_active(self, *args):
        if not self.active:
            self._reset()
        else:
            if self.determinate:
                self._start_determinate()
            else:
                self._start_loop()
