import typing as t

import jank


class UIBase:
    _x: float = 0.
    _y: float = 0.
    _width: float = 0.
    _height: float = 0.
    _anchor_x: float = 0.5
    _anchor_y: float = 0.5
    _parent_anchor_x: float = 0.5
    _parent_anchor_y: float = 0.5
    _parent: t.Optional["UIBase"] = None

    children: t.List["UIBase"]

    def __init__(self, x: float = 0, y: float = 0, parent: t.Optional["UIBase"] = None):
        self._x = x
        self._y = y
        self.children = []
        self.parent = parent
        jank.get_app().push_handlers(self)
        self.update()

    def set_position(self, x: float, y: float):
        """ Set the position of any sprites and/or renderers. """

    def run_cleanup(self):
        """ Run any deletion or garbage collection functions. """

    def draw(self):
        """ Draw any sprites or renderers manually. """

    def get_width(self) -> float:
        """ Get the width of the element. """
        return self._width

    def set_width(self, width: float):
        """ Set the width of the element. """
        self._width = width

    def get_height(self) -> float:
        """ Gets the height of the element. """
        return self._height

    def set_height(self, height: float):
        """ Sets the height of the element. """
        self._height = height

    def update(self):
        self.set_position(self.real_x, self.real_y)
        for child in self.children:
            child.update()

    def delete(self, recursive: bool = True):
        parent = self.parent
        self.parent = None
        while len(self.children) > 0:
            if recursive:
                self.children[0].delete(True)
            else:
                self.children[0].parent = parent
        jank.get_app().remove_handlers(self)
        self.run_cleanup()

    def add_child(self, child: t.Any):
        child._parent = self
        self.children.append(child)
        child.update()

    def on_resize(self, width, height):
        if self.parent is None:
            self.update()

    @property
    def real_x(self) -> float:
        real_x = self.x
        if self.parent is not None:
            real_x += self.parent.real_x
            parent = self.parent
        else:
            parent = jank.get_app().window

        real_x -= self.width*self.anchor_x

        real_x += parent.width*self.parent_anchor_x

        return real_x

    @property
    def real_y(self) -> float:
        real_y = self.y
        if self.parent is not None:
            real_y += self.parent.real_y
            parent = self.parent
        else:
            parent = jank.get_app().window

        real_y -= self.height*self.anchor_y

        real_y += parent.height*self.parent_anchor_y

        return real_y

    @property
    def anchor_x(self) -> float:
        return self._anchor_x

    @anchor_x.setter
    def anchor_x(self, anchor_x: float):
        self._anchor_x = anchor_x
        self.update()

    @property
    def anchor_y(self) -> float:
        return self._anchor_y

    @anchor_y.setter
    def anchor_y(self, anchor_y: float):
        self._anchor_y = anchor_y
        self.update()

    @property
    def anchor(self) -> t.Tuple[float, float]:
        return (self._anchor_x, self._anchor_y)

    @anchor.setter
    def anchor(self, anchor: t.Tuple[float, float]):
        self._anchor_x, self._anchor_y = anchor
        self.update()

    @property
    def parent(self) -> t.Optional["UIBase"]:
        return self._parent

    @parent.setter
    def parent(self, parent: t.Optional["UIBase"]):
        if self.parent is not None:
            self.parent.children.remove(self)
        self._parent = parent
        if self.parent is not None:
            self.parent.children.append(self)
        self.update()

    @property
    def parent_anchor_x(self) -> float:
        return self._parent_anchor_x

    @parent_anchor_x.setter
    def parent_anchor_x(self, parent_anchor_x: float):
        self._parent_anchor_x = parent_anchor_x
        self.update()

    @property
    def parent_anchor_y(self) -> float:
        return self._parent_anchor_y

    @parent_anchor_y.setter
    def parent_anchor_y(self, parent_anchor_y: float):
        self._parent_anchor_y = parent_anchor_y
        self.update()

    @property
    def parent_anchor(self) -> t.Tuple[float, float]:
        return (self._parent_anchor_x, self._parent_anchor_y)

    @parent_anchor.setter
    def parent_anchor(self, parent_anchor: t.Tuple[float, float]):
        self._parent_anchor_x, self._parent_anchor_y = parent_anchor
        self.update()

    @property
    def x(self) -> float:
        return self._x

    @x.setter
    def x(self, x: float):
        self._x = x
        self.update()

    @property
    def y(self) -> float:
        return self._y

    @y.setter
    def y(self, y: float):
        self._y = y
        self.update()

    @property
    def width(self) -> float:
        return self.get_width()

    @width.setter
    def width(self, width: float):
        self.set_width(width)
        self.update()

    @property
    def height(self) -> float:
        return self.get_height()

    @height.setter
    def height(self, height: float):
        self.set_height(height)
        self.update()

    @property
    def bounding_box(self) -> jank.BoundingBox:
        return jank.BoundingBox(
            self.real_x,
            self.real_y,
            self.real_x+self.width,
            self.real_y+self.height
        )

    @property
    def full_bounding_box(self) -> jank.BoundingBox:
        all_bounding_boxes = [
            child.full_bounding_box for child in self.children
        ]
        all_bounding_boxes.append(self.bounding_box)
        return jank.BoundingBox(
            min(bounding_box.left for bounding_box in all_bounding_boxes),
            min(bounding_box.bottom for bounding_box in all_bounding_boxes),
            max(bounding_box.right for bounding_box in all_bounding_boxes),
            max(bounding_box.top for bounding_box in all_bounding_boxes)
        )

    def check_hit(self, real_x: float, real_y: float):
        bb = self.bounding_box
        return bb.left <= real_x < bb.right and bb.bottom <= real_y < bb.top

import typing as t

import jank


class UIBase:
    _x: float = 0.
    _y: float = 0.
    _width: float = 0.
    _height: float = 0.
    _anchor_x: float = 0.5
    _anchor_y: float = 0.5
    _parent_anchor_x: float = 0.5
    _parent_anchor_y: float = 0.5
    _parent: t.Optional["UIBase"] = None

    children: t.List["UIBase"]

    def __init__(self, x: float = 0, y: float = 0, parent: t.Optional["UIBase"] = None):
        self._x = x
        self._y = y
        self.children = []
        self.parent = parent
        jank.get_app().push_handlers(self)
        self.update()

    def set_position(self, x: float, y: float):
        """ Set the position of any sprites and/or renderers. """

    def run_cleanup(self):
        """ Run any deletion or garbage collection functions. """

    def draw(self):
        """ Draw any sprites or renderers manually. """

    def get_width(self) -> float:
        """ Get the width of the element. """
        return self._width

    def set_width(self, width: float):
        """ Set the width of the element. """
        self._width = width

    def get_height(self) -> float:
        """ Gets the height of the element. """
        return self._height

    def set_height(self, height: float):
        """ Sets the height of the element. """
        self._height = height

    def update(self):
        self.set_position(self.real_x, self.real_y)
        for child in self.children:
            child.update()

    def delete(self, recursive: bool = True):
        parent = self.parent
        self.parent = None
        while len(self.children) > 0:
            if recursive:
                self.children[0].delete(True)
            else:
                self.children[0].parent = parent
        jank.get_app().remove_handlers(self)
        self.run_cleanup()

    def add_child(self, child: t.Any):
        child._parent = self
        self.children.append(child)
        child.update()

    def on_resize(self, width, height):
        if self.parent is None:
            self.update()

    @property
    def real_x(self) -> float:
        real_x = self.x
        if self.parent is not None:
            real_x += self.parent.real_x
            parent = self.parent
        else:
            parent = jank.get_app().window

        real_x -= self.width*self.anchor_x

        real_x += parent.width*self.parent_anchor_x

        return real_x

    @property
    def real_y(self) -> float:
        real_y = self.y
        if self.parent is not None:
            real_y += self.parent.real_y
            parent = self.parent
        else:
            parent = jank.get_app().window

        real_y -= self.height*self.anchor_y

        real_y += parent.height*self.parent_anchor_y

        return real_y

    @property
    def anchor_x(self) -> float:
        return self._anchor_x

    @anchor_x.setter
    def anchor_x(self, anchor_x: float):
        self._anchor_x = anchor_x
        self.update()

    @property
    def anchor_y(self) -> float:
        return self._anchor_y

    @anchor_y.setter
    def anchor_y(self, anchor_y: float):
        self._anchor_y = anchor_y
        self.update()

    @property
    def anchor(self) -> t.Tuple[float, float]:
        return (self._anchor_x, self._anchor_y)

    @anchor.setter
    def anchor(self, anchor: t.Tuple[float, float]):
        self._anchor_x, self._anchor_y = anchor
        self.update()

    @property
    def parent(self) -> t.Optional["UIBase"]:
        return self._parent

    @parent.setter
    def parent(self, parent: t.Optional["UIBase"]):
        if self.parent is not None:
            self.parent.children.remove(self)
        self._parent = parent
        if self.parent is not None:
            self.parent.children.append(self)
        self.update()

    @property
    def parent_anchor_x(self) -> float:
        return self._parent_anchor_x

    @parent_anchor_x.setter
    def parent_anchor_x(self, parent_anchor_x: float):
        self._parent_anchor_x = parent_anchor_x
        self.update()

    @property
    def parent_anchor_y(self) -> float:
        return self._parent_anchor_y

    @parent_anchor_y.setter
    def parent_anchor_y(self, parent_anchor_y: float):
        self._parent_anchor_y = parent_anchor_y
        self.update()

    @property
    def parent_anchor(self) -> t.Tuple[float, float]:
        return (self._parent_anchor_x, self._parent_anchor_y)

    @parent_anchor.setter
    def parent_anchor(self, parent_anchor: t.Tuple[float, float]):
        self._parent_anchor_x, self._parent_anchor_y = parent_anchor
        self.update()

    @property
    def x(self) -> float:
        return self._x

    @x.setter
    def x(self, x: float):
        self._x = x
        self.update()

    @property
    def y(self) -> float:
        return self._y

    @y.setter
    def y(self, y: float):
        self._y = y
        self.update()

    @property
    def width(self) -> float:
        return self.get_width()

    @width.setter
    def width(self, width: float):
        self.set_width(width)
        self.update()

    @property
    def height(self) -> float:
        return self.get_height()

    @height.setter
    def height(self, height: float):
        self.set_height(height)
        self.update()

    @property
    def bounding_box(self) -> jank.BoundingBox:
        return jank.BoundingBox(
            self.real_x,
            self.real_y,
            self.real_x+self.width,
            self.real_y+self.height
        )

    @property
    def full_bounding_box(self) -> jank.BoundingBox:
        all_bounding_boxes = [
            child.full_bounding_box for child in self.children
        ]
        all_bounding_boxes.append(self.bounding_box)
        return jank.BoundingBox(
            min(bounding_box.left for bounding_box in all_bounding_boxes),
            min(bounding_box.bottom for bounding_box in all_bounding_boxes),
            max(bounding_box.right for bounding_box in all_bounding_boxes),
            max(bounding_box.top for bounding_box in all_bounding_boxes)
        )

    def check_hit(self, real_x: float, real_y: float):
        bb = self.bounding_box
        return bb.left <= real_x < bb.right and bb.bottom <= real_y < bb.top

Set the position of any sprites and/or renderers. Run any deletion or garbage collection functions. Draw any sprites or renderers manually. Get the width of the element. Set the width of the element. Gets the height of the element. Sets the height of the element.