#!/usr/bin/env python3

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import json
import pathlib
import re
import subprocess
import sys

TBPL_FAILURE = 2

excluded_files = [
    # Testcase for loader.
    "js/xpconnect/tests/chrome/file_expandosharing.jsm",
    "js/xpconnect/tests/unit/environment_script.js",
    "js/xpconnect/tests/unit/bogus_element_type.jsm",
    "js/xpconnect/tests/unit/bogus_exports_type.jsm",
    "js/xpconnect/tests/unit/envChain.jsm",
    "js/xpconnect/tests/unit/envChain_subscript.jsm",
    "js/xpconnect/tests/unit/environment_checkscript.jsm",
    "js/xpconnect/tests/unit/environment_loadscript.jsm",
    "js/xpconnect/tests/unit/import_stack.jsm",
    "js/xpconnect/tests/unit/importer.jsm",
    "js/xpconnect/tests/unit/jsm_loaded-1.jsm",
    "js/xpconnect/tests/unit/jsm_loaded-2.jsm",
    "js/xpconnect/tests/unit/jsm_loaded-3.jsm",
    "js/xpconnect/tests/unit/not-esmified-not-exported.jsm",
    "js/xpconnect/tests/unit/recursive_importA.jsm",
    "js/xpconnect/tests/unit/recursive_importB.jsm",
    "js/xpconnect/tests/unit/ReturnCodeChild.jsm",
    "js/xpconnect/tests/unit/syntax_error.jsm",
    "js/xpconnect/tests/unit/TestBlob.jsm",
    "js/xpconnect/tests/unit/TestFile.jsm",
    "js/xpconnect/tests/unit/uninitialized_lexical.jsm",
    "dom/url/tests/file_url.jsm",
    "dom/url/tests/file_worker_url.jsm",
    "dom/url/tests/test_bug883784.jsm",
    "dom/workers/test/WorkerTest.jsm",
    "dom/encoding/test/file_stringencoding.jsm",
    "remote/shared/messagehandler/test/browser/resources/modules/root/invalid.jsm",
    "toolkit/actors/TestProcessActorChild.jsm",
    "toolkit/actors/TestProcessActorParent.jsm",
    "toolkit/actors/TestWindowChild.jsm",
    "toolkit/actors/TestWindowParent.jsm",
    # Testcase for build system.
    "python/mozbuild/mozbuild/test/backend/data/build/bar.jsm",
    "python/mozbuild/mozbuild/test/backend/data/build/baz.jsm",
    "python/mozbuild/mozbuild/test/backend/data/build/foo.jsm",
    "python/mozbuild/mozbuild/test/backend/data/build/qux.jsm",
    # Testcase for test harness.
    "testing/mochitest/tests/Harness_sanity/ImportTesting.jsm",
    # EXPORTED_SYMBOLS inside testcase.
    "tools/lint/eslint/eslint-plugin-mozilla/tests/mark-exported-symbols-as-used.js",
]

if pathlib.Path(".hg").exists():
    mode = "hg"
elif pathlib.Path(".git").exists():
    mode = "git"
else:
    print(
        "Error: This script needs to be run inside mozilla-central checkout "
        "of either mercurial or git.",
        file=sys.stderr,
    )
    sys.exit(TBPL_FAILURE)


def new_files_struct():
    return {
        "jsm": [],
        "esm": [],
        "subdir": {},
    }


def put_file(files, kind, path):
    """Put a path into files tree structure."""

    if str(path) in excluded_files:
        return

    name = path.name

    current_files = files
    for part in path.parent.parts:
        if part not in current_files["subdir"]:
            current_files["subdir"][part] = new_files_struct()
        current_files = current_files["subdir"][part]

    current_files[kind].append(name)


def run(cmd):
    """Run command and return output as lines, excluding empty line."""
    lines = subprocess.run(cmd, stdout=subprocess.PIPE).stdout.decode()
    return filter(lambda x: x != "", lines.split("\n"))


def collect_jsm(files):
    """Collect JSM files."""
    kind = "jsm"

    # jsm files
    if mode == "hg":
        cmd = ["hg", "files", "set:glob:**/*.jsm"]
    else:
        cmd = ["git", "ls-files", "*.jsm"]
    for line in run(cmd):
        put_file(files, kind, pathlib.Path(line))

    # js files with EXPORTED_SYMBOLS
    if mode == "hg":
        cmd = ["hg", "files", "set:grep('EXPORTED_SYMBOLS = \[') and glob:**/*.js"]
        for line in run(cmd):
            put_file(files, kind, pathlib.Path(line))
    else:
        handled = {}
        cmd = ["git", "grep", "EXPORTED_SYMBOLS = \[", "*.js"]
        for line in run(cmd):
            m = re.search("^([^:]+):", line)
            if not m:
                continue
            path = m.group(1)
            if path in handled:
                continue
            handled[path] = True
            put_file(files, kind, pathlib.Path(path))


def collect_esm(files):
    """Collect system ESM files."""
    kind = "esm"

    # sys.mjs files
    if mode == "hg":
        cmd = ["hg", "files", "set:glob:**/*.sys.mjs"]
    else:
        cmd = ["git", "ls-files", "*.sys.mjs"]
    for line in run(cmd):
        put_file(files, kind, pathlib.Path(line))


def to_stat(files):
    """Convert files tree into status tree."""
    jsm = len(files["jsm"])
    esm = len(files["esm"])
    subdir = {}

    for key, sub_files in files["subdir"].items():
        sub_stat = to_stat(sub_files)

        subdir[key] = sub_stat
        jsm += sub_stat["jsm"]
        esm += sub_stat["esm"]

    stat = {
        "jsm": jsm,
        "esm": esm,
    }
    if len(subdir):
        stat["subdir"] = subdir

    return stat


if mode == "hg":
    cmd = ["hg", "parent", "--template", "{node}"]
    commit_hash = list(run(cmd))[0]

    cmd = ["hg", "parent", "--template", "{date|shortdate}"]
    date = list(run(cmd))[0]
else:
    cmd = ["git", "log", "-1", "--pretty=%H"]
    git_hash = list(run(cmd))[0]
    cmd = ["git", "cinnabar", "git2hg", git_hash]
    commit_hash = list(run(cmd))[0]

    cmd = ["git", "log", "-1", "--pretty=%cs"]
    date = list(run(cmd))[0]

files = new_files_struct()
collect_jsm(files)
collect_esm(files)

stat = to_stat(files)
stat["hash"] = commit_hash
stat["date"] = date

print(json.dumps(stat, indent=2))
