#!/usr/bin/env python3
# Lancement: python cpu_monitor_gui.py

import tkinter as tk
from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
from collections import deque
import time

import psutil

MAX_POINTS = 60
UPDATE_INTERVAL = 1000  # ms


class Tracker:
    def __init__(self):
        self.cpu_data = deque(maxlen=MAX_POINTS)
        self.ram_data = deque(maxlen=MAX_POINTS)
        self.disk_data = deque(maxlen=MAX_POINTS)
        self.disk_read_data = deque(maxlen=MAX_POINTS)
        self.disk_write_data = deque(maxlen=MAX_POINTS)
        self.net_sent_data = deque(maxlen=MAX_POINTS)
        self.net_recv_data = deque(maxlen=MAX_POINTS)
        self._last_cpu = None
        self._last_net_sent = None
        self._last_net_recv = None
        self._last_disk_read = None
        self._last_disk_write = None
        now = time.time()
        self.time_data = deque(maxlen=MAX_POINTS)
        self.time_data.append(now)

    def _net_rate(self, curr, last):
        delta = curr - last
        rate = max(delta / 2.0, 0)
        return max(int(rate), 0)

    def update(self):
        cpu = psutil.cpu_percent(interval=None)
        ram = psutil.virtual_memory().percent
        disk = psutil.disk_usage('/').percent
        self._last_cpu = cpu
        t = time.time()
        self.time_data.append(t)

        self.cpu_data.append(cpu)
        self.ram_data.append(ram)
        self.disk_data.append(disk)

        d_io = psutil.disk_io_counters()
        if d_io is not None:
            d_read = d_io.read_bytes
            d_write = d_io.write_bytes
            if self._last_disk_read is not None:
                self.disk_read_data.append(max(int((d_read - self._last_disk_read) / 2.0), 0))
                self.disk_write_data.append(max(int((d_write - self._last_disk_write) / 2.0), 0))
            self._last_disk_read = d_read
            self._last_disk_write = d_write

        net = psutil.net_io_counters()
        sent = net.bytes_sent
        recv = net.bytes_recv
        if self._last_net_sent is not None:
            send_rate = self._net_rate(sent, self._last_net_sent)
            recv_rate = self._net_rate(recv, self._last_net_recv)
            self.net_sent_data.append(send_rate)
            self.net_recv_data.append(recv_rate)
        self._last_net_sent = sent
        self._last_net_recv = recv

    def get_latest(self):
        if self._last_cpu is None:
            self.update()
        return self._last_cpu, self.ram_data[-1]

    def get_disk(self):
        if not self.disk_data:
            return 0
        return self.disk_data[-1]

    def get_disk_rates(self):
        if not self.disk_read_data or not self.disk_write_data:
            return 0, 0
        return self.disk_read_data[-1], self.disk_write_data[-1]

    def get_net_rates(self):
        if not self.net_sent_data or not self.net_recv_data:
            return 0, 0
        return self.net_sent_data[-1], self.net_recv_data[-1]


class App:
    def __init__(self, root):
        self.root = root
        self.root.title("CPU / RAM Monitor")

        self.tracker = Tracker()

        # --- Header card ---
        card_bg = "#23272f"
        card_fg = "#ffffff"
        accent = "#4fc3f7"

        header_frame = ttk.Frame(root)
        header_frame.pack(fill=tk.X, padx=10, pady=(10, 0))

        self.cpu_label = ttk.Label(
            header_frame,
            text=f"CPU: --%",
            font=("Segoe UI", 20, "bold"),
            foreground=accent,
        )
        self.cpu_label.pack(side=tk.LEFT, padx=20)

        self.ram_label = ttk.Label(
            header_frame,
            text=f"RAM: --%",
            font=("Segoe UI", 20, "bold"),
            foreground="#81c784",
        )
        self.ram_label.pack(side=tk.LEFT, padx=20)

        self.net_label = ttk.Label(
            header_frame,
            text=f"Net: ↓ --",
            font=("Segoe UI", 20, "bold"),
            foreground="#ffb74d",
        )
        self.net_label.pack(side=tk.LEFT, padx=20)

        self.disk_label = ttk.Label(
            header_frame,
            text=f"Disk: --%",
            font=("Segoe UI", 20, "bold"),
            foreground="#ba68c8",
        )
        self.disk_label.pack(side=tk.LEFT, padx=20)

        # --- Plot ---
        fig = Figure(figsize=(14, 3), facecolor=card_bg)
        ax_cpu = fig.add_subplot(1, 4, 1, facecolor=card_bg)
        ax_ram = fig.add_subplot(1, 4, 2, facecolor=card_bg)
        ax_disk = fig.add_subplot(1, 4, 3, facecolor=card_bg)
        ax_net = fig.add_subplot(1, 4, 4, facecolor=card_bg)

        ax_cpu.set_title("CPU %", color=card_fg, fontsize=11)
        ax_ram.set_title("RAM %", color=card_fg, fontsize=11)
        ax_disk.set_title("Disk %", color=card_fg, fontsize=11)
        ax_net.set_title("Réseau (B/s)", color=card_fg, fontsize=11)
        ax_cpu.set_ylim(0, 100)
        ax_ram.set_ylim(0, 100)
        ax_disk.set_ylim(0, 100)
        ax_net.set_ylim(0, 1)
        ax_cpu.set_ylabel("%")
        ax_ram.set_ylabel("%")
        ax_disk.set_ylabel("%")
        ax_net.set_ylabel("B/s")
        ax_cpu.tick_params(colors="#aaa")
        ax_ram.tick_params(colors="#aaa")
        ax_net.tick_params(colors="#aaa")
        for label in ax_cpu.get_yticklabels():
            label.set_color("#aaa")
        for label in ax_ram.get_yticklabels():
            label.set_color("#aaa")
        for label in ax_net.get_yticklabels():
            label.set_color("#aaa")
        for line in ax_cpu.spines.values():
            line.set_color("#3a3f4a")
        for line in ax_ram.spines.values():
            line.set_color("#3a3f4a")
        for line in ax_net.spines.values():
            line.set_color("#3a3f4a")

        self.cpu_line, = ax_cpu.plot(
            [], [], color="#ef5350", linewidth=1.5, marker="", markerfacecolor="#ef5350"
        )
        self.ram_line, = ax_ram.plot(
            [], [], color="#66bb6a", linewidth=1.5, marker="", markerfacecolor="#66bb6a"
        )
        self.disk_line, = ax_disk.plot(
            [], [], color="#ba68c8", linewidth=1.5, marker="", markerfacecolor="#ba68c8"
        )
        self.net_sent_line, = ax_net.plot(
            [], [], color="#ffb74d", linewidth=1.5, marker="", markerfacecolor="#ffb74d"
        )
        self.net_recv_line, = ax_net.plot(
            [], [], color="#90caf9", linewidth=1.5, marker="", markerfacecolor="#90caf9"
        )

        ax_cpu.axhline(y=80, color="#ef5350", linestyle="--", alpha=0.4)
        ax_ram.axhline(y=80, color="#66bb6a", linestyle="--", alpha=0.4)
        ax_disk.axhline(y=80, color="#ba68c8", linestyle="--", alpha=0.4)
        ax_cpu.text(0.02, 0.02, "80%", fontsize=8, color="#ef5350", alpha=0.6, transform=ax_cpu.transAxes)

        self.fig = fig
        self.ax_cpu = ax_cpu
        self.ax_ram = ax_ram
        self.ax_disk = ax_disk
        self.ax_net = ax_net

        canvas = FigureCanvasTkAgg(fig, master=root)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        toolbar = NavigationToolbar2Tk(canvas, root)
        toolbar.update()

        # --- Footer status ---
        status_frame = ttk.Frame(root)
        status_frame.pack(fill=tk.X, padx=10, pady=(0, 10))

        self.status_label = ttk.Label(
            status_frame,
            text="Monitoring...",
            font=("Segoe UI", 9),
            foreground="#888",
        )
        self.status_label.pack(side=tk.LEFT)

        stop_btn = ttk.Button(
            status_frame,
            text="Stop",
            command=self._stop,
            width=10,
        )
        stop_btn.pack(side=tk.RIGHT, padx=(10, 0))

        self._running = True
        self._refresh()

    def _refresh(self):
        if not self._running:
            return
        self.tracker.update()
        cpu, ram = self.tracker.get_latest()
        disk = self.tracker.get_disk()
        send_rate, recv_rate = self.tracker.get_net_rates()
        self.cpu_label.config(text=f"CPU: {cpu:5.1f}%")
        self.ram_label.config(text=f"RAM: {ram:5.1f}%")
        self.disk_label.config(text=f"Disk: {disk:5.1f}%")

        mb_sent = send_rate / (1024 * 1024)
        mb_recv = recv_rate / (1024 * 1024)
        self.net_label.config(text=f"Net: ↑{mb_sent:.2f} ↓{mb_recv:.2f} MB/s")

        cpu_arr = list(self.tracker.cpu_data)
        ram_arr = list(self.tracker.ram_data)
        disk_arr = list(self.tracker.disk_data)
        sent_arr = list(self.tracker.net_sent_data)
        recv_arr = list(self.tracker.net_recv_data)
        self.cpu_line.set_data(range(len(cpu_arr)), cpu_arr)
        self.ram_line.set_data(range(len(ram_arr)), ram_arr)
        self.disk_line.set_data(range(len(disk_arr)), disk_arr)
        self.net_sent_line.set_data(range(len(sent_arr)), sent_arr)
        self.net_recv_line.set_data(range(len(recv_arr)), recv_arr)

        self.ax_cpu.set_xlim(0, MAX_POINTS)
        self.ax_ram.set_xlim(0, MAX_POINTS)
        self.ax_disk.set_xlim(0, MAX_POINTS)
        self.ax_net.set_xlim(0, MAX_POINTS)

        max_net = max(max(sent_arr) if sent_arr else 0, max(recv_arr) if recv_arr else 0)
        if max_net > 0:
            self.ax_net.set_ylim(0, max_net * 1.2)

        self.fig.canvas.draw_idle()
        self.root.after(UPDATE_INTERVAL, self._refresh)

    def _stop(self):
        self._running = False
        self.status_label.config(text="Stopped.")


def main():
    root = tk.Tk()
    app = App(root)
    root.mainloop()


if __name__ == "__main__":
    main()
