#!/usr/bin/env python3
import contextlib
import re
import subprocess

import xml.etree.ElementTree as etree

from datetime import datetime, timedelta

from xeplib import load_xepinfos, Status


def get_deferred(accepted):
    now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
    threshold = now.replace(year=now.year - 1)

    for number, info in sorted(accepted.items()):
        if info["status"] == Status.EXPERIMENTAL and "last_revision" in info:
            last_update = info["last_revision"]["date"]
            if last_update <= threshold:
                yield info


EXPERIMENTAL_STATUS = "<status>Experimental</status>"
DEFERRED_STATUS = "<status>Deferred</status>"
REVISION_RE = re.compile(r"\s+<revision>")
REVISION_TEMPLATE = """
  <revision>
    <version>{version}</version>
    <date>{now:%Y-%m-%d}</date>
    <initials>XEP Editor ({initials})</initials>
    <remark>Defer due to lack of activity.</remark>
  </revision>"""


@contextlib.contextmanager
def stash_guard():
    try:
        subprocess.check_call([
            "git", "diff-index", "--quiet", "HEAD", "--"
        ])
    except subprocess.CalledProcessError:
        # there are changes
        pass
    else:
        yield
        return

    subprocess.check_call([
        "git", "stash",
    ])

    try:
        yield
    finally:
        subprocess.check_call(["git", "stash", "pop"])


def defer_xep(number, last_version, initials, insert_revision):
    filename = "xep-{:04d}.xml".format(number)
    with open(filename, "r") as f:
        xep_text = f.read()

    if EXPERIMENTAL_STATUS not in xep_text:
        raise ValueError("cannot find experimental status in XEP text")

    # this is so incredibly evil ...
    xep_text = xep_text.replace(EXPERIMENTAL_STATUS, DEFERRED_STATUS, 1)
    revision_match = REVISION_RE.search(xep_text)

    version = last_version.split(".")
    if len(version) == 1:
        version.append("1")
    else:
        version[1] = str(int(version[1]) + 1)
        del version[2:]
    version.append("0")

    if insert_revision:
        xep_text = (
            xep_text[:revision_match.start()] +
            REVISION_TEMPLATE.format(
                now=datetime.utcnow(),
                version=".".join(version),
                initials=initials,
            ) + xep_text[revision_match.start():]
        )

    with open(filename, "w") as f:
        f.write(xep_text)
        f.flush()


def main():
    import argparse

    parser = argparse.ArgumentParser(
        description="Show the XEPs which need to be changed to deferred."
    )

    parser.add_argument(
        "-l", "--xeplist",
        type=argparse.FileType("rb"),
        default=None,
        help="XEP list to use (defaults to ./build/xeplist.xml)"
    )

    parser.add_argument(
        "-v", "--verbose",
        help="Print additional metadata for deferred XEPs",
        action="store_true",
        default=False,
    )

    parser.add_argument(
        "-m", "--modify",
        default=False,
        metavar="INITIALS",
        help="Modify the to-be-deferred XEPs in-place and use the given "
        "INITIALS in the remarks."
    )

    parser.add_argument(
       "--no-revision",
       dest="insert_revision",
       action="store_false",
       default=True,
       help="Do not create a revision block",
    )

    parser.add_argument(
        "-c", "--commit",
        default=False,
        action="store_true",
        help="Create a git commit for each deferral (only reasonable with -m)"
    )

    args = parser.parse_args()

    if args.xeplist is None:
        args.xeplist = open("./build/xeplist.xml", "rb")

    with args.xeplist as f:
        tree = etree.parse(f)

    accepted, _ = load_xepinfos(tree)
    deferred = list(get_deferred(accepted))

    with contextlib.ExitStack() as stack:
        if args.commit:
            stack.enter_context(stash_guard())

        for deferred_info in deferred:
            if args.modify:
                defer_xep(deferred_info["number"],
                          deferred_info["last_revision"]["version"],
                          args.modify,
			  args.insert_revision)
                if args.commit:
                    subprocess.check_call([
                        "git", "add", "xep-{:04d}.xml".format(
                            deferred_info["number"],
                        ),
                    ])
                    commit_argv = [
                        "git", "commit", "-vm",
                        "XEP-{:04d}: deferred due to lack of activity".format(
                            deferred_info["number"],
                        )
                    ]
                    if args.insert_revision:
                        commit_argv.append("-e")
                    subprocess.check_call(commit_argv)

            if args.verbose:
                print(
                    "XEP-{info[number]:04d}: {info[title]} "
                    "(last update {info[last_revision][date]:%Y-%m-%d})".format(
                        info=deferred_info
                    )
                )
            else:
                print(deferred_info["number"])


if __name__ == "__main__":
    main()