import time
import sys
import importlib
import subprocess
from babicli.colors import GREEN, RED, RESET

# ===============================================================
# 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


# ===============================================================
# SHARED STATE (exists in real + SIM mode)
# ===============================================================
system_wants = {"k1": None, "k2": None, "launch": None}


# ===============================================================
# 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()

    sim_state = {"k1": False, "k2": False}

    def apply_state_change(key):
        if system_wants[key] is None:
            return  # ignore spam

        if key == "k1":
            sim_state["k1"] = system_wants[key]
            GPIO.pins[17] = int(sim_state["k1"])
            print(f"KEY1 set to {GPIO.pins[17]}")

        elif key == "k2":
            sim_state["k2"] = system_wants[key]
            GPIO.pins[27] = int(sim_state["k2"])
            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

        system_wants[key] = None

    keyboard.on_press_key("1", lambda e: apply_state_change("k1"))
    keyboard.on_press_key("2", lambda e: apply_state_change("k2"))
    keyboard.on_press_key("3", lambda e: apply_state_change("launch"))


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

LED_RED = 5
LED_GREEN = 6
LAUNCH_OUT = 13

BASE_WINDOW = 0.50

BAR_LENGTH = 29
LAUNCH_WINDOW_SECONDS = 30


# ===============================================================
# 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}")


# ===============================================================
# RESET FOR RESTART (NOT EXIT)
# ===============================================================
def wait_reset_for_restart():
    print("Reset required: turn keys OFF + release launch")
    printed = False

    # Request SIM to allow turning things OFF
    system_wants["k1"] = False
    system_wants["k2"] = False
    system_wants["launch"] = False

    while GPIO.input(KEY1) or GPIO.input(KEY2) or GPIO.input(LAUNCH):
        if not printed:
            if GPIO.input(KEY1):
                print("Turn KEY1 off to continue")
            if GPIO.input(KEY2):
                print("Turn KEY2 off to continue")
            if GPIO.input(LAUNCH):
                print("Release LAUNCH button to continue")
            printed = True
        time.sleep(0.1)

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

    print("System reset complete.\n")


# ===============================================================
# WAIT FOR EXIT AFTER SUCCESSFUL LAUNCH
# ===============================================================
def wait_for_exit_after_launch():
    print("Waiting for all keys OFF to exit program.")
    printed = False

    # Request SIM to allow turning things OFF
    system_wants["k1"] = False
    system_wants["k2"] = False
    system_wants["launch"] = False

    while GPIO.input(KEY1) or GPIO.input(KEY2) or GPIO.input(LAUNCH):
        if not printed:
            if GPIO.input(KEY1):
                print("Turn KEY1 off to exit.")
            if GPIO.input(KEY2):
                print("Turn KEY2 off to exit.")
            if GPIO.input(LAUNCH):
                print("Release LAUNCH button to exit.")
            printed = True
        time.sleep(0.1)

    GPIO.cleanup()
    print("System exited after successful launch.")
    sys.exit(0)


# ===============================================================
# COUNTDOWN BAR (single-line update)
# ===============================================================
def countdown_bar(seconds_total):
    for remaining in range(seconds_total, 0, -1):
        if seconds_total <= 0:
            break

        filled = int((remaining / seconds_total) * BAR_LENGTH)
        bar = "█" * filled + "░" * (BAR_LENGTH - filled)

        # RED PROGRESS BAR
        colored_bar = f"{RED}{bar}{RESET}"

        sys.stdout.write(f"\r[{colored_bar}] {remaining}s")
        sys.stdout.flush()

        yield remaining
        time.sleep(1)

    print()  # newline after bar



# ===============================================================
# MAIN 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...")

            # Allow SIM key-on
            system_wants["k1"] = True
            system_wants["k2"] = True

            # --- IMPORTANT ANTI-SPAM WAIT ---
            # Don't start timing until user actually toggles something.
            while not GPIO.input(KEY1) and not GPIO.input(KEY2):
                time.sleep(0.05)

            # Now enforce the "both within window" rule.
            armed = False
            t0 = time.time()
            while time.time() - t0 < BASE_WINDOW:
                if GPIO.input(KEY1) and GPIO.input(KEY2):
                    armed = True
                    break
                time.sleep(0.01)

            if not armed:
                print("Keys not turned together in time.")
                wait_reset_for_restart()
                continue

            print(f"\n{RED}ARMED.{RESET}")
            GPIO.output(LED_RED, 1)
            show_leds(1, 0)

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

            print("\nPress launch button within 30 seconds.")
            system_wants["launch"] = True

            launched = False
            aborted = False

            for remaining in countdown_bar(LAUNCH_WINDOW_SECONDS):

                # Abort if any key turned off during launch window
                if not GPIO.input(KEY1) or not GPIO.input(KEY2):
                    print("\nABORT: A key was turned OFF during launch window.")
                    GPIO.output(LED_RED, 1)
                    GPIO.output(LED_GREEN, 0)
                    show_leds(1, 0)
                    aborted = True
                    break

                if GPIO.input(LAUNCH):
                    print(f"\n{GREEN}LAUNCH CONFIRMED{RESET}")
                    GPIO.output(LED_RED, 0)
                    GPIO.output(LED_GREEN, 1)
                    show_leds(0, 1)
                    time.sleep(1)

                    GPIO.output(LAUNCH_OUT, 1)
                    print("\nFIRING COMPLETE")
                    launched = True
                    break

            if launched:
                # Successful launch -> require physical off -> exit
                wait_for_exit_after_launch()
                return

            if aborted:
                # Abort -> reset -> restart
                wait_reset_for_restart()
                continue

            # Timeout -> reset -> restart
            print("\nLaunch window expired.")
            wait_reset_for_restart()

    finally:
        GPIO.cleanup()


if __name__ == "__main__":
    run()
