"""
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
"""

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

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)
    """
    :attr:`determinate` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    determinate_time = NumericProperty(2)
    """
    :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])
    """
    :attr:`color` is a :class:`~kivy.properties.ListProperty`
    and defaults to ``self.theme_cls.primary_color``.
    """

    _alpha = NumericProperty(0)
    _rotation_angle = NumericProperty(360)
    _angle_start = NumericProperty(0)
    _angle_end = NumericProperty(8)

    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)

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

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