#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-only
"""Melody plugin (ADR-0302, ADR-0303): listening together, in one panel.

The engine hosts a room or follows one; this asks it to, through melody-cli
-- MELODY_SERVER when the engine is elsewhere -- and is its panel: a room's
name and password to start or join one; in a room, Stop or Leave, the party,
the room's queue with a vote for each suggestion, what played and who
listens. Settings are the server and a name, nothing more.
"""
import json
import os
import subprocess
import sys


class Refused(Exception):
    pass


def cli(given, *words, password=None):
    engine = given.get("engine") or {}
    env = dict(os.environ)
    if engine.get("address"):
        env["MELODY_SERVER"] = engine["address"]
    if password:
        env["MELODY_TOGETHER_PASSWORD"] = password
    try:
        done = subprocess.run(["melody-cli", "--json", *words], env=env, stdin=subprocess.DEVNULL,
                              capture_output=True, text=True, timeout=25)
    except FileNotFoundError:
        raise Refused("melody-cli is not installed beside Melody")
    except subprocess.TimeoutExpired:
        raise Refused("the engine did not answer")
    if done.returncode != 0:
        said = done.stderr.strip().splitlines()
        raise Refused(said[-1].removeprefix("melody-cli: ") if said else "the engine refused")
    try:
        return json.loads(done.stdout.strip().splitlines()[-1]) if done.stdout.strip() else {}
    except ValueError:
        return {}


def fail(message):
    print(message, file=sys.stderr)
    sys.exit(1)


def settings_of(given):
    settings = given.get("settings") or {}
    server = str(settings.get("server") or "").strip()
    name = str(settings.get("name") or "").strip() or os.environ.get("USER", "a friend")
    return server, name, str(settings.get("server_password") or "").strip()


def act(given, action):
    """What was pressed, done; what to say of it, if anything."""
    server, name, server_password = settings_of(given)
    inputs = given.get("inputs") or {}
    what, value = action.get("id", ""), action.get("value", "")
    room = str(inputs.get("room") or "").strip().lower()
    password = str(inputs.get("password") or "").strip()
    if what in ("start", "join"):
        if not server:
            return "Set your server first: Settings › Plugins › Listen together."
        if what == "start":
            cli(given, "together", "host", server, room or "-", password or "-", name,
                password=server_password)
        else:
            if not room or not password:
                return "To join, the room's name and its password."
            cli(given, "together", "join", server, room, password, name)
    elif what in ("stop", "leave"):
        cli(given, "together", "leave")
    elif what == "party":
        cli(given, "together", "party", "on" if value == "on" else "off")
    elif what in ("vote", "remove"):
        cli(given, "together", what, value)
    return ""


def track_row(shown, detail="", buttons=None, playing=False, artist=None, title=None):
    # ADR-0304: a track's row, its title over its artist. The engine tells a
    # track as "Artist — Title"; a suggestion tells both.
    if title is None:
        artist, _, title = shown.partition(" — ")
        if not title:
            artist, title = "", shown
    row = {"track": title or "Unknown track"}
    if artist:
        row["artist"] = artist
    if detail:
        row["detail"] = detail
    if playing:
        row["playing"] = True
    if buttons:
        row["buttons"] = buttons
    return row


def panel(given):
    said = ""
    if given.get("action"):
        try:
            said = act(given, given["action"])
        except Refused as refused:
            said = str(refused)
    try:
        room = cli(given, "together", "status")
    except Refused as refused:
        print(json.dumps({"title": "Listening room", "parts": [{"paragraph": str(refused)}]}))
        return
    role = room.get("role") or ""
    server, _, _ = settings_of(given)
    parts = []
    if said or room.get("message"):
        parts.append({"paragraph": said or room["message"]})

    if not role:
        # Out of a room: start one, or join a friend's.
        if not server:
            parts.append({"paragraph": "Set your server under Settings › Plugins › Listen "
                                       "together, and your name."})
        parts += [
            {"input": "room", "label": "Room", "placeholder": "friday-doom", "action": "join"},
            {"input": "password", "label": "Password", "secret": True, "action": "join"},
            {"button": "Start room", "action": "start", "role": "primary"},
            {"button": "Join room", "action": "join"},
            {"paragraph": "Start a room of your own — its name and password yours to choose, "
                          "or made for you — or join a friend's by its name and password."},
        ]
        print(json.dumps({"title": "Start or join a room", "parts": parts}))
        return

    hosting = role == "host"
    listeners = room.get("listeners") or []
    # One line: how one is in the room, its page, and the way out.
    buttons = []
    if room.get("invite"):
        buttons.append({"text": "Web page", "url": room["invite"], "role": "quiet"})
    buttons.append({"text": "Stop" if hosting else "Leave",
                    "action": "stop" if hosting else "leave", "role": "quiet"})
    # The last reaction from the web page, while it is fresh.
    detail = " · ".join(p for p in ("%d listening" % len(listeners), room.get("reaction")) if p)
    parts.append({"status": "Hosting" if hosting else "Listening along",
                  "detail": detail, "live": True, "buttons": buttons})
    if hosting:
        parts.append({"toggle": "party", "label": "Party",
                      "detail": "Friends suggest tracks and vote on them",
                      "on": bool(room.get("party"))})

    # The room's queue, as one list: what plays, what is next, then -- a
    # party -- the suggestions by their votes, or the host's own tracks.
    parts.append({"heading": "Queue"})
    if room.get("track"):
        by = room.get("by")
        parts.append(track_row(room["track"], "suggested by %s" % by if by else "", playing=True))
    else:
        parts.append({"paragraph": "Nothing plays yet."})
    if room.get("party"):
        if room.get("next"):
            parts.append(track_row(room["next"], "next"))
        for each in room.get("suggestions") or []:
            votes = each.get("votes", 0)
            buttons = [{"text": "▲ %d" % votes, "action": "vote", "value": str(each.get("id", 0)),
                        "on": bool(each.get("voted"))}]
            if hosting:
                buttons.append({"text": "✕", "action": "remove", "value": str(each.get("id", 0)),
                                "role": "quiet"})
            parts.append(track_row(
                "", " · ".join(p for p in (each.get("name", ""),
                                           "" if each.get("ready") else "on its way") if p),
                buttons, artist=each.get("artist") or "", title=each.get("title") or ""))
        if not room.get("suggestions"):
            parts.append({"paragraph": "Suggest a track: right-click it, Suggest to the "
                                       "listening room."})
    else:
        for track in room.get("upcoming") or []:
            parts.append(track_row(track))
    history = (room.get("history") or [])[:8]
    if history:
        parts.append({"heading": "Played"})
        parts += [track_row(track) for track in history]
    parts.append({"heading": "Listening"})
    if listeners:
        parts.append({"people": listeners})
    else:
        parts.append({"paragraph": "Nobody yet."})
    print(json.dumps({"title": room.get("room") or "Listening room", "parts": parts}))


def suggest(given):
    paths = [track.get("path") for album in given.get("albums") or []
             for track in album.get("tracks") or [] if track.get("path")]
    if not paths:
        fail("no tracks chosen")
    try:
        cli(given, "together", "suggest", *paths)
    except Refused as refused:
        fail(str(refused))
    print("Suggested to the room: %d track%s" % (len(paths), "" if len(paths) == 1 else "s"))


def main():
    if len(sys.argv) != 2 or sys.argv[1] not in ("panel", "suggest"):
        fail("usage: together panel|suggest")
    given = json.load(sys.stdin)
    if sys.argv[1] == "panel":
        panel(given)
    else:
        suggest(given)


if __name__ == "__main__":
    main()
