#!/usr/bin/env python3
import time
import sys
import os
import signal
import importlib
import subprocess
from datetime import datetime
from babicli.colors import GREEN, RED, RESET

# ===============================================================
# CONFIG
# ===============================================================
ADMIN_CODE = "28021986"
LAUNCH_CODE_LENGTH = 6
LOG_DIR = "launchlogs"

if not os.path.exists(LOG_DIR):
    os.makedirs(LOG_DIR)

log_file = None


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


# ===============================================================
# CTRL-C DISABLED
# ===============================================================
def block_ctrl_c(signum, frame):
    print("\nCTRL-C is disabled. Close the window (X) to exit safely.")
signal.signal(signal.SIGINT, block_ctrl_c)


# ===============================================================
# SIM / GPIO 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

system_wants = {"k1": None, "k2": None, "launch": None}

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

ARM_WINDOW = 0.50
BAR_LENGTH = 29
LAUNCH_WINDOW_SECONDS = 30


# ===============================================================
# LOGGING HELPERS
# ===============================================================
def log(msg):
    global log_file
    timestamp = datetime.now().strftime("%H:%M:%S")
    line = f"[{timestamp}] {msg}\n"
    print(line, end="")
    if log_file:
        log_file.write(line)
        log_file.flush()


# ===============================================================
# AUTHORIZATION
# ===============================================================
import random

def require_authorization():
    global log_file

    one_time_code = "".join(random.choice("0123456789") for _ in range(LAUNCH_CODE_LENGTH))

    print(f"ENTER LAUNCH CODE TO BEGIN: {one_time_code}")
    entered = input("Code: ").strip()

    if entered == ADMIN_CODE:
        log_name = f"ADMIN_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
        log_path = os.path.join(LOG_DIR, log_name)
        log_file = open(log_path, "w")
        log("ADMIN OVERRIDE USED")
        print(f"{GREEN}Admin override accepted.{RESET}")
    elif entered == one_time_code:
        log_name = f"{one_time_code}.txt"
        log_path = os.path.join(LOG_DIR, log_name)
        log_file = open(log_path, "w")
        log(f"Launch code {one_time_code} accepted.")
    else:
        print(f"{RED}INVALID CODE. EXITING.{RESET}")
        sys.exit(1)

    # Operator info
    name = input("Enter operator name: ").strip()
    ID = input("Enter operator ID number: ").strip()

    log(f"Operator: {name}")
    log(f"Operator ID: {ID}")

    input("\nPress ENTER to continue...")

    print("Press the LAUNCH button once to confirm starting the launch sequence.")
    log("Waiting for pre-launch button confirmation.")

    while not GPIO.input(LAUNCH):
        time.sleep(0.05)
    while GPIO.input(LAUNCH):
        time.sleep(0.05)

    print(f"{GREEN}Launch-button confirmation received.{RESET}")
    log("Launch-button confirmation received.")


# ===============================================================
# PROGRESS BAR
# ===============================================================
def countdown_bar(total):
    for remaining in range(total, 0, -1):
        filled = int((remaining / total) * BAR_LENGTH)
        bar = RED + ("█" * filled + "░" * (BAR_LENGTH - filled)) + RESET
        sys.stdout.write(f"\r[{bar}] {remaining}s")
        sys.stdout.flush()
        yield remaining
        time.sleep(1)
    print()


# ===============================================================
# MAIN LOGIC
# ===============================================================
def run():
    require_authorization()

    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)

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

        system_wants["k1"] = True
        system_wants["k2"] = True

        while not GPIO.input(KEY1) and not GPIO.input(KEY2):
            time.sleep(0.05)

        armed = False
        t0 = time.time()
        while time.time() - t0 < ARM_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.")
            log("ARM FAILED: Keys not turned together.")
            return

        print(f"{RED}ARMED.{RESET}")
        log("System ARMED.")
        GPIO.output(LED_RED, 1)

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

        for remaining in countdown_bar(LAUNCH_WINDOW_SECONDS):
            if not GPIO.input(KEY1) or not GPIO.input(KEY2):
                print("ABORT: A key was turned OFF.")
                log("ABORT: Key turned off during launch window.")
                return

            if GPIO.input(LAUNCH):
                print(f"{GREEN}LAUNCH CONFIRMED{RESET}")
                log("Launch button pressed within window.")

                GPIO.output(LED_GREEN, 1)
                GPIO.output(LAUNCH_OUT, 1)

                print("FIRING COMPLETE")
                log("Launch COMPLETED successfully.")
                return

        print("Launch window expired.")
        log("Launch FAILED: Window expired.")
        return


# ===============================================================
# RUN + SAFE EXIT
# ===============================================================
try:
    run()
finally:
    if log_file:
        log_file.write("\n--- END OF LOG ---\n")
        log_file.close()
