#!/usr/bin/env python3
"""Recolour the SVG sources to the Darkmatter palette.

Two jobs, because the two SVG sets are quite different:

xfwm4/assets/*.svg are clean and in sync with their PNGs. Upstream draws the
window buttons as macOS-style traffic lights -- a coloured disc on a dark rim,
with the glyph appearing only on hover. Darkmatter keeps that geometry but
drops the hues: the disc is grey at rest and takes the accent on hover. Which
colour holds the disc depends on the button's state, so the map is per file.
The same source colour also serves two roles at different opacities -- a wash
disc at 0.1 and a dot glyph at 0.75 -- so fills are matched together with the
fill-opacity beside them.

assets/*.svg are only build sources; GTK reads the PNGs. Several of them still
carry elementary's blues, never re-rendered when Nordic forked, so they are
already out of step with their own PNGs. Those get exact-match substitution
only: a colour the palette does not name is left exactly as it was rather than
guessed at.
"""

import glob
import os
import re
import sys

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "lib"))

import darkmatter as dm

# --- xfwm4 -----------------------------------------------------------------

FRAME = {
    "#353c4a": dm.DARK,      # titlebar and frame background
    "#232831": dm.SHADOW,    # rim under the disc
    "#231d2b": dm.BG,        # glyph drawn on the disc
}

# The disc colour by button state. Upstream spends a different hue on each
# button; Darkmatter spends brightness instead.
DISC = {
    "active":   dm.DIM,
    "inactive": dm.BORDER_HI,
    "prelight": dm.SUBTLE,
    "pressed":  dm.FG,
}
# ...except close, which is the one button worth the accent.
DISC_CLOSE = {
    "active":   dm.DIM,
    "inactive": dm.BORDER_HI,
    "prelight": dm.ORANGE,
    "pressed":  dm.ORANGE_D1,
}

# Source colours that carry the disc, whatever their hue upstream.
DISC_SRC = {"#bf616a", "#ebcb8b", "#a3be8c", "#8fbcbb", "#3b4252"}

# #eceff1 is a wash at low opacity and a dot glyph at high opacity.
WASH_MAX_OPACITY = 0.5


def xfwm_map(path):
    name = os.path.basename(path)[:-4]
    state = name.rsplit("-", 1)[-1]
    if state == "shaded":                       # e.g. title-1-active-shaded
        state = name.split("-")[-2]
    state = state if state in DISC else "active"
    disc = (DISC_CLOSE if name.startswith("close") else DISC)[state]

    def sub(m):
        style = m.group(1)
        fill = re.search(r"(?<![-\w])fill:(#[0-9a-fA-F]{6})", style)
        if not fill:
            return m.group(0)
        color = fill.group(1).lower()
        opac = re.search(r"fill-opacity:([0-9.]+)", style)
        opac = float(opac.group(1)) if opac else 1.0

        if color in FRAME:
            new_color = FRAME[color]
        elif color == "#eceff1":
            new_color = dm.FG if opac <= WASH_MAX_OPACITY else disc
        elif color in DISC_SRC:
            new_color = disc
        else:
            return m.group(0)                   # #ffffff highlights, etc.
        s, e = fill.span(1)
        return 'style="%s"' % (style[:s] + new_color + style[e:])

    text = open(path).read()
    out = re.sub(r'style="([^"]*)"', sub, text)
    if out != text:
        open(path, "w").write(out)
        return True
    return False


# --- gtk assets ------------------------------------------------------------

EXACT = {
    "#353c4a": dm.DARK,
    "#232831": dm.BG,
    "#3b4252": dm.BASE,
    "#5d6a83": "#4a494b",
    "#8fbcbb": dm.ORANGE,
    "#ebcb8b": dm.CREAM,
    "#bf616a": dm.RED,
    "#a3be8c": dm.TEAL,
}
CONTROL = {
    "close":              dm.DIM,
    "close_prelight":     dm.ORANGE,
    "close_pressed":      dm.ORANGE_D1,
    "close_unfocused":    dm.BORDER_HI,
    "maximize":           dm.DIM,
    "maximize_prelight":  dm.SUBTLE,
    "maximize_pressed":   dm.FG,
    "maximize_unfocused": dm.BORDER_HI,
    "min":                dm.DIM,
    "min_prelight":       dm.SUBTLE,
    "min_pressed":        dm.FG,
    "min_unfocused":      dm.BORDER_HI,
}
CONTROL_DISC = {
    "close": "#bf616a", "close_prelight": "#bf616a", "close_pressed": "#d52735",
    "close_unfocused": "#3b4252",
    "maximize": "#a3be8c", "maximize_prelight": "#a3be8c",
    "maximize_pressed": "#13c11e", "maximize_unfocused": "#d4d4d4",
    "min": "#ebcb8b", "min_prelight": "#ebcb8b",
    "min_pressed": "#fac536", "min_unfocused": "#d4d4d4",
}
CONTROL_RIM = {
    "#232831": dm.SHADOW, "#9f1d2b": dm.SHADOW, "#975914": dm.SHADOW,
    "#0b7407": dm.SHADOW, "#b4b4b4": dm.SHADOW,
}


def asset_map(path):
    stem = os.path.basename(path)[:-4].replace("@2", "")
    table = dict(EXACT)
    if stem in CONTROL:
        table.update(CONTROL_RIM)
        table[CONTROL_DISC[stem]] = CONTROL[stem]
    text = open(path).read()
    out = re.sub(
        r"#[0-9a-fA-F]{6}",
        lambda m: table.get(m.group(0).lower(), m.group(0)),
        text,
    )
    if out != text:
        open(path, "w").write(out)
        return True
    return False


def main():
    n = sum(xfwm_map(p) for p in sorted(glob.glob("xfwm4/assets/*.svg")))
    print("xfwm4:  recoloured %d SVGs" % n)
    n = sum(asset_map(p) for p in sorted(glob.glob("assets/*.svg")))
    print("assets: recoloured %d SVGs" % n)


if __name__ == "__main__":
    main()
