#!/usr/bin/env python3
"""/deploy guard: the skin trees may only change together with a version bump (040 §3).

skin.milionar and its modern look skin.milionar.modern (an overlay composed over it, 240) share one version: a change
of either tree bumps both manifests.

  deploy_check.py check    exit 1 if resources/skin.milionar or resources/skin.milionar.modern changed since the last
                           recorded deploy but the version did not
  deploy_check.py record   store the current tree hash + version (run after a successful upload)
"""
import hashlib
import json
import os
import re
import sys
import xml.etree.ElementTree as ET

ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
TREE = os.path.join(ROOT, 'resources', 'skin.milionar')
MODERN = os.path.join(ROOT, 'resources', 'skin.milionar.modern')
MANIFESTS = (os.path.join(TREE, 'addon.xml'), os.path.join(MODERN, 'addon.xml'))
STATE = os.path.join(ROOT, '.agents', 'state', 'skin_deploy.json')


def tree_hash():
    """One hash of both skin trees (the classic one alone hashes as before the modern skin existed)."""
    digest = hashlib.sha256()
    for tree in (TREE, MODERN):
        for base, dirs, files in os.walk(tree):
            dirs.sort()
            for name in sorted(files):
                if name == '.DS_Store':
                    continue
                path = os.path.join(base, name)
                relative = os.path.relpath(path, TREE if tree == TREE else ROOT)
                digest.update(relative.encode())
                with open(path, 'rb') as handle:
                    digest.update(hashlib.sha256(handle.read()).digest())
    return digest.hexdigest()


def version():
    return ET.parse(os.path.join(TREE, 'addon.xml')).getroot().get('version')


def _write_manifests(plugin_version, skin_version):
    """The plugin dependency and the one skin version, in both manifests."""
    for path in MANIFESTS:
        with open(path, encoding='utf-8') as handle:
            text = handle.read()
        text, count = re.subn(r'(<import\b[^>]*addon="plugin.video.milionar"[^>]*version=")[^"]+',
                             lambda match: match.group(1) + plugin_version, text)
        if count != 1:
            raise SystemExit('expected one plugin.video.milionar dependency in ' + path)
        text = re.sub(r'(<addon\b[^>]*version=")[^"]+', lambda match: match.group(1) + skin_version, text, count=1)
        with open(path, 'w', encoding='utf-8') as handle:
            handle.write(text)


def prepare(plugin_version):
    """Synchronize the dependency and bump changed skins before the existing guard runs."""
    current = version()
    _write_manifests(plugin_version, current)     # a new dependency is a change of the skin too
    if os.path.exists(STATE):
        with open(STATE) as handle:
            last = json.load(handle)
        parts = lambda value: tuple(int(part) for part in value.split('.'))
        if last['hash'] != tree_hash() and parts(current) <= parts(last['version']):
            old = parts(last['version'])
            current = '.'.join(str(part) for part in old[:-1] + (old[-1] + 1,))
            print('skin version {} -> {}'.format(last['version'], current))
            _write_manifests(plugin_version, current)
    path = os.path.join(TREE, 'changelog.txt')
    with open(path, encoding='utf-8') as handle:
        notes = handle.read()
    if '[B]{}[/B]'.format(current) not in notes:
        with open(path, 'w', encoding='utf-8') as handle:
            handle.write('[B]{}[/B]\n• Release with Milionář {}; skin files and addon dependency updated\n\n'.format(
                current, plugin_version) + notes)
    return current


def main():
    mode = sys.argv[1] if len(sys.argv) > 1 else 'check'
    now = {'version': version(), 'hash': tree_hash()}
    if mode == 'record':
        os.makedirs(os.path.dirname(STATE), exist_ok=True)
        with open(STATE, 'w') as handle:
            json.dump(now, handle, indent=1)
        print('recorded skin {} {}'.format(now['version'], now['hash'][:12]))
        return
    if not os.path.exists(STATE):
        print('no previous deploy recorded: ok (skin {})'.format(now['version']))
        return
    last = json.load(open(STATE))
    if last['hash'] != now['hash'] and tuple(map(int, now['version'].split('.'))) <= tuple(map(int, last['version'].split('.'))):
        raise SystemExit('skin tree changed since the last deploy but the version is still {}: '
                         'bump the version in resources/skin.milionar/addon.xml (+ changelog.txt)'.format(now['version']))
    print('skin ok: {} (last deployed {})'.format(now['version'], last['version']))


if __name__ == '__main__':
    main()
