import time
import sys
import importlib
import subprocess
import random
import threading

# -----------------------------------------------------------------
# Utility: auto-install Python package
# -----------------------------------------------------------------
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


# -----------------------------------------------------------------
# Check if SIMULATION mode is requested
# -----------------------------------------------------------------
SIM_REQUESTED = (len(sys.argv) > 2 and sys.argv[2].lower() == "sim")


# -----------------------------------------------------------------
# GPIO setup OR simulate if unavailable
# -----------------------------------------------------------------
SIM = SIM_REQUESTED

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


# -----------------------------------------------------------------
# Keyboard simulation mode
# -----------------------------------------------------------------
if SIM:
    print("SIMULATION MODE ACTIVE (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()

    # Keyboard toggle state
    sim_key1 = False
    sim_key2 = False

    def kb_listener():
        nonlocal sim_key1, sim_key2

    # We use a thread because keyboard events are async
    def kb_thread():
        global sim_key1, sim_key2
        while True:
            if keyboard.is_pressed("1"):
                sim_key1 = not sim_key1
                GPIO.pins[17] = 1 if sim_key1 else 0
                print(f"\nKEY1 = {GPIO.pins[17]}")
                time.sleep(0.3)

            if keyboard.is_pressed("2"):
                sim_key2 = not sim_key2
                GPIO.pins[27] = 1 if sim_key2 else 0
                print(f"\nKEY2 = {GPIO.pins[27]}")
                time.sleep(0.3)

            if keyboard.is_pressed("3"):
                GPIO.pins[22] = 1
                print("\nLAUNCH BUTTON = PRESSED")
                time.sleep(0.3)
                GPIO.pins[22] = 0

    threading.Thread(target=kb_thread, daemon=True).start()


# -----------------------------------------------------------------
# PINS
# -----------------------------------------------------------------
KEY1 = 17
KEY2 = 27
LAUNCH = 22
LED_RED = 5      # ARMED indicator
LED_GREEN = 6    # FIRING indicator
LAUNCH_OUT = 13

BASE_WINDOW = 0.50
RETRY_EXTRA_MIN = 0.20
RETRY_EXTRA_MAX = 0.60


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


# -----------------------------------------------------------------
# Helper
# -----------------------------------------------------------------
def wait_for_reset():
    print("Reset required: both keys OFF and launch OFF...")
    while GPIO.input(KEY1) == 1 or GPIO.input(KEY2) == 1 or GPIO.input(LAUNCH) == 1:
        time.sleep(0.05)

    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 LOGIC
# -----------------------------------------------------------------
def run():
    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 the system... (Press 1 and 2 in SIM mode)")
        arm_window = BASE_WINDOW

        # ------------------------------
        # WAIT FOR FIRST KEY
        # ------------------------------
        while True:
            if GPIO.input(KEY1) or GPIO.input(KEY2):
                break
            if GPIO.input(LAUNCH):
                print("Launch button pressed during arming. RESET.")
                wait_for_reset()
            time.sleep(0.01)

        t0 = time.time()

        # ------------------------------
        # WAIT FOR SECOND KEY
        # ------------------------------
        def both_keys_on():
            return GPIO.input(KEY1) == 1 and GPIO.input(KEY2) == 1

        def any_key_off():
            return GPIO.input(KEY1) == 0 or GPIO.input(KEY2) == 0

        ok = False
        while time.time() - t0 < arm_window:
            if both_keys_on():
                ok = True
                break
            if any_key_off():
                print("\nKey dropped OFF during arming. RESET.")
                wait_for_reset()
                break
            time.sleep(0.01)

        if not ok:
            print("Keys not matched in time. RETRY in 15 seconds...")
            for i in range(15, 0, -1):
                print(f"{i} sec left...", end="\r")
                time.sleep(1)

            added = random.uniform(RETRY_EXTRA_MIN, RETRY_EXTRA_MAX)
            arm_window += added

            print(f"\nNew window: {arm_window:.3f}s")
            t1 = time.time()
            matched = False

            while time.time() - t1 < arm_window:
                if both_keys_on():
                    matched = True
                    break
                if any_key_off():
                    print("\nKey fell off again. RESET.")
                    wait_for_reset()
                    matched = False
                    break
                time.sleep(0.01)

            if not matched:
                print("Failed again. RESET.")
                wait_for_reset()
                continue

        # ------------------------------
        # SUCCESS — ARMED
        # ------------------------------
        print("\nARMED — red ON. 30 seconds to press LAUNCH.")
        GPIO.output(LED_RED, 1)
        GPIO.output(LED_GREEN, 0)
        show_leds(1, 0)

        if GPIO.input(LAUNCH):
            print("Launch button pressed early. RESET.")
            wait_for_reset()
            continue

        # ------------------------------
        # 30 SECOND LAUNCH WINDOW
        # ------------------------------
        for i in range(30, 0, -1):
            print(f"Launch window: {i} sec...", end="\r")

            if GPIO.input(LAUNCH):
                print("\n*** LAUNCH INITIATED ***")
                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. Waiting for reset.")
                wait_for_reset()
                return

            time.sleep(1)

        print("\nLaunch window expired — system disarmed.")
        GPIO.output(LED_RED, 0)
        GPIO.output(LED_GREEN, 0)
        show_leds(0, 0)
        wait_for_reset()
