#!/usr/bin/env python3
import os
import time
import logging
import signal
import sys
from evdev import InputDevice, UInput, ecodes

LOG_LEVEL = os.environ.get("SWIPE_LOG_LEVEL", "INFO").upper()

logging.basicConfig(
    level=getattr(logging, LOG_LEVEL, logging.INFO),
    format="%(asctime)s [%(levelname)s] touchswipe: %(message)s"
)

log = logging.getLogger("touchswipe")

DEFAULT_DEVICE = "/dev/input/event8"

DEFAULT_ACTIONS = {
    "SWIPE_UP": "HOME",
    "SWIPE_DOWN": "F11",
    "SWIPE_LEFT": "NEXT_APP",
    "SWIPE_RIGHT": "NEXT_APP_2X",
}

DEFAULT_TUNING = {
    "SWIPE_MIN_DISTANCE": 40,
    "SWIPE_MAX_DURATION": 0.6,
    "SWIPE_DOMINANCE_RATIO": 1.5,
}

def load_defaults_file(path):
    values = {}
    try:
        with open(path, "r") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                k, v = line.split("=", 1)
                values[k.strip()] = v.strip()
    except FileNotFoundError:
        pass
    return values

CONFIG = load_defaults_file("/etc/default/touchswipe")

DEVICE = CONFIG.get("TOUCH_DEVICE", DEFAULT_DEVICE)

ACTIONS = {
    k: CONFIG.get(k, v)
    for k, v in DEFAULT_ACTIONS.items()
}

SWIPE_MIN_DISTANCE = int(CONFIG.get(
    "SWIPE_MIN_DISTANCE",
    DEFAULT_TUNING["SWIPE_MIN_DISTANCE"]
))
SWIPE_MAX_DURATION = float(CONFIG.get(
    "SWIPE_MAX_DURATION",
    DEFAULT_TUNING["SWIPE_MAX_DURATION"]
))
SWIPE_DOMINANCE_RATIO = float(CONFIG.get(
    "SWIPE_DOMINANCE_RATIO",
    DEFAULT_TUNING["SWIPE_DOMINANCE_RATIO"]
))

log.info("Device: %s", DEVICE)
log.info("Actions: %s", ACTIONS)
log.info(
    "Tuning: min=%d maxdur=%.2f ratio=%.2f",
    SWIPE_MIN_DISTANCE,
    SWIPE_MAX_DURATION,
    SWIPE_DOMINANCE_RATIO
)

KEYS = [
    ecodes.KEY_LEFTMETA,
    ecodes.KEY_LEFTALT,
    ecodes.KEY_TAB,
    ecodes.KEY_F11,
]

ui = UInput({ecodes.EV_KEY: KEYS}, name="touchswipe-virtual-keyboard")
log.info("uinput virtual keyboard created")

def send_keys(*keys):
    log.debug("Sending keys: %s", keys)
    for k in keys:
        ui.write(ecodes.EV_KEY, k, 1)
    ui.syn()
    for k in reversed(keys):
        ui.write(ecodes.EV_KEY, k, 0)
    ui.syn()

def do_action(name):
    log.info("Action: %s", name)

    if name == "HOME":
        send_keys(ecodes.KEY_LEFTMETA)

    elif name == "F11":
        send_keys(ecodes.KEY_F11)

    elif name == "NEXT_APP":
        send_keys(ecodes.KEY_LEFTALT, ecodes.KEY_TAB)

    elif name == "NEXT_APP_2X":
        send_keys(ecodes.KEY_LEFTALT, ecodes.KEY_TAB)
        time.sleep(0.05)
        send_keys(ecodes.KEY_LEFTALT, ecodes.KEY_TAB)

    else:
        log.warning("Unknown action: %s", name)

try:
    dev = InputDevice(DEVICE)
    log.info("Opened input device: %s (%s)", dev.path, dev.name)
except Exception:
    log.exception("Failed to open input device")
    sys.exit(1)

tracking = False
start_x = start_y = last_x = last_y = None
start_time = None

def reset_gesture():
    global tracking, start_x, start_y, last_x, last_y, start_time
    tracking = False
    start_x = start_y = last_x = last_y = None
    start_time = None

def handle_swipe(dx, dy, duration):
    if duration > SWIPE_MAX_DURATION:
        log.debug("Swipe ignored: too slow")
        return

    abs_dx = abs(dx)
    abs_dy = abs(dy)

    if abs_dx < SWIPE_MIN_DISTANCE and abs_dy < SWIPE_MIN_DISTANCE:
        log.debug("Swipe ignored: too short")
        return

    if abs_dx > abs_dy * SWIPE_DOMINANCE_RATIO:
        do_action(ACTIONS["SWIPE_RIGHT"] if dx > 0 else ACTIONS["SWIPE_LEFT"])

    elif abs_dy > abs_dx * SWIPE_DOMINANCE_RATIO:
        do_action(ACTIONS["SWIPE_DOWN"] if dy > 0 else ACTIONS["SWIPE_UP"])

def shutdown(signum=None, frame=None):
    log.info("Shutting down")
    try:
        dev.close()
        ui.close()
    except Exception:
        pass
    sys.exit(0)

signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)

log.info("Entering event loop")

for event in dev.read_loop():
    if event.type != ecodes.EV_ABS:
        continue

    if event.code == ecodes.ABS_MT_TRACKING_ID:
        if event.value != -1:
            tracking = True
            start_time = time.monotonic()
            start_x = start_y = None
        else:
            if tracking and None not in (start_x, start_y, last_x, last_y):
                duration = time.monotonic() - start_time
                handle_swipe(last_x - start_x, last_y - start_y, duration)
            reset_gesture()

    elif event.code == ecodes.ABS_MT_POSITION_X:
        last_x = event.value
        if start_x is None:
            start_x = last_x

    elif event.code == ecodes.ABS_MT_POSITION_Y:
        last_y = event.value
        if start_y is None:
            start_y = last_y
