import cv2
import time
import numpy as np

# Optimized settings for old laptops
CAM_WIDTH = 480
CAM_HEIGHT = 270
DESIRED_FPS = 30
MIN_MOTION_AREA = 15  # Lower for small birds
MOTION_THRESHOLD = 7  # More sensitive
MERGE_DISTANCE = 15    # Distance to merge rectangles
TRACK_FRAMES = 600       # Frames to keep tracking after motion lost

# Initialize camera
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, CAM_WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, CAM_HEIGHT)
cap.set(cv2.CAP_PROP_FPS, DESIRED_FPS)

# Optimizations for weak systems
cv2.setUseOptimized(True)
cv2.setNumThreads(1)

if not cap.isOpened():
    print("Error: Camera not accessible!")
    exit()

# Get first frame for reference
_, prev_frame = cap.read()
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
prev_gray = cv2.GaussianBlur(prev_gray, (5, 5), 0)

print("Motion detection system activated!")
print(f"Resolution: {int(cap.get(3))}x{int(cap.get(4))}")
print(f"Frame rate: {cap.get(cv2.CAP_PROP_FPS):.1f}")
print("Press Q to exit")

# Tracking variables
tracked_rect = None
track_counter = 0

def merge_rectangles(rects):
    """Merge nearby rectangles into one"""
    if not rects:
        return []
    
    # Convert to centers for clustering
    centers = np.array([(x + w/2, y + h/2) for (x, y, w, h) in rects])
    
    # Simple clustering by distance
    merged = []
    while centers.size > 0:
        # Get first center
        center = centers[0]
        
        # Find all centers within merge distance
        distances = np.linalg.norm(centers - center, axis=1)
        close_indices = np.where(distances < MERGE_DISTANCE)[0]
        
        # Get corresponding rectangles
        close_rects = [rects[i] for i in close_indices]
        
        # Merge rectangles
        x_min = min(r[0] for r in close_rects)
        y_min = min(r[1] for r in close_rects)
        x_max = max(r[0] + r[2] for r in close_rects)
        y_max = max(r[1] + r[3] for r in close_rects)
        
        merged_rect = (x_min, y_min, x_max - x_min, y_max - y_min)
        merged.append(merged_rect)
        
        # Remove processed rectangles
        centers = np.delete(centers, close_indices, axis=0)
        rects = [r for i, r in enumerate(rects) if i not in close_indices]
    
    return merged

try:
    while True:
        start_time = time.time()
        ret, frame = cap.read()
        if not ret:
            continue
        
        # Convert to grayscale and blur
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        gray = cv2.GaussianBlur(gray, (5, 5), 0)
        
        # Compute absolute difference
        frame_diff = cv2.absdiff(prev_gray, gray)
        _, thresh = cv2.threshold(frame_diff, MOTION_THRESHOLD, 255, cv2.THRESH_BINARY)
        
        # Find contours
        contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # Process contours
        motion_rects = []
        for contour in contours:
            if cv2.contourArea(contour) > MIN_MOTION_AREA:
                motion_rects.append(cv2.boundingRect(contour))
        
        # Merge nearby rectangles
        merged_rects = merge_rectangles(motion_rects)
        
        # Update tracking
        motion_detected = bool(merged_rects)
        if motion_detected:
            # Track the largest detected object
            tracked_rect = max(merged_rects, key=lambda r: r[2]*r[3])
            track_counter = TRACK_FRAMES
        elif track_counter > 0:
            # Continue tracking last known position
            track_counter -= 1
            motion_detected = True
        
        # Draw tracking rectangle
        if motion_detected and tracked_rect:
            x, y, w, h = tracked_rect
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
            cv2.putText(frame, "Tracking", (x, y-10), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
        
        # Draw merged rectangles
        for rect in merged_rects:
            x, y, w, h = rect
            cv2.rectangle(frame, (x, y), (x + w, y + h), (50, 50, 255), 1)
        
        # Display status
        status = "Motion: " + ("Detected" if motion_detected else "Searching")
        cv2.putText(frame, status, (10, 20), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
        
        # Display FPS
        fps = 1.0 / (time.time() - start_time)
        cv2.putText(frame, f"FPS: {fps:.1f}", (10, 40), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
        
        # Show grayscale processed image and color result
        cv2.imshow('Processed (Grayscale)', thresh)
        cv2.imshow('Bird Motion Detection', frame)
        
        # Update previous frame
        prev_gray = gray
        
        # Exit on Q key
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

except Exception as e:
    print(f"Error: {str(e)}")

finally:
    cap.release()
    cv2.destroyAllWindows()
    print("System resources released")
