import ctypes
import math
import os
import random
import time
import tkinter as tk
from tkinter import messagebox


class CatLaserApp:
    DOT_RADIUS = 20.0
    MIN_SPEED = 300.0
    MAX_SPEED = 800.0
    FRAME_MS = 16
    MAX_STEP = 0.2
    TURN_DELAY_MIN = 0.9
    TURN_DELAY_MAX = 2.4
    TURN_ANGLE = 0.45
    BOUNCE_ANGLE = 0.35
    EXIT_SIZE = 48.0
    EXIT_MARGIN = 12.0
    CROSS_PAD = 14.0
    CROSS_WIDTH = 4.0
    HINT_DELAY_MS = 9000
    FOCUS_DELAY_MS = 150
    HINT_OFFSET = 26.0
    ES_CONTINUOUS = 0x80000000
    ES_DISPLAY_REQUIRED = 0x00000002
    ES_SYSTEM_REQUIRED = 0x00000001

    def __init__(self, root):
        self.root = root
        self.paused = False
        self.hint_id = None
        self.exit_rect = (0.0, 0.0, 0.0, 0.0)
        self.width = float(root.winfo_screenwidth())
        self.height = float(root.winfo_screenheight())
        self.x = self.width / 2.0
        self.y = self.height / 2.0
        self.vx = 0.0
        self.vy = 0.0
        self.last_time = time.perf_counter()
        self.next_turn = self.last_time + random.uniform(self.TURN_DELAY_MIN, self.TURN_DELAY_MAX)

        root.title("Лазер для кота")
        root.configure(bg="black")
        root.protocol("WM_DELETE_WINDOW", self.request_exit)
        self.enable_fullscreen()

        self.canvas = tk.Canvas(root, bg="black", highlightthickness=0, borderwidth=0)
        self.canvas.pack(fill="both", expand=True)

        self.hint_id = self.canvas.create_text(
            self.width / 2.0,
            self.height - self.HINT_OFFSET,
            text="ПРОБЕЛ или ENTER — новая траектория, ESC — выход",
            fill="#3c3c3c",
            font=("Segoe UI", 12),
        )

        radius = self.DOT_RADIUS
        self.dot = self.canvas.create_oval(
            self.x - radius,
            self.y - radius,
            self.x + radius,
            self.y + radius,
            fill="#ff0000",
            outline="#ff0000",
        )

        self.draw_exit_button()
        self.randomize_velocity()
        self.bind_events()
        self.keep_awake()

        root.after(self.FRAME_MS, self.tick)
        root.after(self.HINT_DELAY_MS, self.hide_hint)
        root.after(self.FOCUS_DELAY_MS, self.activate)

    def enable_fullscreen(self):
        try:
            self.root.attributes("-fullscreen", True)
        except tk.TclError:
            try:
                self.root.state("zoomed")
            except tk.TclError as error:
                messagebox.showerror("Ошибка", "Не удалось развернуть окно: " + str(error), parent=self.root)

    def bind_events(self):
        self.root.bind("<space>", self.on_big_button)
        self.root.bind("<Return>", self.on_big_button)
        self.root.bind("<KP_Enter>", self.on_big_button)
        self.root.bind("<Escape>", self.on_escape)
        self.root.bind("<Unmap>", self.on_unmap)
        self.root.bind("<Map>", self.on_map)
        self.canvas.bind("<Button-1>", self.on_click)
        self.canvas.bind("<Configure>", self.on_configure)

    def activate(self):
        self.root.focus_force()

    def hide_hint(self):
        if self.hint_id is not None:
            self.canvas.delete(self.hint_id)
            self.hint_id = None

    def draw_exit_button(self):
        self.canvas.delete("exitcross")
        size = self.EXIT_SIZE
        margin = self.EXIT_MARGIN
        pad = self.CROSS_PAD
        x1 = max(0.0, self.width - margin - size)
        x2 = x1 + size
        y1 = margin
        y2 = y1 + size
        self.canvas.create_line(
            x1 + pad, y1 + pad, x2 - pad, y2 - pad,
            fill="white", width=self.CROSS_WIDTH, capstyle="round",
            stipple="gray50", tags="exitcross",
        )
        self.canvas.create_line(
            x1 + pad, y2 - pad, x2 - pad, y1 + pad,
            fill="white", width=self.CROSS_WIDTH, capstyle="round",
            stipple="gray50", tags="exitcross",
        )
        self.exit_rect = (x1, y1, x2, y2)

    def bounds(self):
        radius = self.DOT_RADIUS
        left = min(radius, self.width / 2.0)
        right = max(left, self.width - radius)
        top = min(radius, self.height / 2.0)
        bottom = max(top, self.height - radius)
        return left, top, right, bottom

    def current_speed(self):
        speed = math.hypot(self.vx, self.vy)
        if speed < self.MIN_SPEED:
            speed = self.MIN_SPEED
        elif speed > self.MAX_SPEED:
            speed = self.MAX_SPEED
        return speed

    def randomize_velocity(self):
        angle = random.uniform(0.0, 2.0 * math.pi)
        speed = random.uniform(self.MIN_SPEED, self.MAX_SPEED)
        self.vx = math.cos(angle) * speed
        self.vy = math.sin(angle) * speed

    def turn_softly(self):
        speed = self.current_speed()
        angle = math.atan2(self.vy, self.vx) + random.uniform(-self.TURN_ANGLE, self.TURN_ANGLE)
        self.vx = math.cos(angle) * speed
        self.vy = math.sin(angle) * speed

    def reflect(self, horizontal, positive):
        speed = self.current_speed()
        angle = math.atan2(self.vy, self.vx) + random.uniform(-self.BOUNCE_ANGLE, self.BOUNCE_ANGLE)
        vx = math.cos(angle) * speed
        vy = math.sin(angle) * speed
        if horizontal:
            self.vx = abs(vx) if positive else -abs(vx)
            self.vy = vy
        else:
            self.vy = abs(vy) if positive else -abs(vy)
            self.vx = vx

    def update_dot(self):
        radius = self.DOT_RADIUS
        self.canvas.coords(self.dot, self.x - radius, self.y - radius, self.x + radius, self.y + radius)

    def move_dot(self, dt):
        self.x += self.vx * dt
        self.y += self.vy * dt
        left, top, right, bottom = self.bounds()
        if self.x < left:
            self.x = left
            self.reflect(True, True)
        elif self.x > right:
            self.x = right
            self.reflect(True, False)
        if self.y < top:
            self.y = top
            self.reflect(False, True)
        elif self.y > bottom:
            self.y = bottom
            self.reflect(False, False)
        self.update_dot()

    def tick(self):
        now = time.perf_counter()
        dt = now - self.last_time
        self.last_time = now
        if dt < 0.0:
            dt = 0.0
        elif dt > self.MAX_STEP:
            dt = self.MAX_STEP
        if not self.paused:
            if now >= self.next_turn:
                self.turn_softly()
                self.next_turn = now + random.uniform(self.TURN_DELAY_MIN, self.TURN_DELAY_MAX)
            self.move_dot(dt)
        self.root.after(self.FRAME_MS, self.tick)

    def on_big_button(self, event):
        self.randomize_velocity()
        return "break"

    def on_escape(self, event):
        self.request_exit()
        return "break"

    def on_click(self, event):
        x1, y1, x2, y2 = self.exit_rect
        if x1 <= event.x <= x2 and y1 <= event.y <= y2:
            self.request_exit()
        else:
            self.randomize_velocity()
        return "break"

    def on_configure(self, event):
        self.width = float(event.width)
        self.height = float(event.height)
        left, top, right, bottom = self.bounds()
        self.x = min(max(self.x, left), right)
        self.y = min(max(self.y, top), bottom)
        if self.hint_id is not None:
            self.canvas.coords(self.hint_id, self.width / 2.0, self.height - self.HINT_OFFSET)
        self.draw_exit_button()
        self.update_dot()

    def on_unmap(self, event):
        if event.widget is self.root:
            self.paused = True

    def on_map(self, event):
        if event.widget is self.root:
            self.paused = False
            self.last_time = time.perf_counter()
            self.next_turn = self.last_time + random.uniform(self.TURN_DELAY_MIN, self.TURN_DELAY_MAX)

    def request_exit(self):
        self.paused = True
        answer = messagebox.askyesno("Выход", "Закрыть приложение?", parent=self.root)
        if answer:
            self.release_awake()
            self.root.destroy()
            return
        self.paused = False
        self.last_time = time.perf_counter()
        self.next_turn = self.last_time + random.uniform(self.TURN_DELAY_MIN, self.TURN_DELAY_MAX)

    def keep_awake(self):
        if os.name != "nt":
            return
        try:
            kernel32 = ctypes.windll.kernel32
            kernel32.SetThreadExecutionState.argtypes = [ctypes.c_uint]
            kernel32.SetThreadExecutionState.restype = ctypes.c_uint
            kernel32.SetThreadExecutionState(
                self.ES_CONTINUOUS | self.ES_DISPLAY_REQUIRED | self.ES_SYSTEM_REQUIRED
            )
        except (AttributeError, OSError) as error:
            messagebox.showerror("Ошибка", "Не удалось отключить спящий режим: " + str(error), parent=self.root)

    def release_awake(self):
        if os.name != "nt":
            return
        try:
            ctypes.windll.kernel32.SetThreadExecutionState(self.ES_CONTINUOUS)
        except (AttributeError, OSError) as error:
            messagebox.showerror("Ошибка", "Не удалось вернуть спящий режим: " + str(error), parent=self.root)


def main():
    root = tk.Tk()
    try:
        CatLaserApp(root)
    except Exception as error:
        messagebox.showerror("Ошибка", "Не удалось запустить приложение: " + str(error), parent=root)
        root.destroy()
        return
    root.mainloop()


if __name__ == "__main__":
    main()