import time
import sys
import importlib
import subprocess
import random

# ===============================================================
# AUTO-INSTALL UTILITY
# ===============================================================
def ensure(pkg):
    try:
        importlib.import_module(pkg)
        return True
    except ImportError:
        ans = input(f"Package '{pkg}' is required. Install it? [Y/n] ").strip().lower()
        if ans in ["", "y", "yes"]:
            try:
                subprocess.check_call([sys.executable, "-m", "pip", "install", pkg])
                return True
            except Exception as e:
                print(f"Install failed: {e}")
                return False
        return False


# ===============================================================
# MODE SETUP
# ===============================================================
SIM_REQUESTED = (len(sys.argv) > 2 and sys.argv[2].lower() == "sim")
SIM = SIM_REQUESTED

if not SIM:
    if ensure("RPi.GPIO"):
        try:
            import RPi.GPIO as GPIO
        except Exception:
            SIM = True
    else:
        SIM = True


# ===============================================================
# SIM MODE SETUP
# ===============================================================
if SIM:
    print("SIMULATION MODE (keyboard: 1=KEY1, 2=KEY2, 3=LAUNCH)")
    ensure("keyboard")
    import keyboard

    class FakeGPIO:
        BCM = IN = OUT = PUD_DOWN = None
        pins = {}

        def setmode(self, *a, **k): pass
        def setup(self, pin, mode, pull_up_down=None): self.pins[pin] = 0
        def input(self, pin): return self.pins.get(pin, 0)
        def output(self, pin, value): self.pins[pin] = value
        def cleanup(self): pass

    GPIO = FakeGPIO()

    # Realistic state-driven key simulation
    sim_state = {"k1": False, "k2": False}
    system_wants = {"k1": None, "k2": None, "launch": None}

    def apply_state_change(key):
        """Only change key state when system_wants requests it."""
        if system_wants[key] is None:
            return  # ignore spam presses

        # perform change
        sim_state[key] = system_wants[key]

        if key == "k1":
            GPIO.pins[17] = int(sim_state[key])
            print(f"KEY1 set to {GPIO.pins[17]}")
        elif key == "k2":
            GPIO.pins[27] = int(sim_state[key])
            print(f"KEY2 set to {GPIO.pins[27]}")
        elif key == "launch":
            print("LAUNCH TRIGGER")
            GPIO.pins[22] = 1
            time.sleep(0.15)
            GPIO.pins[22] = 0

        # clear request
        system_wants[key] = None

    def on_k1(e): apply_state_change("k1")
    def on_k2(e): apply_state_change("k2")
    def on_launch(e): apply_state_change("launch")

    keyboard.on_press_key("1", on_k1)
    keyboard.on_press_key("2", on_k2)
    keyboard.on_press_key("3", on_launch)


# ===============================================================
# PIN DEFINITIONS
# ===============================================================
KEY1 = 17
KEY2 = 27
LAUNCH = 22

LED_RED = 5
LED_GREEN = 6
LAUNCH_OUT = 13

BASE_WINDOW = 0.50
RETRY_EXTRA_MIN = 0.20
RETRY_EXTRA_MAX = 0.60


# ===============================================================
# LED DISPLAY IN SIM MODE
# ===============================================================
def show_leds(red, green):
    if SIM:
        r = "[RED]" if red else "[---]"
        g = "[GREEN]" if green else "[---]"
        print(f"\nLEDs: {r}  {g}")


# ===============================================================
# RESET HANDLING
# ===============================================================
def wait_for_reset(system_wants):
    print("Reset required: turn keys OFF + release launch")

    # request user action
    system_wants["k1"] = False
    system_wants["k2"] = False
    system_wants["launch"] = False

    # wait until states match
    while GPIO.input(KEY1) or GPIO.input(KEY2) or GPIO.input(LAUNCH):
        time.sleep(0.05)

    # clear outputs
    GPIO.output(LED_RED, 0)
    GPIO.output(LED_GREEN, 0)
    GPIO.output(LAUNCH_OUT, 0)
    show_leds(0, 0)
    print("System reset complete.\n")


# ===============================================================
# MAIN LAUNCH LOGIC
# ===============================================================
def run():
    try:
        GPIO.setmode(GPIO.BCM)

        GPIO.setup(KEY1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        GPIO.setup(KEY2, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        GPIO.setup(LAUNCH, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

        GPIO.setup(LED_RED, GPIO.OUT)
        GPIO.setup(LED_GREEN, GPIO.OUT)
        GPIO.setup(LAUNCH_OUT, GPIO.OUT)

        GPIO.output(LED_RED, 0)
        GPIO.output(LED_GREEN, 0)
        GPIO.output(LAUNCH_OUT, 0)
        show_leds(0, 0)

        while True:
            print("Turn both keys to ARM...")

            # request key turns
            system_wants["k1"] = True
            system_wants["k2"] = True

            # wait until both keys are ON
            t0 = time.time()
            while time.time() - t0 < BASE_WINDOW:
                if GPIO.input(KEY1) and GPIO.input(KEY2):
                    break
                time.sleep(0.01)
            else:
                print("Keys not turned in time. Retry...")
                system_wants["k1"] = False
                system_wants["k2"] = False
                wait_for_reset(system_wants)
                continue

            # ARMED
            print("ARMED.")
            GPIO.output(LED_RED, 1)
            show_leds(1, 0)

            if GPIO.input(LAUNCH):
                print("Launch pressed early. Resetting.")
                wait_for_reset(system_wants)
                continue

            # Request launch
            print("Press launch button within 30 seconds (3 key in sim).")
            system_wants["launch"] = True

            for i in range(30, 0, -1):
                print(f"{i} sec...", end="\r")
                if GPIO.input(LAUNCH):
                    print("\nLAUNCH CONFIRMED")
                    GPIO.output(LED_RED, 0)
                    GPIO.output(LED_GREEN, 1)
                    show_leds(0, 1)
                    time.sleep(1)
                    GPIO.output(LAUNCH_OUT, 1)
                    print("FIRING COMPLETE")
                    wait_for_reset(system_wants)
                    break
                time.sleep(1)
            else:
                print("\nLaunch window expired.")
                wait_for_reset(system_wants)

    finally:
        GPIO.cleanup()
