#!/usr/bin/env python3

"""Open this computer's own manual.

    classroom-manual              open it
    classroom-manual --features   say what this device would show

One manual is installed on every machine, carrying every page. What makes
it THIS room's manual is that the pages for features this device does not
have are removed before a teacher ever sees them: a Bible study laptop
opens it and reads about the Browser, Presentations and the volume, with
no mention of Wonder Ink or Parent Call to wonder about.

WHAT REACHES THE CLASSROOM ACCOUNT, AND WHAT DOES NOT.

Only a list of setting NAMES — ["WONDERINK_ENABLED", ...] — and only
those the package already defines as optional applications. No values
from device.conf, no broker address, no broker password, no update URL,
no Wi-Fi anything, no other key an administrator has put in that file.

That list is prepared by classroom-desktop-layout, which is already the
one place that decides what this device offers, and left in the runtime
directory where it is readable and carries nothing worth protecting. If
it is not there, every page is shown — which is exactly the printed
manual, and is never wrong, only less specific.

Shown through Chrome in application mode: no address bar, no tabs, one
window that closes. Chrome is on every classroom machine already, the
manual is a local file, and none of it needs the network.
"""

import argparse
import html
import json
import os
import subprocess
import sys
import tempfile

sys.path.insert(0, "/usr/local/lib/ekklesia-classroom")
sys.path.insert(
    0,
    os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib"),
)

from classroom_config import FEATURES, feature, read_device_config  # noqa: E402

MANUAL = "/usr/local/share/ekklesia-classroom/manual/classroom-manual.html"

# Written by classroom-desktop-layout at boot. See the module docstring
# for what is and is not in it.
MANIFEST = "/run/ekklesia-classroom/features.json"


def say(message):
    print(f"classroom-manual: {message}")


def enabled_features():
    """The optional applications this device offers, by setting name."""
    try:
        with open(MANIFEST, "r", encoding="utf-8") as handle:
            listed = json.load(handle)

        if isinstance(listed, list):
            # Filtered against what this package knows, so a stale or
            # hand-edited manifest can only ever name a real feature.
            return [name for name in listed if name in FEATURES]
    except (OSError, ValueError):
        pass

    # No manifest. Fall back to asking device.conf directly for the
    # feature keys and nothing else — the same question, one step less
    # removed. A root-run --features does this; a classroom session
    # normally has the manifest.
    config = read_device_config()

    return [name for name in sorted(FEATURES) if feature(config, name)]


def page(features):
    """The installed manual, with this device's features declared in it."""
    with open(MANUAL, "r", encoding="utf-8") as handle:
        text = handle.read()

    declaration = (
        "<script>window.CLASSROOM_FEATURES = "
        + json.dumps(sorted(features))
        + ";</script>"
    )

    # Before the viewer script, which reads it. Nothing else in the file
    # is touched, so what a teacher reads is the file the package
    # installed.
    return text.replace("<body>", "<body>" + declaration, 1)


def main():
    parser = argparse.ArgumentParser(
        description="Open this computer's manual.")
    parser.add_argument("--features", action="store_true",
                        help="say what this device would show; opens nothing")
    arguments = parser.parse_args()

    features = enabled_features()

    if arguments.features:
        for name in sorted(FEATURES):
            print(f"  {name:<24} {'shown' if name in features else 'hidden'}")

        return 0

    if not os.path.exists(MANUAL):
        say(f"the manual is not installed at {MANUAL}")
        return 1

    # Composed into the teacher's own cache rather than beside the
    # installed file, which is root's and read-only.
    cache = os.path.join(
        os.environ.get("XDG_CACHE_HOME")
        or os.path.expanduser("~/.cache"),
        "ekklesia-classroom")
    os.makedirs(cache, exist_ok=True)

    try:
        composed = os.path.join(cache, "manual.html")

        with tempfile.NamedTemporaryFile(
                "w", encoding="utf-8", dir=cache, delete=False,
                suffix=".html") as handle:
            handle.write(page(features))
            temporary = handle.name

        os.replace(temporary, composed)
    except OSError as error:
        say(f"could not prepare the manual: {error}")
        return 1

    scale = "1"

    try:
        scale = subprocess.run(
            ["/usr/local/bin/classroom-ui-scale"], capture_output=True,
            text=True, timeout=10, check=False).stdout.strip() or "1"
    except (OSError, subprocess.SubprocessError):
        pass

    try:
        # Application mode: the manual in a window of its own, with no
        # address bar for a teacher to end up somewhere else in. The
        # room's ordinary browser profile is deliberately not used, so
        # opening Help cannot disturb a lesson that is already open in
        # Chrome.
        os.execvp("google-chrome", [
            "google-chrome",
            "--ozone-platform=wayland",
            f"--force-device-scale-factor={scale}",
            f"--user-data-dir={os.path.join(cache, 'manual-profile')}",
            "--no-first-run",
            "--no-default-browser-check",
            "--password-store=basic",
            "--hide-crash-restore-bubble",
            f"--app=file://{composed}",
        ])
    except OSError as error:
        say(f"could not open the browser: {error}")
        say(f"the manual itself is at {composed}")
        return 1


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