"""
Components/CircularLayout
=========================

CircularLayout is a special layout that places widgets around a circle.

MDCircularLayout
----------------

.. rubric:: Usage

.. code-block::

    from kivy.lang.builder import Builder
    from kivy.uix.label import Label

    from kivymd.app import MDApp

    kv = '''
    MDScreen:

        MDCircularLayout:
            id: container
            pos_hint: {"center_x": .5, "center_y": .5}
            row_spacing: min(self.size) * 0.1
    '''


    class Main(MDApp):
        def build(self):
            return Builder.load_string(kv)

        def on_start(self):
            for x in range(1, 49):
                self.root.ids.container.add_widget(
                    Label(text=f"{x}", color=[0, 0, 0, 1])
                )


    Main().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-layout.png
    :align: center
"""

__all__ = ("MDCircularLayout",)

from math import atan2, cos, degrees, radians, sin

from kivy.properties import BooleanProperty, NumericProperty

from kivymd.uix.floatlayout import MDFloatLayout


class MDCircularLayout(MDFloatLayout):
    degree_spacing = NumericProperty(30)
    """
    The space between children in degree.

    :attr:`degree_spacing` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `30`.
    """

    circular_radius = NumericProperty(None, allownone=True)
    """
    Radius of circle. Radius will be the greatest value in the layout if `circular_radius` if not specified.

    :attr:`circular_radius` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `None`.
    """

    start_from = NumericProperty(60)
    """
    The positon of first child in degree.

    :attr:`start_from` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `60`.
    """

    max_degree = NumericProperty(360)
    """
    Maximum range in degree allowed for each row of widgets before jumping to the next row.

    :attr:`max_degree` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `360`.
    """

    circular_padding = NumericProperty("25dp")
    """
    Padding between outer widgets and the edge of the biggest circle.

    :attr:`circular_padding` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `25dp`.
    """

    row_spacing = NumericProperty("50dp")
    """
    Space between each row of widget.

    :attr:`row_spacing` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `50dp`.
    """

    clockwise = BooleanProperty(True)
    """
    Direction of widgets in circular direction.

    :attr:`clockwise` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `True`.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.bind(
            row_spacing=self._update_layout,
        )

    def get_angle(self, pos: tuple) -> float:
        """Returns the angle of given pos."""

        center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2]
        (dx, dy) = (center[0] - pos[0], center[1] - pos[1])
        angle = degrees(atan2(float(dy), float(dx)))
        angle += 180
        return angle

    def remove_widget(self, widget, **kwargs):
        super().remove_widget(widget, **kwargs)
        self._update_layout()

    def do_layout(self, *largs, **kwargs):
        self._update_layout()
        return super().do_layout(*largs, **kwargs)

    def _max_per_row(self):
        return int(self.max_degree / self.degree_spacing)

    def _update_layout(self, *args):
        for index, child in enumerate(reversed(self.children)):
            pos = self._point_on_circle(
                self._calculate_radius(index),
                self._calculate_degree(index),
            )
            child.center = pos

    def _calculate_radius(self, index):
        """Calculates the radius for given index."""

        idx = int(index / self._max_per_row())

        if not self.circular_radius:
            init_radius = (
                min([self.width / 2, self.height / 2]) - self.circular_padding
            )
        else:
            init_radius = self.circular_radius

        if idx != 0:
            space = self.row_spacing * idx
            init_radius -= space

        return init_radius

    def _calculate_degree(self, index):
        """Calculates the angle for given index."""

        if self.clockwise:
            degree = self.start_from - index * self.degree_spacing
        else:
            degree = self.start_from + index * self.degree_spacing

        return degree

    def _point_on_circle(self, radius, degree):
        angle = radians(degree)
        center = [self.pos[0] + self.width / 2, self.pos[1] + self.height / 2]
        x = center[0] + (radius * cos(angle))
        y = center[1] + (radius * sin(angle))
        return [x, y]


if __name__ == "__main__":
    from kivy.lang.builder import Builder
    from kivy.uix.label import Label

    from kivymd.app import MDApp

    kv = """
MDScreen:

    MDCircularLayout:
        id: container
        pos_hint: {"center_x": .5, "center_y": .5}
        row_spacing: min(self.size) * 0.1
    """

    class Main(MDApp):
        def build(self):
            return Builder.load_string(kv)

        def on_start(self):
            for x in range(1, 49):
                self.root.ids.container.add_widget(
                    Label(text=f"{x}", color=[0, 0, 0, 1])
                )

    Main().run()
