import cv2
import datetime
import os
import csv
from PIL import Image, ImageTk
import tkinter as tk
from tkinter import ttk

class MotionDetectorApp:
    def __init__(self, window, window_title):
        self.window = window
        self.window.title(window_title)
        self.window.geometry("1280x820")
        self.window.configure(bg="#2c3e50")

        # Inisialisasi Kamera Default (Index 0)
        self.selected_camera_id = 0
        self.cap = cv2.VideoCapture(self.selected_camera_id, cv2.CAP_DSHOW)
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

        self.fps = self.cap.get(cv2.CAP_PROP_FPS)
        if self.fps == 0 or self.fps != self.fps:
            self.fps = 20.0

        self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        self.total_pixels = self.frame_width * self.frame_height

        # Variabel Kontrol Sesi & Perekaman
        self.is_started = False
        self.out = None
        self.is_recording = False
        self.no_motion_frames = 0
        self.MAX_NO_MOTION_FRAMES = 30
        
        self.camera_distance = 5.0  # Default jarak kamera (meter)
        self.csv_file = None
        self.csv_writer = None
        self.csv_filename = ""

        # StringVar untuk Dashboard
        self.log_thresh = tk.StringVar(value="25")
        self.log_distance = tk.StringVar(value="5.0 m")
        self.log_pixel_pct = tk.StringVar(value="0.00 %")
        self.log_est_size = tk.StringVar(value="0.00 m²")
        self.log_contours = tk.StringVar(value="0")
        self.log_timestamp = tk.StringVar(value="-")
        self.log_status = tk.StringVar(value="IDLE (Menunggu Mulai)")

        # Folder penyimpanan
        for folder in ["recordings", "logs"]:
            if not os.path.exists(folder):
                os.makedirs(folder)

        # Frame awal untuk background differencing
        ret, self.frame1 = self.cap.read()
        if ret:
            self.frame1 = cv2.convertScaleAbs(self.frame1, alpha=1.0, beta=0)
            self.gray1 = cv2.cvtColor(self.frame1, cv2.COLOR_BGR2GRAY)
            self.gray1 = cv2.GaussianBlur(self.gray1, (21, 21), 0)
        else:
            self.gray1 = None

        # --- TATA LETAK GUI (UI LAYOUT) ---
        
        # Container Utama untuk 2 Jendela Video (Kiri)
        video_container = tk.Frame(window, bg="#2c3e50")
        video_container.pack(side=tk.LEFT, padx=10, pady=10)

        # 1. Canvas Live View Asli
        live_frame = tk.LabelFrame(video_container, text="1. Live View Kamera", fg="white", bg="#2c3e50", font=("Arial", 10, "bold"))
        live_frame.pack(side=tk.TOP, pady=5)
        self.canvas_live = tk.Canvas(live_frame, width=480, height=360, bg="black")
        self.canvas_live.pack(padx=5, pady=5)

        # 2. Canvas Citra Hitam-Putih (Threshold)
        thresh_frame = tk.LabelFrame(video_container, text="2. Citra Deteksi (Hitam Putih)", fg="white", bg="#2c3e50", font=("Arial", 10, "bold"))
        thresh_frame.pack(side=tk.TOP, pady=5)
        self.canvas_thresh = tk.Canvas(thresh_frame, width=480, height=360, bg="black")
        self.canvas_thresh.pack(padx=5, pady=5)

        # Frame Kanan: Panel Kontrol, Live Data Dashboard, & Table Preview
        control_frame = tk.Frame(window, bg="#34495e", padx=12, pady=12, relief=tk.RAISED, bd=2)
        control_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)

        tk.Label(control_frame, text="RISET MOTION DETECTOR - zidCreative R&D", fg="white", bg="#34495e", font=("Arial", 11, "bold")).pack(pady=3)

        # --- KOTAK PEMBATAS (LABEL FRAME) UNTUK SLIDER ---
        config_frame = tk.LabelFrame(control_frame, text="Konfigurasi pra-pengamatan", fg="white", bg="#34495e", font=("Arial", 9, "bold"), padx=8, pady=6)
        config_frame.pack(fill=tk.X, pady=4)
        
        # --- PENGATURAN KAMERA & JARAK ---
        top_config_frame = tk.Frame(config_frame, bg="#34495e")
        top_config_frame.pack(fill=tk.X, pady=3)

        # Dropdown Kamera
        cam_box = tk.Frame(config_frame, bg="#34495e")
        cam_box.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        tk.Label(cam_box, text="Kamera (Index):", fg="white", bg="#34495e", font=("Arial", 8, "bold")).pack(anchor="w")
        self.cam_var = tk.StringVar(value="0")
        self.cam_dropdown = ttk.Combobox(cam_box, textvariable=self.cam_var, values=["0", "1", "2"], state="readonly", font=("Arial", 9, "bold"))
        self.cam_dropdown.pack(fill=tk.X, pady=2)
        self.cam_dropdown.bind("<<ComboboxSelected>>", self.change_camera)

        # Input Jarak
        dist_box = tk.Frame(config_frame, bg="#34495e")
        dist_box.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=(5, 0))
        tk.Label(dist_box, text="Jarak Latar (m):", fg="white", bg="#34495e", font=("Arial", 8, "bold")).pack(anchor="w")
        self.distance_entry = tk.Entry(dist_box, font=("Arial", 9, "bold"), justify="center")
        self.distance_entry.insert(0, "5.0")
        self.distance_entry.pack(fill=tk.X, pady=2)

        # --- TOMBOL KONTROL SESI (MULAI & SELESAI) ---
        btn_container = tk.Frame(control_frame, bg="#34495e")
        btn_container.pack(fill=tk.X, pady=4)

        self.btn_start = tk.Button(btn_container, text="▶ Mulai Deteksi", bg="#27ae60", fg="white", font=("Arial", 9, "bold"), command=self.start_session)
        self.btn_start.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=(0, 3))

        self.btn_stop = tk.Button(btn_container, text="⏹ Selesai", bg="#c0392b", fg="white", font=("Arial", 9, "bold"), state=tk.DISABLED, command=self.stop_session)
        self.btn_stop.pack(side=tk.RIGHT, expand=True, fill=tk.X, padx=(3, 0))

        # --- KOTAK PEMBATAS (LABEL FRAME) UNTUK SLIDER ---
        slider_frame = tk.LabelFrame(control_frame, text="Parameter Kontrol & Kalibrasi", fg="white", bg="#34495e", font=("Arial", 9, "bold"), padx=8, pady=6)
        slider_frame.pack(fill=tk.X, pady=4)

        # --- LAYOUT SLIDER 2 KOLOM (KIRI: Threshold & MinArea | KANAN: Brightness) ---
        sliders_container = tk.Frame(slider_frame, bg="#34495e")
        sliders_container.pack(fill=tk.X, pady=2)

        # Kolom Kiri (Threshold & MinArea)
        col_left = tk.Frame(sliders_container, bg="#34495e")
        col_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))

        tk.Label(col_left, text="Sensitivitas Threshold:", fg="white", bg="#34495e", anchor="w", font=("Arial", 8)).pack(fill=tk.X)
        self.thresh_slider = ttk.Scale(col_left, from_=0, to=255, orient=tk.HORIZONTAL, command=self.update_thresh_label)
        self.thresh_slider.set(25)
        self.thresh_slider.pack(fill=tk.X)
        self.thresh_val_label = tk.Label(col_left, text="Nilai: 25", fg="#1abc9c", bg="#34495e", font=("Arial", 8), anchor="w")
        self.thresh_val_label.pack(fill=tk.X, pady=(0, 4))

        tk.Label(col_left, text="Minimum Area Objek:", fg="white", bg="#34495e", anchor="w", font=("Arial", 8)).pack(fill=tk.X)
        self.area_slider = ttk.Scale(col_left, from_=100, to=10000, orient=tk.HORIZONTAL, command=self.update_area_label)
        self.area_slider.set(1000)
        self.area_slider.pack(fill=tk.X)
        self.area_val_label = tk.Label(col_left, text="Nilai: 1000", fg="#1abc9c", bg="#34495e", font=("Arial", 8), anchor="w")
        self.area_val_label.pack(fill=tk.X)

        # Kolom Kanan (Brightness / Kecerahan)
        col_right = tk.Frame(sliders_container, bg="#34495e")
        col_right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))

        tk.Label(col_right, text="Kecocokan Kecerahan:", fg="white", bg="#34495e", anchor="w", font=("Arial", 8)).pack(fill=tk.X)
        self.brightness_slider = ttk.Scale(col_right, from_=-100, to=100, orient=tk.HORIZONTAL, command=self.update_brightness_label)
        self.brightness_slider.set(0)
        self.brightness_slider.pack(fill=tk.X)
        self.brightness_val_label = tk.Label(col_right, text="Nilai: 0 (Normal)", fg="#1abc9c", bg="#34495e", font=("Arial", 8), anchor="w")
        self.brightness_val_label.pack(fill=tk.X, pady=(0, 20))  # Menyesuaikan tinggi dengan kolom kiri

        # --- LIVE DATA LOGGING DASHBOARD ---
        log_frame = tk.LabelFrame(control_frame, text="Live Data Metrik (Kalkulasi Jarak)", fg="white", bg="#34495e", font=("Arial", 9, "bold"), padx=6, pady=4)
        log_frame.pack(fill=tk.X, pady=3)

        def add_row(parent, label_text, var_ref, row_idx):
            lbl_title = tk.Label(parent, text=label_text, fg="#bdc3c7", bg="#34495e", anchor="w", font=("Arial", 8))
            lbl_title.grid(row=row_idx, column=0, sticky="w", pady=1)
            lbl_val = tk.Label(parent, textvariable=var_ref, fg="#f39c12", bg="#34495e", anchor="e", font=("Arial", 8, "bold"))
            lbl_val.grid(row=row_idx, column=1, sticky="e", pady=1)

        log_frame.columnconfigure(0, weight=1)
        log_frame.columnconfigure(1, weight=1)

        add_row(log_frame, "Threshold Aktif:", self.log_thresh, 0)
        add_row(log_frame, "Jarak Kamera (D):", self.log_distance, 1)
        add_row(log_frame, "Piksel Bergerak (%):", self.log_pixel_pct, 2)
        add_row(log_frame, "Estimasi Luas Fisik:", self.log_est_size, 3)
        add_row(log_frame, "Kontur Terdeteksi:", self.log_contours, 4)
        add_row(log_frame, "Status Sesi:", self.log_status, 5)

        # --- LIVE TABLE PREVIEW (TREEVIEW LENGKAP) ---
        preview_frame = tk.LabelFrame(control_frame, text="Live Table Preview (Log Lengkap)", fg="white", bg="#34495e", font=("Arial", 9, "bold"), padx=5, pady=4)
        preview_frame.pack(fill=tk.BOTH, expand=True, pady=3)

        columns = ("time", "thresh", "min_area", "pct", "size", "contours", "status")
        self.tree = ttk.Treeview(preview_frame, columns=columns, show="headings", height=3)
        self.tree.heading("time", text="Waktu")
        self.tree.heading("thresh", text="Thr")
        self.tree.heading("min_area", text="MinArea")
        self.tree.heading("pct", text="Piksel%")
        self.tree.heading("size", text="Luas(m²)")
        self.tree.heading("contours", text="Kontur")
        self.tree.heading("status", text="Status")

        self.tree.column("time", width=55, anchor="center")
        self.tree.column("thresh", width=30, anchor="center")
        self.tree.column("min_area", width=50, anchor="center")
        self.tree.column("pct", width=45, anchor="center")
        self.tree.column("size", width=50, anchor="center")
        self.tree.column("contours", width=40, anchor="center")
        self.tree.column("status", width=55, anchor="center")
        self.tree.pack(fill=tk.BOTH, expand=True)

        # Tombol Keluar
        btn_exit = tk.Button(control_frame, text="Keluar Aplikasi", bg="#7f8c8d", fg="white", font=("Arial", 8, "bold"), command=self.on_closing)
        btn_exit.pack(fill=tk.X, pady=(2, 0))

        # Jalankan Loop Pembaruan Frame GUI
        self.delay = 15 
        self.update_frame()

        self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.window.mainloop()

    def update_thresh_label(self, val):
        self.thresh_val_label.config(text=f"Nilai: {int(float(val))}")

    def update_area_label(self, val):
        self.area_val_label.config(text=f"Nilai: {int(float(val))}")

    def update_brightness_label(self, val):
        b_val = int(float(val))
        self.brightness_val_label.config(text=f"Nilai: {b_val}")

    def change_camera(self, event=None):
        if self.is_started:
            return
        try:
            new_cam_id = int(self.cam_var.get())
            if self.cap.isOpened():
                self.cap.release()
            
            self.cap = cv2.VideoCapture(new_cam_id, cv2.CAP_DSHOW)
            self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
            self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
            
            ret, self.frame1 = self.cap.read()
            if ret:
                brightness_val = int(self.brightness_slider.get())
                self.frame1 = cv2.convertScaleAbs(self.frame1, alpha=1.0, beta=brightness_val)
                self.gray1 = cv2.cvtColor(self.frame1, cv2.COLOR_BGR2GRAY)
                self.gray1 = cv2.GaussianBlur(self.gray1, (21, 21), 0)
            else:
                self.gray1 = None
            
            print(f"[INFO] Kamera berhasil dialihkan ke index: {new_cam_id}")
        except Exception as e:
            print(f"[ERROR] Gagal mengganti kamera: {e}")

    def start_session(self):
        try:
            self.camera_distance = float(self.distance_entry.get())
        except ValueError:
            self.camera_distance = 5.0

        self.is_started = True
        self.btn_start.config(state=tk.DISABLED, bg="#95a5a6")
        self.btn_stop.config(state=tk.NORMAL, bg="#c0392b")
        self.distance_entry.config(state=tk.DISABLED)
        self.cam_dropdown.config(state=tk.DISABLED)

        session_time = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        self.csv_filename = f"logs/experiment_log_{session_time}.csv"
        
        self.csv_file = open(self.csv_filename, mode='w', newline='', encoding='utf-8')
        self.csv_writer = csv.writer(self.csv_file)
        
        # Header CSV lengkap dengan Brightness tanpa menghilangkan parameter lain
        self.csv_writer.writerow(["Timestamp", "Threshold", "MinArea", "CameraDistance_m", "Brightness", "PixelChangedPct", "EstimatedArea_m2", "ContoursCount", "StatusRecord"])
        
        print(f"[INFO] Sesi riset dimulai. Kamera ID: {self.cam_var.get()} | Jarak: {self.camera_distance}m | Log: {self.csv_filename}")

    def stop_session(self):
        self.is_started = False
        self.btn_start.config(state=tk.NORMAL, bg="#27ae60")
        self.btn_stop.config(state=tk.DISABLED, bg="#95a5a6")
        self.distance_entry.config(state=tk.NORMAL)
        self.cam_dropdown.config(state="readonly")

        if self.is_recording and self.out is not None:
            self.out.release()
            self.is_recording = False

        if self.csv_file is not None:
            self.csv_file.close()
            self.csv_file = None
            print(f"[INFO] Sesi riset selesai. File CSV berhasil disimpan.")

        self.log_status.set("SELESAI (Idle)")

    def update_frame(self):
        ret, raw_frame = self.cap.read()
        if ret:
            current_time = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3]
            full_timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]

            thresh_val = int(self.thresh_slider.get())
            min_area = int(self.area_slider.get())
            brightness_val = int(self.brightness_slider.get())

            frame2 = cv2.convertScaleAbs(raw_frame, alpha=1.0, beta=brightness_val)
            display_frame = frame2.copy()

            self.log_thresh.set(str(thresh_val))
            self.log_distance.set(f"{self.camera_distance} m")
            self.log_timestamp.set(full_timestamp)

            gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
            gray2 = cv2.GaussianBlur(gray2, (21, 21), 0)

            pixel_pct = 0.0
            total_contour_pixel_area = 0.0
            valid_contours_count = 0
            record_status_str = "STANDBY"

            if self.gray1 is not None:
                diff = cv2.absdiff(self.gray1, gray2)
                thresh = cv2.threshold(diff, thresh_val, 255, cv2.THRESH_BINARY)[1]
                thresh = cv2.dilate(thresh, None, iterations=2)

                non_zero_pixels = cv2.countNonZero(thresh)
                pixel_pct = (non_zero_pixels / self.total_pixels) * 100.0
                self.log_pixel_pct.set(f"{pixel_pct:.3f} %")

                contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

                motion_detected = False
                calibration_factor = 0.00005 

                for i, contour in enumerate(contours):
                    area_px = cv2.contourArea(contour)
                    if area_px < min_area:
                        continue

                    valid_contours_count += 1
                    total_contour_pixel_area += area_px
                    motion_detected = True
                    
                    contour_area_m2 = area_px * (self.camera_distance ** 2) * calibration_factor

                    (x, y, w, h) = cv2.boundingRect(contour)
                    cv2.rectangle(display_frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
                    
                    label_text = f"#{valid_contours_count} | {contour_area_m2:.2f}m²"
                    cv2.putText(display_frame, label_text, (x, max(y - 10, 20)),
                                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

                estimated_real_area_m2 = total_contour_pixel_area * (self.camera_distance ** 2) * calibration_factor
                self.log_est_size.set(f"{estimated_real_area_m2:.3f} m²")

                self.log_contours.set(str(valid_contours_count))
                cv2.putText(display_frame, f"Waktu: {full_timestamp} | Brig: {brightness_val} | Jarak: {self.camera_distance}m", (10, 30),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 0, 255), 2)

                if self.is_started:
                    if motion_detected:
                        self.no_motion_frames = 0
                        if not self.is_recording:
                            self.is_recording = True
                            video_filename = f"recordings/session_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.avi"
                            fourcc = cv2.VideoWriter_fourcc(*'XVID')
                            self.out = cv2.VideoWriter(video_filename, fourcc, self.fps, (self.frame_width, self.frame_height))
                        
                        record_status_str = "MEREKAM"
                        self.log_status.set("MEREKAM (REC)")
                        cv2.circle(display_frame, (self.frame_width - 30, 30), 10, (0, 0, 255), -1)
                    else:
                        if self.is_recording:
                            self.no_motion_frames += 1
                            if self.no_motion_frames > self.MAX_NO_MOTION_FRAMES:
                                self.is_recording = False
                                if self.out is not None:
                                    self.out.release()
                            else:
                                record_status_str = "JEDA"
                                self.log_status.set("Menunggu Jeda...")
                        else:
                            record_status_str = "STANDBY"
                            self.log_status.set("SESI AKTIF (Aman)")

                    if self.is_recording and self.out is not None:
                        self.out.write(display_frame)

                    if self.csv_writer is not None:
                        self.csv_writer.writerow([
                            full_timestamp, thresh_val, min_area, self.camera_distance, brightness_val,
                            f"{pixel_pct:.3f}", f"{estimated_real_area_m2:.4f}", 
                            valid_contours_count, record_status_str
                        ])
                        
                        self.tree.insert("", 0, values=(current_time, thresh_val, min_area, f"{pixel_pct:.1f}%", f"{estimated_real_area_m2:.2f}m²", valid_contours_count, record_status_str))
                        if len(self.tree.get_children()) > 15:
                            last_item = self.tree.get_children()[-1]
                            self.tree.delete(last_item)
                else:
                    self.log_status.set("IDLE (Klik Mulai)")

                self.gray1 = gray2.copy()

            # Render ke Kanvas GUI
            img_live = cv2.cvtColor(display_frame, cv2.COLOR_BGR2RGB)
            img_live = Image.fromarray(img_live).resize((480, 360), Image.Resampling.LANCZOS)
            self.photo_live = ImageTk.PhotoImage(image=img_live)
            self.canvas_live.create_image(0, 0, image=self.photo_live, anchor=tk.NW)

            img_thresh = cv2.cvtColor(thresh, cv2.COLOR_GRAY2RGB) if 'thresh' in locals() else cv2.cvtColor(gray2, cv2.COLOR_GRAY2RGB)
            img_thresh = Image.fromarray(img_thresh).resize((480, 360), Image.Resampling.LANCZOS)
            self.photo_thresh = ImageTk.PhotoImage(image=img_thresh)
            self.canvas_thresh.create_image(0, 0, image=self.photo_thresh, anchor=tk.NW)

        self.window.after(self.delay, self.update_frame)

    def on_closing(self):
        self.stop_session()
        if self.cap.isOpened():
            self.cap.release()
        self.window.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    app = MotionDetectorApp(root, "Riset Motion Detector - zidCreative R&D")