import serial
import time
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from matplotlib.patches import Polygon

# --- CONFIGURATION ---
COM_PORT = 'COM10'  # Change to your actual port
BAUD_RATE = 9600
ATTEMPT_THRESHOLD = 5  # degrees
ATTEMPT_COOLDOWN = 1  # seconds below threshold to end attempt
NUM_ATTEMPTS = 5
BIN_SIZE = 10  # degrees

Y_RANGE = 90  # Range for pointer visualization
YELLOW_THRESHOLD = 0.15*180/3.14  # Degrees for yellow region
RED_THRESHOLD = 2*YELLOW_THRESHOLD  # Degrees for red region

# --- SETUP SERIAL ---
ser = serial.Serial(COM_PORT, BAUD_RATE, timeout=1)
time.sleep(2)

def parse_line(line):
    try:
        parts = line.strip().split(',')
        if len(parts) >= 3:
            is_high = int(parts[0])
            x_deg = float(parts[1]) * 180 / 3.14
            y_deg = float(parts[2]) * 180 / 3.14
            return is_high,x_deg, y_deg
    except:
        return None
    return None

# --- SETUP PLOT (Pointer-Over-Colored-Bar) ---
plt.ion()
fig, ax = plt.subplots(figsize=(7, 3))
ax.set_xlim(-Y_RANGE, Y_RANGE)
ax.set_ylim(-1, 1)
ax.set_xticks(np.linspace(-Y_RANGE, Y_RANGE, 7))
ax.axis('off')

# Draw colored bar background (red-yellow-green-yellow-red)
bar_height = 0.3
green_width = 2 * YELLOW_THRESHOLD
yellow_width = RED_THRESHOLD - YELLOW_THRESHOLD
red_width = Y_RANGE - RED_THRESHOLD
# Red left
ax.add_patch(plt.Rectangle((-Y_RANGE, -bar_height / 2), red_width, bar_height, color='red', zorder=0))
# Yellow left
ax.add_patch(plt.Rectangle((-RED_THRESHOLD, -bar_height/2), yellow_width, bar_height, color='yellow', zorder=0))
# Green center
ax.add_patch(plt.Rectangle((-YELLOW_THRESHOLD, -bar_height/2), green_width, bar_height, color='limegreen', zorder=0))
# Yellow right
ax.add_patch(plt.Rectangle((YELLOW_THRESHOLD, -bar_height/2), yellow_width, bar_height, color='yellow', zorder=0))
# Red right
ax.add_patch(plt.Rectangle((RED_THRESHOLD, -bar_height/2), red_width, bar_height, color='red', zorder=0))

# Draw pointer (initially centered)
pointer_line, = ax.plot([0, 0], [-0.5, 0.5], color='black', linewidth=5, zorder=2)

attempts = []
current_attempt = []
attempt_started = False
attempt_start_time = None
cooldown_start = None

print("Listening for data...")

# Placeholders for annotation objects so we can remove/redraw them
value_text = None
counter_text = None
arrow_patch = None
direction_text = None

def get_bar_color(y):
    if -YELLOW_THRESHOLD < y < YELLOW_THRESHOLD:
        return 'limegreen'
    elif YELLOW_THRESHOLD <= y < RED_THRESHOLD or -RED_THRESHOLD < y <= -YELLOW_THRESHOLD:
        return 'yellow'
    else:
        return 'red'

try:
    while len(attempts) < NUM_ATTEMPTS:
        line_raw = ser.readline().decode('utf-8')
        result = parse_line(line_raw)

        if result:
            is_high, x, y = result
            now = time.time()

            if is_high == 1:
                print("Button pressed! Resetting attempts...")
                attempts = []
                attempt_started = False
                cooldown_start = None
                pointer_line.set_xdata([0, 0])
                if value_text:
                    value_text.remove()
                    value_text = None
                if arrow_patch:
                    arrow_patch.remove()
                    arrow_patch = None
                if direction_text:
                    direction_text.remove()
                    direction_text = None
                if counter_text:
                    counter_text.remove()
                    counter_text = None
                fig.canvas.draw()
                fig.canvas.flush_events()
                continue

            if x > ATTEMPT_THRESHOLD:
                if not attempt_started:
                    print(f"\nAttempt {len(attempts)+1} started")
                    attempt_start_time = now
                    current_attempt = []
                    attempt_started = True
                    cooldown_start = None

                t = now - attempt_start_time
                current_attempt.append((t, x, y))

                # --- LIVE POINTER UPDATE ---
                y_clamped = max(-Y_RANGE, min(Y_RANGE, y))
                pointer_line.set_xdata([y_clamped, y_clamped])


                # Display value annotation above pointer
                if value_text: value_text.remove()
                value_text = ax.text(
                    y_clamped, 0.6, f"{y_clamped:.1f}°",
                    ha='center', va='bottom', fontsize=14, color='black', backgroundcolor='white', zorder=3
                )

                # Show attempt counter below the bar
                if counter_text: counter_text.remove()
                counter_text = ax.text(
                    0, -0.85, f"Attempt {len(attempts)+1} / {NUM_ATTEMPTS}",
                    ha='center', va='bottom', fontsize=14, color='black', zorder=3
                )

                # Remove previous arrow/direction text
                if arrow_patch:
                    arrow_patch.remove()
                    arrow_patch = None
                if direction_text:
                    direction_text.remove()
                    direction_text = None

                # Arrow and label: always centered above value text, colored based on region
                arrow_y = 0.85
                arrow_size_x = 10
                arrow_size_y = 0.13
                arrow_center_x = 0
                label_y = 0.97

                if y > YELLOW_THRESHOLD:
                    # Arrow points left, colored per bar, and text 'move left'
                    col = get_bar_color(y)
                    tip = (arrow_center_x - arrow_size_x, arrow_y)
                    base1 = (arrow_center_x + arrow_size_x, arrow_y + arrow_size_y)
                    base2 = (arrow_center_x + arrow_size_x, arrow_y - arrow_size_y)
                    arrow_patch = Polygon([tip, base1, base2], closed=True, color=col, zorder=3)
                    ax.add_patch(arrow_patch)
                    direction_text = ax.text(
                        arrow_center_x, label_y, "Move Left", ha='center', va='bottom',
                        fontsize=14, color=col, fontweight='bold', zorder=3
                    )
                elif y < -YELLOW_THRESHOLD:
                    # Arrow points right, colored per bar, and text 'move right'
                    col = get_bar_color(y)
                    tip = (arrow_center_x + arrow_size_x, arrow_y)
                    base1 = (arrow_center_x - arrow_size_x, arrow_y + arrow_size_y)
                    base2 = (arrow_center_x - arrow_size_x, arrow_y - arrow_size_y)
                    arrow_patch = Polygon([tip, base1, base2], closed=True, color=col, zorder=3)
                    ax.add_patch(arrow_patch)
                    direction_text = ax.text(
                        arrow_center_x, label_y, "Move Right", ha='center', va='bottom',
                        fontsize=14, color=col, fontweight='bold', zorder=3
                    )


                fig.canvas.draw()
                fig.canvas.flush_events()


            elif attempt_started:
                if cooldown_start is None:
                    cooldown_start = now
                elif now - cooldown_start >= ATTEMPT_COOLDOWN:
                    print(f"Attempt {len(attempts)+1} ended")
                    attempts.append(current_attempt)
                    attempt_started = False
                    cooldown_start = None
                    # Reset pointer to center when not active and clear optional annotations
                    pointer_line.set_xdata([0, 0])
                    if value_text:
                        value_text.remove()
                        value_text = None
                    if arrow_patch:
                        arrow_patch.remove()
                        arrow_patch = None
                    if direction_text:
                        direction_text.remove()
                        direction_text = None
                    if counter_text:
                        counter_text.remove()
                        counter_text = None
                    fig.canvas.draw()
                    fig.canvas.flush_events()
except KeyboardInterrupt:
    print("\nExiting early...")

ser.close()
plt.ioff()
plt.close(fig) # closes the pointer/attempt window

print("\nData collection complete!")

# --- FINAL COMPARISON PLOT ---
plt.figure(figsize=(10, 6))
for i, attempt in enumerate(attempts):
    t, _, y = zip(*attempt)
    plt.plot(t, y, label=f'Attempt {i+1}')
plt.xlabel('Time (s)')
plt.ylabel('Y value (°)')
plt.title('Y over Time for All Attempts')
plt.legend()
plt.grid(True)
plt.tight_layout()
# Add horizontal threshold marker lines
plt.axhline(YELLOW_THRESHOLD, color='red', linestyle='--', linewidth=1)
plt.axhline(-YELLOW_THRESHOLD, color='red', linestyle='--', linewidth=1)
plt.show()

# --- BINNED TABLES ---
print("\nBinned Average Y values (10° bins of X) per Attempt:")
bin_edges = np.arange(-90, 100, BIN_SIZE)
for i, attempt in enumerate(attempts):
    df = pd.DataFrame(attempt, columns=["time", "x", "y"])
    df["x_bin"] = pd.cut(df["x"], bins=bin_edges)
    bin_means = df.groupby("x_bin")["y"].mean().dropna()
    print(f"\nAttempt {i+1} Binned Averages:")
    print(bin_means.round(2).to_frame())