#!/usr/bin/env python3
import hashlib
import json
import os
from pathlib import Path
import re
import runpy
import sys
import tempfile
from urllib.parse import unquote, urlsplit
import xml.etree.ElementTree as ET


LIBRARY = Path("/mnt/droiddeck-sd")
ADDED = Path("/mnt/droiddeck-lib")
URI = "file:///mnt/droiddeck-sd"
KDE_OWNER = "http://www.kde.org"
MARK = "DroidDeckSteamLibrary"
ICON_NS = "http://www.freedesktop.org/standards/desktop-bookmarks"
ET.register_namespace("bookmark", ICON_NS)


def steam_libraries(home, data):
    steam = data / "Steam"
    libraries = {steam: "Internal Storage"}
    listing = os.environ.get("BL_STEAM_LIBRARIES")
    selected = None
    if listing:
        try:
            selected = {Path(e["path"]): str(e["label"]) for e in json.loads(Path(listing).read_text())}
        except (OSError, ValueError, KeyError, TypeError):
            pass
    vdf = steam / "steamapps/libraryfolders.vdf"
    if vdf.is_file():
        parser = runpy.run_path(str(Path(__file__).with_name("droiddeck-steam-ui-scale")))
        folders = parser["parse"](vdf.read_text()).get("libraryfolders")
        if not isinstance(folders, parser["Block"]):
            raise ValueError("missing libraryfolders")
        for _, entry in folders.pairs:
            if not isinstance(entry, parser["Block"]):
                continue
            path = entry.get("path")
            if not isinstance(path, str):
                continue
            path = Path(re.sub(r'\\(["\\])', r'\1', path))
            managed = path == LIBRARY or path.is_relative_to(home / "Games") or path.is_relative_to(ADDED)
            if selected is not None and managed and path not in selected:
                continue
            if path == LIBRARY and selected is None and not os.environ.get("BL_LIBRARY_LABEL"):
                continue
            label = entry.get("label")
            label = re.sub(r'\\(["\\])', r'\1', label) if isinstance(label, str) else ""
            libraries[path] = label or path.name
    if selected is not None:
        libraries.update(selected)
    elif os.environ.get("BL_LIBRARY_LABEL"):
        libraries[LIBRARY] = os.environ["BL_LIBRARY_LABEL"]
    libraries[steam] = "Internal Storage"
    return {path.as_uri(): "Steam (" + (" ".join(label.split()) or path.name) + ")"
            for path, label in libraries.items() if path.is_absolute() and (path / "steamapps").is_dir()}


def place_uri(uri):
    url = urlsplit(uri or "")
    if url.scheme == "file" and url.netloc in ("", "localhost") and url.path.startswith("/"):
        return Path(unquote(url.path)).as_uri()
    return uri


def update_places(path, libraries):
    if path.is_file():
        parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True, insert_pis=True))
        root = ET.fromstring(path.read_bytes(), parser=parser)
        if root.tag != "xbel":
            raise ValueError("invalid Dolphin places file")
    else:
        if not libraries:
            return
        root = ET.Element("xbel", {"version": "1.0"})
    before = ET.tostring(root)
    seen = set()
    personal = {place_uri(b.get("href")) for b in root.findall("bookmark")
                if b.find("info/metadata[@owner='" + KDE_OWNER + "']/" + MARK) is None}
    for bookmark in root.findall("bookmark"):
        metadata = bookmark.find("info/metadata[@owner='" + KDE_OWNER + "']")
        if metadata is None or metadata.find(MARK) is None:
            continue
        uri = place_uri(bookmark.get("href"))
        if uri not in libraries or uri in seen or uri in personal:
            root.remove(bookmark)
            continue
        seen.add(uri)
        title = bookmark.find("title")
        if title is None:
            title = ET.SubElement(bookmark, "title")
        title.text = libraries[uri]
    for uri, label in libraries.items():
        if uri in seen or uri in personal:
            continue
        bookmark = ET.SubElement(root, "bookmark", {"href": uri})
        ET.SubElement(bookmark, "title").text = label
        info = ET.SubElement(bookmark, "info")
        icon = ET.SubElement(info, "metadata", {"owner": "http://freedesktop.org"})
        ET.SubElement(icon, "{" + ICON_NS + "}icon", {"name": "steam"})
        metadata = ET.SubElement(info, "metadata", {"owner": KDE_OWNER})
        ET.SubElement(metadata, "ID").text = "droiddeck-steam-" + hashlib.sha256(uri.encode()).hexdigest()[:16]
        ET.SubElement(metadata, MARK).text = "true"
        ET.SubElement(metadata, "isSystemItem").text = "false"
    if ET.tostring(root) != before:
        atomic_write(path, ET.tostring(root, encoding="unicode", xml_declaration=True))


def bookmarks_file(home, config):
    current = config / "gtk-3.0/bookmarks"
    legacy = home / ".gtk-bookmarks"
    if current.is_file() and current.read_text(errors="surrogateescape").strip():
        return current
    return legacy if legacy.is_file() else current


def update_bookmark(path, label):
    original = path.read_text(errors="surrogateescape") if path.is_file() else ""
    lines = [line for line in original.splitlines(keepends=True)
             if line.strip().split(" ", 1)[0] != URI]
    content = "".join(lines)
    if label is not None:
        label = " ".join(label.split()) or "Secondary library"
        if content and not content.endswith("\n"):
            content += "\n"
        content += URI + " " + label + "\n"
    if content == original:
        return
    atomic_write(path, content)


def atomic_write(path, content):
    path.parent.mkdir(parents=True, exist_ok=True)
    temporary = None
    try:
        with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", errors="surrogateescape",
                                         dir=path.parent, delete=False) as stream:
            temporary = stream.name
            stream.write(content)
        os.replace(temporary, path)
    finally:
        if temporary and os.path.exists(temporary):
            os.unlink(temporary)


def main():
    home = Path.home()
    config = Path(os.environ.get("XDG_CONFIG_HOME") or home / ".config")
    label = os.environ.get("BL_LIBRARY_LABEL")
    available = bool(label) and (LIBRARY / "steamapps").is_dir()
    update_bookmark(bookmarks_file(home, config), label if available else None)
    data = Path(os.environ.get("XDG_DATA_HOME") or home / ".local/share")
    update_places(data / "user-places.xbel", steam_libraries(home, data))


if __name__ == "__main__":
    try:
        main()
    except (OSError, ValueError, ET.ParseError) as error:
        print("== desktop library bookmark: " + str(error), file=sys.stderr)
