# 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 io
import os
import sys
from datetime import datetime

import buildconfig
from mozbuild.preprocessor import Preprocessor

TEMPLATE = """
// 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/.

#include<winuser.h>
#include<winver.h>

// Note: if you contain versioning information in an included
// RC script, it will be discarded
// Use module.ver to explicitly set these values

// Do not edit this file. Changes won't affect the build.

{include}

Identity LimitedAccessFeature {{ L"{lafidentity}_pcsmm0jrprpb2" }}


/////////////////////////////////////////////////////////////////////////////
//
// Version
//

1 VERSIONINFO
 FILEVERSION    {fileversion}
 PRODUCTVERSION {productversion}
 FILEFLAGSMASK 0x3fL
 FILEFLAGS {fileflags}
 FILEOS VOS__WINDOWS32
 FILETYPE VFT_DLL
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "000004b0"
        BEGIN
            VALUE "Comments", "{comment}"
            VALUE "LegalCopyright", "{copyright}"
            VALUE "CompanyName", "{company}"
            VALUE "FileDescription", "{description}"
            VALUE "FileVersion", "{mfversion}"
            VALUE "ProductVersion", "{mpversion}"
            VALUE "InternalName", "{module}"
            VALUE "LegalTrademarks", "{trademarks}"
            VALUE "OriginalFilename", "{binary}"
            VALUE "ProductName", "{productname}"
            VALUE "BuildID", "{buildid}"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x0, 1200
    END
END

"""


def preprocess(path, defines):
    pp = Preprocessor(defines=defines, marker="%")
    pp.context.update(defines)
    pp.out = io.StringIO()
    pp.do_filter("substitution")
    pp.do_include(io.open(path, "r", encoding="latin1"))
    pp.out.seek(0)
    return pp.out


def parse_module_ver(path, defines):
    result = {}
    for line in preprocess(path, defines):
        content, *comment = line.split("#", 1)
        if not content.strip():
            continue
        entry, value = content.split("=", 1)
        result[entry.strip()] = value.strip()
    return result


def get_buildid():
    path = os.path.join(buildconfig.topobjdir, "buildid.h")
    define, MOZ_BUILDID, buildid = io.open(path, "r", encoding="utf-8").read().split()
    return buildid


def days_from_2000_to_buildid(buildid):
    start = datetime(2000, 1, 1, 0, 0, 0)
    buildid_time = datetime.strptime(buildid, "%Y%m%d%H%M%S")
    return (buildid_time - start).days


def digits_only(s):
    for l in range(len(s), 0, -1):
        if s[:l].isdigit():
            return s[:l]
    return "0"


def split_and_normalize_version(version, len):
    return ([digits_only(x) for x in version.split(".")] + ["0"] * len)[:len]


def has_manifest(module_rc, manifest_id):
    for lineFromInput in module_rc.splitlines():
        line = lineFromInput.split(None, 2)
        if len(line) < 2:
            continue
        id, what, *rest = line
        if id == manifest_id and what in ("24", "RT_MANIFEST"):
            return True
    return False


def generate_module_rc(binary="", rcinclude=None):
    deps = set()
    buildid = get_buildid()
    milestone = buildconfig.substs["GRE_MILESTONE"]
    app_version = buildconfig.substs.get("MOZ_APP_VERSION") or milestone
    app_winversion = ",".join(split_and_normalize_version(app_version, 4))
    milestone_winversion = ",".join(
        split_and_normalize_version(milestone, 3)
        + [str(days_from_2000_to_buildid(buildid))]
    )
    display_name = buildconfig.substs.get("MOZ_APP_DISPLAYNAME", "Mozilla")

    milestone_string = milestone

    flags = ["0"]
    if buildconfig.substs.get("MOZ_DEBUG"):
        flags.append("VS_FF_DEBUG")
        milestone_string += " Debug"
    if not buildconfig.substs.get("MOZILLA_OFFICIAL"):
        flags.append("VS_FF_PRIVATEBUILD")
    if buildconfig.substs.get("NIGHTLY_BUILD"):
        flags.append("VS_FF_PRERELEASE")

    defines = {
        "MOZ_APP_DISPLAYNAME": display_name,
        "MOZ_APP_VERSION": app_version,
        "MOZ_APP_WINVERSION": app_winversion,
    }

    relobjdir = os.path.relpath(".", buildconfig.topobjdir)
    srcdir = os.path.join(buildconfig.topsrcdir, relobjdir)
    module_ver = os.path.join(srcdir, "module.ver")
    if os.path.exists(module_ver):
        deps.add(module_ver)
        overrides = parse_module_ver(module_ver, defines)
    else:
        overrides = {}

    if rcinclude:
        include = "// From included resource {}\n{}".format(
            rcinclude, preprocess(rcinclude, defines).read()
        )
    else:
        include = ""

    # Set the identity field for the Limited Access Feature
    # Must match the tokens used in Win11LimitedAccessFeatures.cpp
    lafidentity = "MozillaFirefox"
    # lafidentity = "FirefoxBeta"
    # lafidentity = "FirefoxNightly"

    data = TEMPLATE.format(
        include=include,
        lafidentity=lafidentity,
        fileversion=overrides.get("WIN32_MODULE_FILEVERSION", milestone_winversion),
        productversion=overrides.get(
            "WIN32_MODULE_PRODUCTVERSION", milestone_winversion
        ),
        fileflags=" | ".join(flags),
        comment=overrides.get("WIN32_MODULE_COMMENT", ""),
        copyright=overrides.get("WIN32_MODULE_COPYRIGHT", "License: MPL 2"),
        company=overrides.get("WIN32_MODULE_COMPANYNAME", "Mozilla Foundation"),
        description=overrides.get("WIN32_MODULE_DESCRIPTION", ""),
        mfversion=overrides.get("WIN32_MODULE_FILEVERSION_STRING", milestone_string),
        mpversion=overrides.get("WIN32_MODULE_PRODUCTVERSION_STRING", milestone_string),
        module=overrides.get("WIN32_MODULE_NAME", ""),
        trademarks=overrides.get("WIN32_MODULE_TRADEMARKS", "Mozilla"),
        binary=overrides.get("WIN32_MODULE_ORIGINAL_FILENAME", binary),
        productname=overrides.get("WIN32_MODULE_PRODUCTNAME", display_name),
        buildid=buildid,
    )

    manifest_id = "2" if binary.lower().endswith(".dll") else "1"
    if binary and not has_manifest(data, manifest_id):
        manifest_path = os.path.join(srcdir, binary + ".manifest")
        if os.path.exists(manifest_path):
            manifest_path = manifest_path.replace("\\", "\\\\")
            data += '\n{} RT_MANIFEST "{}"\n'.format(manifest_id, manifest_path)

    with io.open("{}.rc".format(binary or "module"), "w", encoding="latin1") as fh:
        fh.write(data)


if __name__ == "__main__":
    generate_module_rc(*sys.argv[1:])
