import cv2
import numpy as np
from collections import deque
import serial
import serial.tools.list_ports
import tkinter as tk
from tkinter import ttk, messagebox
import threading
import time

# 시리얼 포트 설정
ser = None
moving_avg_frame = None  # 전역 변수로 선언
moving_windows_size = 30    # 웹캠의 FPS 30기준 90설정으로 3초 무빙 적용
def connect_serial(port):
    global ser
    try:
        ser = serial.Serial(port, 9600, timeout=1)
        messagebox.showinfo("Info", f"Connected to {port}")
    except Exception as e:
        messagebox.showerror("Error", str(e))

def disconnect_serial():
    global ser
    if ser and ser.is_open:
        ser.close()
        messagebox.showinfo("Info", "Disconnected")

def send_serial_data(data):
    global ser
    if ser and ser.is_open:
        print(data)
        ser.write(data.encode())

def get_serial_ports():
    ports = serial.tools.list_ports.comports()
    return [port.device for port in ports]

def on_select_port(event):
    global selected_port
    selected_port = event.widget.get()

def create_gui(window_width=400, window_height=300):
    global selected_port
    selected_port = None

    root = tk.Tk()
    root.title("Serial Port Selector")
    root.geometry(f"{window_width}x{window_height}")

    tk.Label(root, text="Select Serial Port:").pack(pady=10)

    ports = get_serial_ports()
    if not ports:
        messagebox.showerror("Error", "No serial ports found")
        return

    port_var = tk.StringVar(value=ports)
    port_menu = ttk.Combobox(root, textvariable=port_var, values=ports)
    port_menu.pack(pady=10)
    port_menu.bind("<<ComboboxSelected>>", on_select_port)

    connect_button = tk.Button(root, text="Connect", command=lambda: connect_serial(selected_port))
    connect_button.pack(pady=10)

    disconnect_button = tk.Button(root, text="Disconnect", command=disconnect_serial)
    disconnect_button.pack(pady=10)

    root.mainloop()

def send_frame_data(data):
    global moving_avg_frame, ser

    if moving_avg_frame is not None and ser and ser.is_open:
        frame_data = "[" + ",".join(map(str, data)) + "]"
        send_serial_data(frame_data)

def main():
    global moving_avg_frame, list_moving_avg_color

    list_moving_avg_color = list()

    cap = cv2.VideoCapture(0)  # 0번 카메라 사용

    if not cap.isOpened():
        print("웹캠을 열 수 없습니다.")
        return

    # 각 구역의 밝기 값을 저장할 데이터 구조
    num_rows, num_cols = 3, 4
    block_brightness_history = [[deque(maxlen=moving_windows_size) for _ in range(num_cols)] for _ in range(num_rows)]

    # 시리얼 데이터를 송신하는 스레드 시작
    #threading.Thread(target=send_frame_data, daemon=True).start()
    start_time = 0
    end_time = 0

    while True:
        ret, frame = cap.read()
        if not ret:
            break

        # 프레임을 YUV 색상 모델로 변환
        yuv = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV)

        # Y 채널 (밝기)만 추출
        y_channel = yuv[:, :, 0]

        # 프레임 크기
        height, width = y_channel.shape

        # 각 구역의 크기
        block_height = height // num_rows
        block_width = width // num_cols

        # 새로운 빈 이미지 생성
        output_frame = np.zeros((height, width, 3), dtype=np.uint8)
        moving_avg_frame = np.zeros((height, width, 3), dtype=np.uint8)

        secotrCnt = 0
        for i in range(num_rows):
            for j in range(num_cols):
                # 각 구역에 대한 좌표 계산
                y_start = i * block_height
                y_end = (i + 1) * block_height
                x_start = j * block_width
                x_end = (j + 1) * block_width

                # 해당 구역의 밝기 정보 추출
                block = y_channel[y_start:y_end, x_start:x_end]

                # 구역의 평균 밝기 값 계산
                avg_brightness = np.mean(block)

                # 밝기 값을 기록
                block_brightness_history[i][j].append(avg_brightness)

                # 무빙 에버리지 계산
                moving_avg_brightness = np.mean(block_brightness_history[i][j])

                # 평균 밝기 값을 시각화 색상으로 매핑
                color = map_brightness_to_color(avg_brightness)
                moving_avg_color = map_brightness_to_color(moving_avg_brightness)

                # 구역에 평균 밝기 값을 시각화하여 출력 프레임에 적용
                output_frame[y_start:y_end, x_start:x_end] = color
                moving_avg_frame[y_start:y_end, x_start:x_end] = moving_avg_color

                # RS232로 전송할 섹션에 계산된 moving_avg_color을 리스트에 저장 로직
                if len(list_moving_avg_color) < 12:
                    list_moving_avg_color.append(int(round(moving_avg_brightness)))
                else:
                    list_moving_avg_color[secotrCnt] = int(round(moving_avg_brightness))

                # 구역의 번호 표시
                cv2.putText(output_frame, f"Sector {int(secotrCnt)}", (x_start + 10, y_start + 50),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
                cv2.putText(moving_avg_frame, f"Sector {int(secotrCnt)}", (x_start + 10, y_start + 50),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
                secotrCnt += 1

                # 구역의 평균 밝기 값을 화면에 숫자로 표시
                cv2.putText(output_frame, f"{int(avg_brightness)}", (x_start + 10, y_end - 20),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
                cv2.putText(moving_avg_frame, f"{int(moving_avg_brightness)}", (x_start + 10, y_end - 20),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)

        # 원본 이미지
        cv2.imshow('(1)Original Model', frame)

        # RGB to YUV 영상
        cv2.imshow('(2)Color Model(RGB to YUV)', yuv)

        # 밝기 정보를 화면에 표시
        cv2.imshow('(3)Y Channel (Brightness)', output_frame)

        # 무빙 에버리지 필터 적용 결과를 새로운 창에 표시
        cv2.imshow('(4)Moving Average Brightness', moving_avg_frame)

        # 'q' 키를 누르면 종료
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
        
        end_time = time.time() * 1000
        if end_time-start_time > 100:
            if(len(list_moving_avg_color) == 12):
                send_frame_data(list_moving_avg_color)
                start_time = time.time()*1000
                
    cap.release()
    cv2.destroyAllWindows()

def map_brightness_to_color(brightness):
    # 밝기 값을 시각화 색상으로 매핑
    color_value = int(brightness)
    return np.array([color_value, color_value, color_value], dtype=np.uint8)

if __name__ == "__main__":
    # GUI 창의 크기를 설정하는 변수
    window_width = 500  # 원하는 가로 크기로 수정
    window_height = 400  # 원하는 세로 크기로 수정

    threading.Thread(target=lambda: create_gui(window_width, window_height), daemon=True).start()
    main()