import time
import os
import psutil
import socket
import subprocess
from datetime import datetime
from collections import deque
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from PIL import Image, ImageDraw, ImageFont

# ---------------- CONSTANTS ----------------
I2C_PORT = 3
I2C_ADDR = 0x3C

CPU_HISTORY_LEN = 100
UPDATE_INTERVAL = 0.5

GRAPH_HEIGHT = 30
GRAPH_FOOTER_HEIGHT = 10

RAM_BAR_WIDTH = 25
RAM_BAR_HEIGHT = 52

FS_BOX = (64, 42, 100, 63)
FS_PIE_CENTER = (74, 52)
FS_PIE_RADIUS = 9

scroll_offset = 0
SCROLL_SPEED = 2  # pixels per update

# ---------------- OLED SETUP ----------------
serial = i2c(port=I2C_PORT, address=I2C_ADDR)
device = ssd1306(serial)
WIDTH, HEIGHT = device.width, device.height

cpu_history = deque([0] * CPU_HISTORY_LEN, maxlen=CPU_HISTORY_LEN)

try:
    small_font = ImageFont.truetype("DejaVuSans.ttf", 10)
except IOError:
    small_font = ImageFont.load_default()

# ---------------- HELPERS ----------------
def get_cpu_temp():
    temps = psutil.sensors_temperatures()
    if temps:
        return next(iter(temps.values()))[0].current
    try:
        with open("/sys/class/thermal/thermal_zone0/temp") as f:
            return int(f.read()) / 1000
    except OSError:
        return 0.0

def get_root_fs_usage():
    return psutil.disk_usage("/").percent

def format_uptime():
    boot = datetime.fromtimestamp(psutil.boot_time())
    seconds = int((datetime.now() - boot).total_seconds())
    hours, rem = divmod(seconds, 3600)
    minutes, _ = divmod(rem, 60)
    return hours, minutes

def truncate_text(text, max_len=8):
    if len(text) <= max_len:
        return text
    return text[:max_len - 1] + "…"

def get_wifi_info(interface="wlan0"):
    try:
        result = subprocess.run(
            ["iw", "dev", interface, "link"],
            capture_output=True,
            text=True
        )

        if "Not connected" in result.stdout:
            return "--", 0

        ssid = "--"
        signal_dbm = None

        for line in result.stdout.splitlines():
            line = line.strip()

            if line.startswith("SSID:"):
                ssid = line.split("SSID:")[1].strip()

            if line.startswith("signal:"):
                signal_dbm = int(line.split()[1])

        # Convert dBm → percentage (rough but standard)
        if signal_dbm is not None:
            signal_pct = max(0, min(100, 2 * (signal_dbm + 100)))
        else:
            signal_pct = 0

        return truncate_text(ssid), signal_pct

    except Exception:
        return "--", 0

def estimate_power_w(cpu_percent):
    # Constants (from measurement)
    BASE_POWER = 1.9          # W
    IDLE_FREQ_POWER = 0.3     # W at max freq
    MAX_CPU_POWER = 3.0       # W at full load + max freq

    MIN_FREQ = 614.0          # MHz
    MAX_FREQ = 1600.0         # MHz

    # Read current frequency (assume all cores similar)
    with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq") as f:
        freq_mhz = int(f.read()) / 1000

    freq_factor = max(
        0.0,
        min(1.0, (freq_mhz - MIN_FREQ) / (MAX_FREQ - MIN_FREQ))
    )

    load = cpu_percent / 100.0

    power = (
        BASE_POWER +
        IDLE_FREQ_POWER * freq_factor +
        MAX_CPU_POWER * freq_factor * load
    )

    return round(power, 2)

# ---------------- DRAWING ----------------
def draw_cpu_graph(draw, cpu, load, temp, watts):
    draw.rectangle((0, 0, CPU_HISTORY_LEN, GRAPH_HEIGHT), outline=255)

    if len(cpu_history) > 1:
        points = [
            (i, GRAPH_HEIGHT - (v / 100 * GRAPH_HEIGHT))
            for i, v in enumerate(cpu_history)
        ]
        points += [(len(cpu_history) - 1, GRAPH_HEIGHT), (0, GRAPH_HEIGHT)]
        draw.polygon(points, fill=255)

    footer_top = GRAPH_HEIGHT
    footer_bottom = GRAPH_HEIGHT + GRAPH_FOOTER_HEIGHT

    draw.rectangle((0, footer_top, CPU_HISTORY_LEN, footer_bottom), outline=255)
    draw.rectangle((0, footer_top, 25, footer_bottom), fill=255)

    draw.text((1, footer_top - 1), "CPU", font=small_font, fill=0)
    draw.line((59, footer_top, 59, footer_bottom), fill=255, width=2)

    load_text = f"{watts:.1f} W"
    lw = draw.textbbox((0, 0), load_text, font=small_font)[2]
    load_x = 25 + (59 - 25 - lw) // 2
    draw.text((load_x, footer_top - 1), load_text, font=small_font, fill=255)

    cpu_text = f"{cpu:.0f}%"
    cw = draw.textbbox((0, 0), cpu_text, font=small_font)[2]
    cpu_x = 59 + (CPU_HISTORY_LEN - 59 - cw) // 2
    draw.text((cpu_x, footer_top - 1), cpu_text, font=small_font, fill=255)

    draw.rectangle((0, 0, 26, 10), fill=255)
    draw.text((1, -1), f"{temp:.0f}°C", font=small_font, fill=0)

def draw_ram_bar(draw, mem_percent):
    x = WIDTH - 1 - RAM_BAR_WIDTH
    y = 0

    draw.rectangle((x, y, WIDTH - 1, RAM_BAR_HEIGHT), outline=255)

    fill_h = int((mem_percent / 100) * RAM_BAR_HEIGHT)
    draw.rectangle(
        (x, RAM_BAR_HEIGHT - fill_h, WIDTH - 1, RAM_BAR_HEIGHT),
        fill=255
    )

    label = f"{mem_percent:.0f}%"
    tw = draw.textbbox((0, 0), label, font=small_font)[2]
    draw.text((x + (RAM_BAR_WIDTH - tw) // 2 + 1,
               RAM_BAR_HEIGHT - fill_h - 11),
              label, font=small_font, fill=255)

    draw.rectangle((x, RAM_BAR_HEIGHT, WIDTH - 1, HEIGHT - 1), outline=0, fill=255)
    draw.text((103, 52), "RAM", font=small_font, fill=0)

def draw_fs_pie(draw, used_pct):
    draw.rectangle(FS_BOX, outline=255)

    cx, cy = FS_PIE_CENTER
    r = FS_PIE_RADIUS
    bbox = (cx - r, cy - r, cx + r, cy + r)

    draw.ellipse(bbox, outline=255)
    angle = int(360 * (used_pct / 100))

    draw.pieslice(bbox, start=270, end=270 + angle, fill=255)
    draw.text((86, 42), "FS", font=small_font, fill=255)
    draw.text((90, 51), "/", font=small_font, fill=255)

def draw_uptime(draw, hours, minutes):
    draw.rectangle((-1, 53, 62, HEIGHT), outline=255)
    draw.text((0, 53), f"up: {hours}h {minutes}m",
              font=small_font, fill=255)

def draw_wifi_icon(draw, ssid, signal_pct, x=0, y=0):
    bars = 4
    bar_width = 2
    bar_spacing = 1
    max_height = 8   # tallest bar
    filled_bars = int(signal_pct / 100 * bars + 0.5)

    for i in range(bars):
        bar_height = int((i + 1) / bars * max_height)
        y_top = y + (max_height - bar_height)
        fill = 1 if i < filled_bars else 0
        draw.rectangle(
            (x + i * (bar_width + bar_spacing), y_top,
             x + i * (bar_width + bar_spacing) + bar_width,
             y + max_height),
            fill=fill
        )

    # Draw SSID text right next to icon
    text_x = x + bars * (bar_width + bar_spacing) + 1
    draw.text((text_x, y-3), ssid, font=small_font, fill=255)

# ---------------- MAIN UPDATE ----------------
def update_display():
    cpu = psutil.cpu_percent(interval=0.1)
    watts = estimate_power_w(cpu)
    mem = psutil.virtual_memory().percent
    load1, _, _ = os.getloadavg()
    cpu_history.append(cpu)

    temp = get_cpu_temp()
    fs_used = get_root_fs_usage()
    hours, minutes = format_uptime()

    image = Image.new("1", (WIDTH, HEIGHT))
    draw = ImageDraw.Draw(image)

    draw.rectangle((0, 0, WIDTH, HEIGHT), fill=0)

    ssid, signal = get_wifi_info("wlan0")
    draw_wifi_icon(draw, ssid, signal, x=0, y=43)
    draw_cpu_graph(draw, cpu, load1, temp, watts)
    draw_ram_bar(draw, mem)
    draw_fs_pie(draw, fs_used)
    draw_uptime(draw, hours, minutes)

    device.display(image)

# ---------------- MAIN LOOP ----------------
if __name__ == "__main__":
    try:
        for _ in range(CPU_HISTORY_LEN):
            cpu_history.append(psutil.cpu_percent(interval=0.01))

        while True:
            update_display()
            time.sleep(UPDATE_INTERVAL)
    except KeyboardInterrupt:
        device.clear()
