#!/usr/bin/env python3
"""Build public/fluffhex/ from the upstream Hextris checkout in sources/hextris.

Hextris (GPL-3.0) -> renamed "Fluffhex". This script is the authoritative record
of every modification we make, so the changes can be republished (GPL-3.0 §5).
Each replacement asserts that it matched, so an upstream change fails loudly.
"""
import re
import shutil
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
ROOT = Path(__file__).resolve().parent.parent
SRC = ROOT / "sources" / "hextris"
DST = ROOT / "public" / "fluffhex"

BASE_URL = "https://games.fluffernookgames.com/fluffhex/"
DESC = "A fast-paced hexagon puzzle game. Rotate the hexagon and match three colors."

# Files/dirs from upstream that must not be shipped.
SKIP = {
    ".git", "CNAME", "README.md", "a.js",           # a.js: upstream remote-script stub
}
SKIP_IMAGES = {                                       # store badges / social assets
    "android.png", "appstore.svg", "btn_facebook.svg", "btn_twitter.svg",
    "btn_share.svg", "facebook-opengraph.png", "twitter-opengraph.png",
}
SKIP_VENDOR = {"rrssb.min.js"}
SKIP_STYLE = {"rrssb.css"}
SKIP_FONTS = {"Lovelo.otf", "roboto.woff", "QuattrocentoSans-Regular.ttf"}  # unused upstream; licence unclear


def sub(text, old, new, count=1, regex=False, path=""):
    if regex:
        out, n = re.subn(old, new, text, flags=re.S)
    else:
        n = text.count(old)
        out = text.replace(old, new)
    if n < 1 or (count and n != count):
        sys.exit(f"[{path}] expected {count} match(es), got {n} for: {old[:70]!r}")
    return out


def edit(rel, fn):
    p = DST / rel
    p.write_text(fn(p.read_text(encoding="utf-8"), rel), encoding="utf-8")


def copy_tree():
    if DST.exists():
        shutil.rmtree(DST)
    for src in SRC.rglob("*"):
        rel = src.relative_to(SRC)
        parts = rel.parts
        if parts[0] in SKIP or src.is_dir():
            continue
        if parts[0] == "images" and len(parts) == 2 and parts[1] in SKIP_IMAGES:
            continue
        if parts[0] == "vendor" and parts[-1] in SKIP_VENDOR:
            continue
        if parts[0] == "style" and len(parts) == 2 and parts[1] in SKIP_STYLE:
            continue
        if parts[:2] == ("style", "fonts") and parts[-1] in SKIP_FONTS:
            continue
        out = DST / rel
        out.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(src, out)


def patch_index(t, p):
    t = sub(t, "<title>Hextris</title>", "<title>Fluffhex</title>", path=p)
    # Replace the whole upstream <meta> block (itunes/og/twitter pointing at the
    # original authors) with a minimal first-party one.
    start = t.index('<meta name="description"')
    end = t.index('<link rel="icon"')
    head = f'''<meta name="description" content="{DESC}">
		<link rel="manifest" href="manifest.webmanifest">
		<meta name="apple-mobile-web-app-title" content="Fluffhex">
		<meta name="apple-mobile-web-app-capable" content="yes">
		<link rel="apple-touch-icon" sizes="120x120" href="images/icons/apple-touch-120.png">
		<link rel="apple-touch-icon" sizes="152x152" href="images/icons/apple-touch-152.png">
		<link rel="apple-touch-icon" sizes="167x167" href="images/icons/apple-touch-167.png">
		<link rel="apple-touch-icon" sizes="180x180" href="images/icons/apple-touch-180.png">
		<link rel="apple-touch-icon" sizes="512x512" href="images/icons/apple-touch-512.png">
		<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, minimal-ui"/>
		<meta property="og:url" content="{BASE_URL}"/>
		<meta property="og:site_name" content="Fluffernook Games"/>
		<meta property="og:title" content="Fluffhex"/>
		<meta property="og:description" content="{DESC}"/>
		<meta property="og:type" content="website"/>
		'''
    t = t[:start] + head + t[end:]
    # Google Fonts: Exo is already bundled in style/fonts and loaded via @font-face.
    t = sub(t, r"\t\t<link href='http://fonts.googleapis.com[^>]*>\n", "", regex=True, path=p)
    # rrssb social-share css/js
    t = sub(t, '\t\t<link rel="stylesheet" href="style/rrssb.css"/>\n', "", path=p)
    t = sub(t, "\t\t<script type=\"text/javascript\" src='vendor/rrssb.min.js'></script>\n", "", path=p)
    # Google AdSense
    t = sub(t, r'\t\t<script data-ad-client=.*?</script>\n', "", regex=True, path=p)
    # Google Analytics inline snippet
    t = sub(t, r"\t\t<script> \(function\(i,s,o,g,r,a,m\).*?</script>\n", "", regex=True, path=p)
    # Share / store badges / russian VK share: replace with an empty #buttonCont
    # (main.js measures its offset for layout) plus a small credit line.
    s = t.index("\t\t\t\t<div id='socialShare'>")
    e = t.index("\t</body>")
    t = t[:s] + "\t\t\t\t<div id='buttonCont'></div>\n\t\t\t</div>\n\t\t</div>\n" + t[e:]
    return t


def patch_manifest(t, p):
    t = sub(t, '"name": "Hextris"', '"name": "Fluffhex"', path=p)
    t = sub(t, '"id": "hextris.io"', '"id": "fluffhex"', path=p)
    t = sub(t, '"description": "An addictive puzzle game inspired by Tetris."',
            f'"description": "{DESC}"', path=p)
    return t


KEYS = [
    ('localStorage.getItem("saveState")', 'localStorage.getItem("fluffhex_saveState")'),
    ('localStorage.setItem("saveState"', 'localStorage.setItem("fluffhex_saveState"'),
    ("localStorage.getItem('saveState')", "localStorage.getItem('fluffhex_saveState')"),
    ("localStorage.setItem('highscores'", "localStorage.setItem('fluffhex_highscores'"),
    ('localStorage.setItem("highscores"', 'localStorage.setItem("fluffhex_highscores"'),
    ("localStorage.getItem('highscores')", "localStorage.getItem('fluffhex_highscores')"),
]


def rekey(t, p):
    """Namespace storage keys: all games share one origin."""
    for old, new in KEYS:
        t = t.replace(old, new)
    return t


def patch_main(t, p):
    t = rekey(t, p)
    # Score beacon to the upstream author's server.
    t = sub(t, "\t\t\t$.get('http://54.183.184.126/' + String(score))\n", "", path=p)
    # Remote script loader (http://hextris.io/a.js).
    t = sub(t, r"\n\(function\(\)\{\n    \tvar script = document.createElement\('script'\);.*?\}\)\(\)\n?",
            "\n", regex=True, path=p)
    # Help text: credits, no store links / upstream site.
    t = sub(t, r"<hr> <p id = 'afterhr'></p> By .*?Hextris Website</a>\"\);",
            "<hr> <p id = 'afterhr'></p> Fluffhex is based on <a href='https://github.com/Hextris/hextris' target='_blank' rel='noopener'>Hextris</a>"
            " by <a href='http://loganengstrom.com' target='_blank' rel='noopener'>Logan Engstrom</a> &amp; "
            "<a href='http://github.com/garrettdreyfus' target='_blank' rel='noopener'>Garrett Finucane</a> (GPL-3.0).<br>"
            "<a href='../credits/' target='_blank'>Credits &amp; source code</a>\");",
            regex=True, path=p)
    t = sub(t, "The goal of Hextris is", "The goal of Fluffhex is", path=p)
    return t


def patch_view(t, p):
    t = rekey(t, p)
    t = t.replace('"Hextris");', '"Fluffhex");')
    t = sub(t, 'Cookies.set("visited",true);', 'Cookies.set("fluffhex_visited",true);', path=p)
    # Title is one letter longer than the original; shrink so it fits.
    t = sub(t, '150, "#2c3e50", "Fluffhex"', '130, "#2c3e50", "Fluffhex"', count=2, path=p)
    # Ad-free upsell links on the pause screen -> plain "Game Paused".
    t = sub(t, r"'pausedAndroid': .*?\n\t\t'pausediOS': .*?\n\t\t'pausedOther': .*?\n",
            "'pausedAndroid': \"<div class='centeredHeader unselectable'>Game Paused</div>\",\n"
            "\t\t'pausediOS': \"<div class='centeredHeader unselectable'>Game Paused</div>\",\n"
            "\t\t'pausedOther': \"<div class='centeredHeader unselectable'>Game Paused</div>\",\n",
            regex=True, path=p)
    return t


def patch_init(t, p):
    t = rekey(t, p)
    # Google Analytics.
    t = sub(t, r"\t\t\(function\(i, s, o, g, r, a, m\) \{.*?ga\('send', 'pageview'\);\n\n", "",
            regex=True, path=p)
    return t


def patch_generic_rekey(t, p):
    return rekey(t, p)


def main():
    if not SRC.exists():
        sys.exit(f"missing {SRC}")
    copy_tree()
    edit("index.html", patch_index)
    # Share button is gone; centre the restart button it used to sit beside.
    edit("style/style.css", lambda t, p: sub(t, "left:calc(50% - 124px);", "left:50%;", path=p))
    edit("manifest.webmanifest", patch_manifest)
    edit("js/main.js", patch_main)
    edit("js/view.js", patch_view)
    edit("js/initialization.js", patch_init)
    edit("js/save-state.js", patch_generic_rekey)
    edit("js/Block.js", patch_generic_rekey)
    # GPL-3.0: ship the record of modifications alongside the modified source.
    shutil.copy2(__file__, DST / "MODIFICATIONS-build_fluffhex.py")
    from inject_gamenav import inject
    inject("fluffhex", top=84)  # clear the game's own help button at top-left
    print("built", DST)


if __name__ == "__main__":
    main()
