#!/usr/bin/env python3
"""
Visualize and manually decode printer tracking dots
Helps identify the 15x8 grid pattern
"""
from PIL import Image, ImageDraw, ImageFont
import numpy as np
import os

def visualize_dot_pattern(image_path="yd_Zard_uncompressed_dots.png"):
    """Create a visualization to help manually decode the pattern"""
    
    print(f"Analyzing {image_path}...")
    
    # Load the dots image
    img = Image.open(image_path)
    img_array = np.array(img)
    
    print(f"Image size: {img_array.shape}")
    
    # Convert to grayscale if needed
    if len(img_array.shape) == 3:
        gray = np.mean(img_array, axis=2)
    else:
        gray = img_array
    
    # Find potential dots (bright pixels)
    threshold = np.percentile(gray, 99.5)
    dots_mask = gray > threshold
    
    # Get dot coordinates
    y_coords, x_coords = np.where(dots_mask)
    
    print(f"Found {len(x_coords)} potential dot pixels")
    
    if len(x_coords) == 0:
        print("No dots found! Trying lower threshold...")
        threshold = np.percentile(gray, 95)
        dots_mask = gray > threshold
        y_coords, x_coords = np.where(dots_mask)
        print(f"Found {len(x_coords)} potential dots at 95th percentile")
    
    if len(x_coords) > 0:
        # Try to find the grid pattern
        # Printer dots are typically arranged in a regular 15x8 grid
        
        # Focus on a small region to find the pattern
        crop_size = 500
        crop = gray[:crop_size, :crop_size]
        crop_threshold = np.percentile(crop, 98)
        crop_dots = crop > crop_threshold
        crop_y, crop_x = np.where(crop_dots)
        
        print(f"\nIn top-left {crop_size}x{crop_size} region: {len(crop_x)} dots")
        
        if len(crop_x) > 10:
            # Sort and find spacing
            sorted_x = np.sort(crop_x)
            sorted_y = np.sort(crop_y)
            
            # Find differences
            x_diffs = np.diff(sorted_x)
            y_diffs = np.diff(sorted_y)
            
            # Filter reasonable spacing (5-200 pixels)
            x_valid = x_diffs[(x_diffs > 5) & (x_diffs < 200)]
            y_valid = y_diffs[(y_diffs > 5) & (y_diffs < 200)]
            
            if len(x_valid) > 0:
                # Find most common spacing using histogram
                hist, bins = np.histogram(x_valid, bins=30)
                most_common_x_spacing = bins[np.argmax(hist)]
                print(f"Estimated X spacing: {most_common_x_spacing:.1f} pixels")
            
            if len(y_valid) > 0:
                hist, bins = np.histogram(y_valid, bins=30)
                most_common_y_spacing = bins[np.argmax(hist)]
                print(f"Estimated Y spacing: {most_common_y_spacing:.1f} pixels")
            
            # Create enhanced visualization
            viz_scale = 5
            viz = Image.fromarray(crop).convert('RGB')
            viz = viz.resize((crop_size * viz_scale, crop_size * viz_scale), Image.NEAREST)
            draw = ImageDraw.Draw(viz)
            
            # Mark all dots in red
            for x, y in zip(crop_x, crop_y):
                x_scaled = x * viz_scale
                y_scaled = y * viz_scale
                draw.ellipse([x_scaled-8, y_scaled-8, x_scaled+8, y_scaled+8], 
                           fill='red', outline='yellow', width=2)
            
            viz.save("dot_pattern_visualization.png")
            print(f"\nSaved visualization to: dot_pattern_visualization.png")
            print("Look for a 15x8 grid pattern of red dots")
            
            # Try to extract a 15x8 grid if we found spacing
            if len(x_valid) > 0 and len(y_valid) > 0:
                print("\n" + "="*70)
                print("Attempting to extract 15x8 grid...")
                print("="*70)
                
                # Use median spacing
                x_spacing = np.median(x_valid)
                y_spacing = np.median(y_valid)
                
                # Find the top-left corner (minimum x and y with dots)
                start_x = crop_x.min()
                start_y = crop_y.min()
                
                print(f"Grid starting at: ({start_x}, {start_y})")
                print(f"Spacing: X={x_spacing:.1f}, Y={y_spacing:.1f}")
                
                # Try to extract a 15x8 grid
                grid = np.zeros((8, 15), dtype=int)
                
                for row in range(8):
                    for col in range(15):
                        expected_x = start_x + col * x_spacing
                        expected_y = start_y + row * y_spacing
                        
                        # Check if there's a dot near this position
                        tolerance = x_spacing / 3
                        nearby = ((crop_x > expected_x - tolerance) & 
                                 (crop_x < expected_x + tolerance) &
                                 (crop_y > expected_y - tolerance) & 
                                 (crop_y < expected_y + tolerance))
                        
                        if np.any(nearby):
                            grid[row, col] = 1
                
                print("\nExtracted grid (1=dot, 0=no dot):")
                print("Rows (top to bottom), Columns (left to right)")
                print("    " + "".join(f"{i:2}" for i in range(1, 16)))
                for i, row in enumerate(grid):
                    print(f"R{i}: " + "".join(f" {val}" for val in row))
                
                # Decode the pattern
                print("\n" + "="*70)
                print("DECODING:")
                print("="*70)
                
                # Each column encodes a number in binary (ignoring first row)
                decoded = []
                for col_num in range(1, 15):  # Columns 1-14 (0-indexed: 0-13)
                    col = grid[1:, col_num]  # Skip first row (row 0)
                    # Read as binary (top to bottom = MSB to LSB)
                    value = sum(bit * (2 ** (6 - idx)) for idx, bit in enumerate(col))
                    decoded.append(value)
                    
                    # Identify what this column represents
                    description = ""
                    if col_num == 1:
                        description = f"Minutes: {value:02d}"
                    elif col_num == 4:
                        description = f"Hour: {value:02d}"
                    elif col_num == 5:
                        description = f"Day: {value:02d}"
                    elif col_num == 6:
                        description = f"Month: {value:02d}"
                    elif col_num == 7:
                        description = f"Year: 20{value:02d}"
                    elif 10 <= col_num <= 13:
                        description = f"Serial digit {col_num-9}: {value}"
                    
                    if description:
                        print(f"Column {col_num:2d}: {value:3d} ({value:07b}) - {description}")
                
                # Format the result
                if decoded[6] > 0:  # Column 7 (year) should have a value
                    year = f"20{decoded[6]:02d}"
                    month = f"{decoded[5]:02d}"
                    day = f"{decoded[4]:02d}"
                    hour = f"{decoded[3]:02d}"
                    minutes = f"{decoded[0]:02d}"
                    serial = f"{decoded[9]}{decoded[10]}{decoded[11]}{decoded[12]}"
                    
                    print("\n" + "="*70)
                    print("FINAL RESULT:")
                    print("="*70)
                    print(f"Date/Time: {year}_{month}_{day}_{hour}:{minutes}")
                    print(f"Serial: {serial}")
                    print(f"\nFlag format: uoftctf{{{year}_{month}_{day}_{hour}:{minutes}_{serial}}}")
                    print("="*70)

if __name__ == "__main__":
    import sys
    
    # Try the dots image first
    if os.path.exists("yd_Zard_uncompressed_dots.png"):
        visualize_dot_pattern("yd_Zard_uncompressed_dots.png")
    else:
        print("yd_Zard_uncompressed_dots.png not found!")
        print("Run: deda_extract_yd Zard_uncompressed.png -d 300 -e --debug")
