#!/usr/bin/env python3
"""
Glass curtain wall adhesive test system - backend
- Tail latest CSV files (pattern ai0_accel_*.csv) in CSV_DIR and push new rows via WebSocket (/ws)
- Serve MJPEG camera stream at /video_feed (USB camera like /dev/video0)
- Serve static frontend from / (static/index.html)
Configuration: edit CSV_DIR, CSV_GLOB, CAMERA_INDEX, POLL_INTERVAL below
"""
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import StreamingResponse, FileResponse
from fastapi.staticfiles import StaticFiles
import asyncio
import threading
import time
import glob
import os
import json
import cv2

# ----------------- Configuration -----------------
CSV_DIR = r"/home/pi/acc_data"           # <- your CSV directory
CSV_GLOB = "ai0_accel_*.csv"             # pattern used by your C program
POLL_INTERVAL = 0.05                     # seconds between file tail polls
QUEUE_MAXSIZE = 20000
CAMERA_INDEX = 0                          # USB camera (0 maps to /dev/video0)
# -------------------------------------------------

app = FastAPI(title="玻璃幕墙结构胶检测系统 - 后端")
# Serve static files (frontend)
app.mount("/", StaticFiles(directory="static", html=True), name="static")

# Async queue for samples
data_queue: asyncio.Queue = asyncio.Queue(maxsize=QUEUE_MAXSIZE)
clients = set()

def find_latest_csv():
    """Return the newest CSV file matching pattern, or None."""
    try:
        files = glob.glob(os.path.join(CSV_DIR, CSV_GLOB))
        if not files:
            return None
        files.sort(key=os.path.getmtime, reverse=True)
        return files[0]
    except Exception as e:
        print("find_latest_csv error:", e)
        return None

def tail_file(path, loop, queue: asyncio.Queue, stop_event: threading.Event):
    """Blocking tail that reads appended lines and schedules putting them into asyncio queue."""
    try:
        f = open(path, "r", encoding="utf-8", errors="ignore")
    except Exception as e:
        print("tail_file open error:", e)
        return
    # Move to end of file (we assume file already contains header / existing data)
    f.seek(0, os.SEEK_END)
    print(f"[tail_file] Tailing: {path}")
    while not stop_event.is_set():
        line = f.readline()
        if not line:
            time.sleep(POLL_INTERVAL)
            continue
        line = line.strip()
        if not line:
            continue
        parts = [p.strip() for p in line.split(",") if p.strip()!='']
        # Expected format: time,volt,accel_g,accel_mps2
        if len(parts) < 4:
            # skip malformed lines
            continue
        try:
            t = float(parts[0])
            volt = float(parts[1])
            accel_g = float(parts[2])
            accel_mps2 = float(parts[3])
            sample = {
                "t": round(t, 6),
                "volt": round(volt, 6),
                "accel_g": round(accel_g, 6),
                "accel_mps2": round(accel_mps2, 6)
            }
            # schedule putting into asyncio queue thread-safely
            loop.call_soon_threadsafe(asyncio.create_task, queue.put(sample))
        except Exception as e:
            print("parse error:", e, "line:", line)
            continue
    f.close()
    print(f"[tail_file] Stopped tailing: {path}")

async def csv_watcher_task():
    """Background task: watch for newest CSV and spawn tail thread for it."""
    stop_event = None
    tail_thread = None
    current = None
    loop = asyncio.get_event_loop()
    while True:
        latest = find_latest_csv()
        if latest != current:
            # stop previous tail thread
            if tail_thread and stop_event:
                stop_event.set()
                tail_thread.join(timeout=1.0)
            current = latest
            if current:
                stop_event = threading.Event()
                tail_thread = threading.Thread(target=tail_file, args=(current, loop, data_queue, stop_event), daemon=True)
                tail_thread.start()
                print("[csv_watcher] Started tail for", current)
            else:
                print("[csv_watcher] No CSV found yet. Waiting...")
        await asyncio.sleep(1.0)

@app.on_event("startup")
async def startup_event():
    # start CSV watcher
    asyncio.create_task(csv_watcher_task())
    print("Backend started. Watching CSV in:", CSV_DIR)

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    clients.add(websocket)
    print("Client connected:", websocket.client)
    try:
        while True:
            sample = await data_queue.get()
            # send sample to websocket client
            await websocket.send_text(json.dumps({"type":"sample","data": sample}, ensure_ascii=False))
    except WebSocketDisconnect:
        print("Client disconnected", websocket.client)
    except Exception as e:
        print("WebSocket error:", e)
    finally:
        clients.discard(websocket)

# Simple health endpoint
@app.get("/health")
def health():
    return {"status":"ok"}

# ----------------- Camera MJPEG stream -----------------
def gen_frames():
    """Yield JPEG frames from the USB camera as multipart MJPEG stream."""
    cap = cv2.VideoCapture(CAMERA_INDEX)
    if not cap.isOpened():
        print(f"[gen_frames] Cannot open camera index {CAMERA_INDEX}")
    while True:
        success, frame = cap.read()
        if not success:
            time.sleep(0.05)
            continue
        # optionally resize for bandwidth
        # frame = cv2.resize(frame, (1280, 720))
        ret, buffer = cv2.imencode('.jpg', frame)
        if not ret:
            continue
        frame_bytes = buffer.tobytes()
        yield (b'--frame\\r\\n' b'Content-Type: image/jpeg\\r\\n\\r\\n' + frame_bytes + b'\\r\\n')
    cap.release()

@app.get("/video_feed")
def video_feed():
    return StreamingResponse(gen_frames(), media_type='multipart/x-mixed-replace; boundary=frame')
