"""
    https://stepik.org/lesson/468998/step/8?unit=459819

    Два квадрата, один управляется стрелками, другой клавишами WASD
    Для выхода нужно нажать Q или Esc

    Библиотека graph: https://kpolyakov.spb.ru/download/pygraph.zip
"""

import graph
from graph import *

from typing import NamedTuple
import types

consts = types.SimpleNamespace()
consts.VK_Q = 81
consts.VK_W = 87
consts.VK_A = 65
consts.VK_S = 83
consts.VK_D = 68


class Point(NamedTuple):
    """
    Вспомогательный класс для упрощения работы с координатами (сложение)
    """

    x: int | float
    y: int | float

    def __add__(self, other):
        if isinstance(other, self.__class__):
            return self.__class__(self.x + other.x, self.y + other.y)
        if isinstance(other, (int, float)):
            return self.__class__(self.x + other, self.y + other)
        return NotImplemented

    def __sub__(self, other):
        if isinstance(other, self.__class__):
            return self.__class__(self.x - other.x, self.y - other.y)
        if isinstance(other, (int, float)):
            return self.__class__(self.x - other, self.y - other)
        return NotImplemented


data = {"delta_a": Point(0, 0), "delta_b": Point(0, 0), "callbacks": []}


def square_a_on_scene(start_pos=None):
    start_pos = start_pos or Point(0, 0)

    scene_sz = Point(300, 300)
    box_sz = Point(40, 40)
    box_offset = Point(80, 80)

    brushColor("Blue")
    rectangle(*start_pos, *(start_pos + scene_sz))
    brushColor("Yellow")

    box_pos = start_pos + box_offset
    obj = rectangle(*box_pos, *(box_pos + box_sz))

    def update():
        delta = data["delta_a"]
        coord = Point(xCoord(obj), yCoord(obj)) - start_pos
        if (
            coord.x + delta.x < 0
            or coord.x + box_sz.x + delta.x > scene_sz.x
            or coord.y + delta.y < 0
            or coord.y + box_sz.y + delta.y > scene_sz.y
        ):
            return
        moveObjectBy(obj, delta.x, delta.y)

    data.setdefault("callbacks", []).append(update)


def square_b(start_pos=None):
    start_pos = start_pos or Point(0, 0)

    scene_sz = Point(300, 300)
    box_sz = Point(40, 40)
    box_offset = Point(200, 200)

    brushColor("Yellow")

    box_pos = start_pos + box_offset
    obj = rectangle(*box_pos, *(box_pos + box_sz))

    def update():
        delta = data["delta_b"]
        coord = Point(xCoord(obj), yCoord(obj)) - start_pos
        if (
            coord.x + delta.x < 0
            or coord.x + box_sz.x + delta.x > scene_sz.x
            or coord.y + delta.y < 0
            or coord.y + box_sz.y + delta.y > scene_sz.y
        ):
            return
        moveObjectBy(obj, delta.x, delta.y)

    data.setdefault("callbacks", []).append(update)


def keyPressed(event):
    match event.keycode:
        case graph.VK_LEFT:
            data["delta_a"] = Point(-5, 0)
        case graph.VK_RIGHT:
            data["delta_a"] = Point(5, 0)
        case graph.VK_UP:
            data["delta_a"] = Point(0, -5)
        case graph.VK_DOWN:
            data["delta_a"] = Point(0, 5)
        case consts.VK_A:
            data["delta_b"] = Point(-5, 0)
        case consts.VK_D:
            data["delta_b"] = Point(5, 0)
        case consts.VK_W:
            data["delta_b"] = Point(0, -5)
        case consts.VK_S:
            data["delta_b"] = Point(0, 5)
        case consts.VK_Q | graph.VK_ESCAPE:
            killTimer(data["update_timer"])
            close()


def update():
    for callback in data["callbacks"]:
        callback()
    data["delta_a"] = Point(0, 0)
    data["delta_b"] = Point(0, 0)


def main():
    canvasSize(300, 300)
    square_a_on_scene()
    square_b()

    data["update_timer"] = onTimer(update, 40)
    onKey(keyPressed)
    run()


main()