"""
Iambic Morse Code Keyer - Modes A and B
For Feather M4 Express (or compatible CircuitPython board)

Hardware connections:
- D0: Dit paddle input (pulled high, grounds when pressed)
- D1: Dah paddle input (pulled high, grounds when pressed)
- D4: Keying output (high = key down)
- D5: Sidetone output (PWM square wave, 700 Hz)

Author: Dean Mertz, K0MKT
"""

import time
import board
import digitalio
import pwmio

# =============================================================================
# CONFIGURATION - Adjust these to your preferences
# =============================================================================

WPM = 20                    # Words per minute (adjust to taste)
SIDETONE_FREQ = 700        # Sidetone frequency in Hz
WEIGHTING = 1.0            # 1.0 = normal, >1.0 = heavy, <1.0 = light
MODE = 'A'                  # 'A' or 'B' - Iambic keyer mode

# =============================================================================
# TIMING CALCULATIONS
# =============================================================================

# PARIS standard: 50 dot durations = 1 word
# At 1 WPM: 1 word = 60 seconds, so 1 dot = 60/50 = 1.2 seconds
dit_time = (1.2 / WPM) * WEIGHTING

# Standard timing relationships
dah_time = dit_time * 3
element_space = dit_time
letter_space = dit_time * 3
word_space = dit_time * 7

# =============================================================================
# HARDWARE INITIALIZATION
# =============================================================================

# Paddle inputs (active low with internal pullup)
dit_paddle = digitalio.DigitalInOut(board.D0)
dit_paddle.direction = digitalio.Direction.INPUT
dit_paddle.pull = digitalio.Pull.UP

dah_paddle = digitalio.DigitalInOut(board.D1)
dah_paddle.direction = digitalio.Direction.INPUT
dah_paddle.pull = digitalio.Pull.UP

# Keying output (high = key down)
key_out = digitalio.DigitalInOut(board.D4)
key_out.direction = digitalio.Direction.OUTPUT
key_out.value = False

# Sidetone output (PWM) - initialized on first use
sidetone = None

# =============================================================================
# KEY CONTROL FUNCTIONS
# =============================================================================

def key_down():
    """Key down - activate transmitter and sidetone"""
    global sidetone
    key_out.value = True
    # Reinitialize PWM to ensure clean start
    sidetone = pwmio.PWMOut(board.D5, frequency=SIDETONE_FREQ, duty_cycle=32768)

def key_up():
    """Key up - deactivate transmitter and sidetone"""
    global sidetone
    key_out.value = False
    if sidetone is not None:
        sidetone.deinit()  # Completely turn off PWM to eliminate leakage
        sidetone = None

def read_paddles():
    """
    Read paddle state
    Returns: (dit_pressed, dah_pressed)
    Note: Paddles are active LOW (False when pressed)
    """
    dit_pressed = not dit_paddle.value
    dah_pressed = not dah_paddle.value
    return (dit_pressed, dah_pressed)

def send_dit():
    """Send a dit element"""
    key_down()
    time.sleep(dit_time)
    key_up()
    time.sleep(element_space)

def send_dah():
    """Send a dah element"""
    key_down()
    time.sleep(dah_time)
    key_up()
    time.sleep(element_space)

# =============================================================================
# IAMBIC KEYERS
# =============================================================================

def iambic_keyer_mode_a():
    """
    Iambic Mode A keyer implementation
    
    Mode A behavior:
    - Single paddle: sends dits or dahs
    - Both paddles (squeeze): alternates dit-dah-dit-dah...
    - No memory: only sends while paddles held
    - Simpler and more predictable than Mode B
    """
    
    # State variable
    last_element = None      # 'dit' or 'dah' - tracks what we sent last for alternation
    
    print("Iambic CW Keyer - Mode A")
    print(f"Speed: {WPM} WPM")
    print(f"Sidetone: {SIDETONE_FREQ} Hz")
    print("Ready to send...")
    
    while True:
        dit_pressed, dah_pressed = read_paddles()
        
        # Both paddles pressed - alternate based on last element
        if dit_pressed and dah_pressed:
            if last_element == 'dit':
                send_dah()
                last_element = 'dah'
            else:
                send_dit()
                last_element = 'dit'
                
        # Only dit paddle pressed
        elif dit_pressed:
            send_dit()
            last_element = 'dit'
            
        # Only dah paddle pressed
        elif dah_pressed:
            send_dah()
            last_element = 'dah'
        
        else:
            # No paddles pressed - reset state
            last_element = None
        
        # Small delay to prevent overwhelming the CPU
        time.sleep(0.001)

def iambic_keyer_mode_b():
    """
    Iambic Mode B keyer implementation
    
    Mode B behavior:
    - Single paddle: sends dits or dahs
    - Both paddles (squeeze): alternates dit-dah-dit-dah...
    - Remembers opposite paddle press during element
    - Completes one extra element after release
    """
    
    # State variables
    last_element = None      # 'dit' or 'dah' - tracks what we sent last
    squeeze_detected = False  # True when both paddles pressed
    
    print("Iambic CW Keyer - Mode B")
    print(f"Speed: {WPM} WPM")
    print(f"Sidetone: {SIDETONE_FREQ} Hz")
    print("Ready to send...")
    
    while True:
        dit_pressed, dah_pressed = read_paddles()
        
        # Check for squeeze (both paddles pressed)
        if dit_pressed and dah_pressed:
            squeeze_detected = True
        
        # Send elements based on paddle state
        if dit_pressed:
            send_dit()
            last_element = 'dit'
            
            # If squeeze was detected, send opposite element
            if squeeze_detected:
                send_dah()
                last_element = 'dah'
                squeeze_detected = False
                
        elif dah_pressed:
            send_dah()
            last_element = 'dah'
            
            # If squeeze was detected, send opposite element
            if squeeze_detected:
                send_dit()
                last_element = 'dit'
                squeeze_detected = False
        
        else:
            # No paddles pressed - reset state
            last_element = None
            squeeze_detected = False
        
        # Small delay to prevent overwhelming the CPU
        time.sleep(0.001)

# =============================================================================
# MAIN PROGRAM
# =============================================================================

if __name__ == "__main__":
    try:
        # Ensure key is up at startup
        key_up()
        
        # Run the selected keyer mode
        if MODE == 'A':
            iambic_keyer_mode_a()
        elif MODE == 'B':
            iambic_keyer_mode_b()
        else:
            print(f"Error: Invalid MODE '{MODE}'. Use 'A' or 'B'")
        
    except KeyboardInterrupt:
        # Clean shutdown on Ctrl-C
        print("\nShutting down...")
        key_up()
        print("73!")
