#!/usr/bin/env python3
# Lancement: cpu-monitor

import tkinter as tk
from tkinter import ttk
import subprocess
import tempfile
import os
import time

import psutil

import gi
try:
    gi.require_version('AyatanaAppIndicator3', '0.1')
    from gi.repository import AyatanaAppIndicator3, GLib, Gtk
    HAVE_INDICATOR = True
except (ImportError, ValueError):
    HAVE_INDICATOR = False


MAX_POINTS = 60
UPDATE_INTERVAL = 1000  # ms


try:
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    from matplotlib.figure import Figure
    HAVE_MPL = True
except ImportError:
    HAVE_MPL = False


class Tracker:
    def __init__(self):
        self.cpu_data = []
        self.ram_data = []
        self.disk_data = []
        self._last_cpu = None
        now = time.time()
        self.time_data = [now]

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

    def update(self, dt):
        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)

        net = psutil.net_io_counters()
        sent = net.bytes_sent
        recv = net.bytes_recv
        self._last_net_sent = sent
        self._last_net_recv = recv

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

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

    def get_net_rates(self):
        return 0, 0

    def cpu_percent(self):
        return psutil.cpu_percent(interval=None)


class SysTrayIndicator:
    def __init__(self, monitor):
        if not HAVE_INDICATOR:
            return

        self.indicator = AyatanaAppIndicator3.Indicator.new(
            "cpu-monitor",
            "cpu-monitor",
            AyatanaAppIndicator3.IndicatorCategory.SYSTEM_SERVICES
        )
        self.indicator.set_menu(self._create_menu())
        self.monitor = monitor
        self._update_icon()

        try:
            import cairo
        except ImportError:
            return

    def _create_menu(self):
        menu = Gtk.Menu()

        item = Gtk.MenuItem(label=f"CPU: {psutil.cpu_percent(interval=None)}%")
        item.show()
        menu.append(item)

        item2 = Gtk.MenuItem(label=f"RAM: {psutil.virtual_memory().percent}%")
        item2.show()
        menu.append(item2)

        sep = Gtk.SeparatorMenuItem()
        sep.show()
        menu.append(sep)

        open_item = Gtk.MenuItem(label="Open Monitor")
        open_item.connect("activate", lambda w: subprocess.Popen(["cpu-monitor"]))
        open_item.show()
        menu.append(open_item)

        close_item = Gtk.MenuItem(label="Quit")
        close_item.connect("activate", lambda w: Gtk.main_quit())
        close_item.show()
        menu.append(close_item)

        return menu

    def _update_icon(self):
        if not HAVE_INDICATOR:
            return
        try:
            import cairo
        except ImportError:
            return

        cpu = psutil.cpu_percent()
        if cpu > 80:
            color = (0.93, 0.33, 0.31)
        elif cpu > 50:
            color = (1.0, 0.55, 0.0)
        else:
            color = (0.40, 0.76, 0.97)

        size = 24
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
        cr = cairo.Context(surface)
        cr.set_source_rgba(*color, 1.0)
        cr.paint()

        tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False, dir='/tmp')
        tmp.close()
        surface.write_to_png(tmp.name)

        try:
            self.indicator.set_icon_full(tmp.name, "CPU Monitor")
        except Exception:
            pass

        GLib.timeout_add_seconds(2, self._update_icon)


def create_tray_icon(monitor):
    if HAVE_INDICATOR:
        return SysTrayIndicator(monitor)
    return None


class App:
    cpu_data = []
    ram_data = []
    _last_cpu = None

    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)

        self.fig = None

        if HAVE_MPL:
            # --- Plot ---
            self.fig = Figure(figsize=(14, 3), facecolor=card_bg)
            ax_cpu = self.fig.add_subplot(1, 3, 1, facecolor=card_bg)
            ax_ram = self.fig.add_subplot(1, 3, 2, facecolor=card_bg)
            ax_disk = self.fig.add_subplot(1, 3, 3, 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_cpu.set_ylim(0, 100)
            ax_ram.set_ylim(0, 100)
            ax_disk.set_ylim(0, 100)

            ax_cpu.tick_params(colors="#aaa")
            ax_ram.tick_params(colors="#aaa")
            ax_disk.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_disk.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_disk.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"
            )

            ax_cpu.axhline(y=80, color="#ef5350", linestyle="--", alpha=0.4)

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

        # --- 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
        cpu = self.tracker.cpu_percent()
        ram = psutil.virtual_memory().percent
        disk = psutil.disk_usage('/').percent
        App._last_cpu = cpu
        self.cpu_data.append(cpu)
        self.ram_data.append(ram)

        if len(self.cpu_data) > MAX_POINTS:
            self.cpu_data = self.cpu_data[-MAX_POINTS:]
        if len(self.ram_data) > MAX_POINTS:
            self.ram_data = self.ram_data[-MAX_POINTS:]

        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}%")

        if HAVE_MPL and self.fig:
            ax_cpu = self.fig.axes[0]
            ax_ram = self.fig.axes[1]
            ax_disk = self.fig.axes[2]

            self.cpu_line.set_data(range(len(self.cpu_data)), self.cpu_data)
            self.ram_line.set_data(range(len(self.ram_data)), self.ram_data)

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

            self.fig.canvas.draw_idle()

        self.root.after(UPDATE_INTERVAL, self._refresh)

    def _stop(self):
        self._running = False
        self.status_label.config(text="Stopped.")
        self.root.destroy()
        if HAVE_INDICATOR:
            GLib.idle_add(Gtk.main_quit)


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

    _tray = None
    if HAVE_INDICATOR:
        _tray = SysTrayIndicator(app)
        root.bind('<Destroy>', lambda e: GLib.idle_add(Gtk.main_quit))

    root.mainloop()


if __name__ == "__main__":
    main()
