#!/usr/bin/python3
"""
Quickpad launcher, installed by the .deb as /usr/bin/quickpad.

Quickpad updates itself by overwriting its own script file, and a normal user
can't write to /usr. So the package only ships a pristine copy in
/usr/share/quickpad/, and this launcher runs a per-user copy instead:

  - an older ./install-mint.sh install at ~/.local/bin/quickpad is reused (it
    keeps its self-updates and its menu entry is left alone);
  - otherwise the copy lives at ~/.local/share/quickpad/quickpad.py.

The packaged file is copied over the user's copy only when that copy is missing
or OLDER, so a newer self-updated copy is never downgraded by the package.

Set QUICKPAD_LAUNCHER_DRY=1 to print the decision and exit instead of starting
Quickpad (used by the tests and the Docker check).
"""

import os
import re
import sys
import tempfile

PACKAGED = "/usr/share/quickpad/quickpad.py"
VERSION_RE = re.compile(rb'^VERSION = "([^"]+)"', re.M)


def read_version(path):
    """The VERSION string from a Quickpad script, or None if it's missing or unreadable."""
    try:
        with open(path, "rb") as f:
            m = VERSION_RE.search(f.read())
    except OSError:
        return None
    return m.group(1).decode("ascii", "replace") if m else None


def version_tuple(v):
    """'3.10' -> (3, 10). Non-numeric parts count as 0, so odd tags never crash the launcher."""
    parts = []
    for p in (v or "").split("."):
        try:
            parts.append(int(p))
        except ValueError:
            parts.append(0)
    return tuple(parts)


def user_copy_path(home=None):
    home = home or os.path.expanduser("~")
    legacy = os.path.join(home, ".local", "bin", "quickpad")
    if os.path.exists(legacy):
        return legacy
    return os.path.join(home, ".local", "share", "quickpad", "quickpad.py")


def needs_copy(packaged_ver, user_ver):
    """Copy when the user has no copy yet, or theirs is older than the packaged one."""
    if user_ver is None:
        return True
    if packaged_ver is None:
        return False
    return version_tuple(user_ver) < version_tuple(packaged_ver)


def install_copy(src, dest):
    """Write to a temp file beside dest then swap it in, so a half-written copy never runs."""
    folder = os.path.dirname(dest)
    os.makedirs(folder, exist_ok=True)
    with open(src, "rb") as f:
        data = f.read()
    fd, tmp = tempfile.mkstemp(prefix=".quickpad-", dir=folder)
    try:
        with os.fdopen(fd, "wb") as f:
            f.write(data)
        os.chmod(tmp, 0o755)
        os.replace(tmp, dest)
    except BaseException:
        try:
            os.remove(tmp)
        except OSError:
            pass
        raise


def prepare(packaged=PACKAGED, home=None):
    """Make sure the user's copy is current. Returns (path_to_run, copied)."""
    dest = user_copy_path(home)
    packaged_ver = read_version(packaged)
    user_ver = read_version(dest) if os.path.exists(dest) else None
    if os.path.exists(packaged) and needs_copy(packaged_ver, user_ver):
        install_copy(packaged, dest)
        return dest, True
    return dest, False


def main():
    try:
        path, copied = prepare()
    except OSError as e:
        # Can't write the user copy (full disk, odd permissions): still start the packaged one so Rod isn't
        # left with nothing. Self-update won't work from there, but typing will.
        print(f"quickpad: couldn't set up your copy ({e}); starting the installed one.", file=sys.stderr)
        path, copied = PACKAGED, False
    if os.environ.get("QUICKPAD_LAUNCHER_DRY") == "1":
        print(f"path={path}")
        print(f"copied={'yes' if copied else 'no'}")
        return 0
    os.execv(sys.executable, [sys.executable, path, *sys.argv[1:]])


if __name__ == "__main__":
    sys.exit(main())
