# Copyright (c) V-Nova International Limited 2022-2025. All rights reserved.
# This software is licensed under the BSD-3-Clause-Clear License by V-Nova Limited.
# No patent licenses are granted under this license. For enquiries about patent licenses,
# please contact legal@v-nova.com.
# The LCEVCdec software is a stand-alone project and is NOT A CONTRIBUTION to any other project.
# If the software is incorporated into another project, THE TERMS OF THE BSD-3-CLAUSE-CLEAR LICENSE
# AND THE ADDITIONAL LICENSING INFORMATION CONTAINED IN THIS FILE MUST BE MAINTAINED, AND THE
# SOFTWARE DOES NOT AND MUST NOT ADOPT THE LICENSE OF THE INCORPORATING PROJECT. However, the
# software may be incorporated into a project under a compatible license provided the requirements
# of the BSD-3-Clause-Clear license are respected, and V-Nova Limited remains
# licensor of the software ONLY UNDER the BSD-3-Clause-Clear license (not the compatible license).
# ANY ONWARD DISTRIBUTION, WHETHER STAND-ALONE OR AS PART OF ANY OTHER PROJECT, REMAINS SUBJECT TO
# THE EXCLUSION OF PATENT LICENSES PROVISION OF THE BSD-3-CLAUSE-CLEAR LICENSE.

'''
    Generates a source file containing version data for a specific component.

    Can either be run directly on the command line or imported and the class
    ComponentGenerator can be used by another script.

    @todo: Embed compiler information here (i.e. compiler name and explicit version)
'''
import argparse
import datetime
import getpass
import os
import platform
import re

from lint import COPYRIGHT_MSG, format_cpp_comment
from write_changed import git_command, write_if_changed

EDIT_WARNING = 'DO NOT EDIT, THIS FILE IS AUTOGENERATED by cmake/tools/version_files.py '

header_msg = format_cpp_comment(COPYRIGHT_MSG + '\n\n' + EDIT_WARNING).format(
    years=datetime.datetime.now().year)

VersionTemplateH = header_msg + r'''
#pragma once

#include <stdint.h>

#if defined (_WIN32)
#define VN_VERSION_EXPORT() __declspec(dllexport)
#else
#define VN_VERSION_EXPORT() __attribute__((visibility ("default")))
#endif

#ifdef __cplusplus
extern "C" {{
#endif

VN_VERSION_EXPORT() const char* {component}Name(void);
VN_VERSION_EXPORT() uint32_t {component}VersionMajor(void);
VN_VERSION_EXPORT() uint32_t {component}VersionMinor(void);
VN_VERSION_EXPORT() uint32_t {component}VersionRevision(void);
VN_VERSION_EXPORT() const char* {component}Version(void);
VN_VERSION_EXPORT() const char* {component}VersionFull(void);
VN_VERSION_EXPORT() const char* {component}Hash(void);
VN_VERSION_EXPORT() const char* {component}HashShort(void);
VN_VERSION_EXPORT() const char* {component}BuildDate(void);
VN_VERSION_EXPORT() const char* {component}BuildMachine(void);
VN_VERSION_EXPORT() const char* {component}BuildUser(void);
VN_VERSION_EXPORT() uint8_t {component}IsDirty(void);

#ifdef __cplusplus
}}
#endif

#undef VN_VERSION_EXPORT
'''

VersionTemplateSrc = header_msg + r'''
#include "{output_h}"
#include <stdint.h>

#if defined (_WIN32)
#define VN_VERSION_EXPORT() __declspec(dllexport)
#else
#define VN_VERSION_EXPORT() __attribute__((visibility ("default")))
#endif

#ifdef __cplusplus
extern "C" {{
#endif

const char* const k{component}Name = "{name}";
const uint32_t k{component}VersionMajor = {version[major]};
const uint32_t k{component}VersionMinor = {version[minor]};
const uint32_t k{component}VersionRevision = {version[revision]};
const char* const k{component}Version = "{version[major]}.{version[minor]}.{version[revision]}";
const char* const k{component}VersionFull = "{git_version}";
const char* const k{component}Hash = "{git_hash}";
const char* const k{component}HashShort = "{version[hash]}";
const char* const k{component}BuildDate = "{build[date]}";
const char* const k{component}BuildMachine = "{build[machine]}";
const char* const k{component}BuildUser = "{build[user]}";
const uint8_t k{component}BuildDirty = {build[dirty]};

#define VERSION_JSON_PREFIX "$VersionJSON$: "
const char* const k{component}VersionJSON =
 VERSION_JSON_PREFIX
 "{{\n"
 "  \"name\":\"{name}\",\n"
 "  \"version_full\":\"{git_version}\",\n"
 "  \"version\":\"{version[major]}.{version[minor]}.{version[revision]}\",\n"
 "  \"version_major\":{version[major]},\n"
 "  \"version_minor\":{version[minor]},\n"
 "  \"version_revision\":{version[revision]},\n"
 "  \"version_ahead\":{version[ahead]},\n"
 "  \"hash\":\"{git_hash}\",\n"
 "  \"hash_short\":\"{version[hash]}\",\n"
 "  \"build_date\":\"{build[date]}\",\n"
 "  \"build_machine\":\"{build[machine]}\",\n"
 "  \"build_user\":\"{build[user]}\",\n"
 "  \"build_dirty\":{build[dirty]}\n"
 "}}";

VN_VERSION_EXPORT() const char* {component}Name(void) {{ return k{component}Name; }}
VN_VERSION_EXPORT() uint32_t {component}VersionMajor(void) {{ return k{component}VersionMajor; }}
VN_VERSION_EXPORT() uint32_t {component}VersionMinor(void) {{ return k{component}VersionMinor; }}
VN_VERSION_EXPORT() uint32_t {component}VersionRevision(void) {{ return k{component}VersionRevision; }}
VN_VERSION_EXPORT() const char* {component}Version(void) {{ return k{component}Version; }}
VN_VERSION_EXPORT() const char* {component}VersionFull(void) {{ return k{component}VersionFull; }}
VN_VERSION_EXPORT() const char* {component}Hash(void) {{ return k{component}Hash; }}
VN_VERSION_EXPORT() const char* {component}HashShort(void) {{ return k{component}HashShort; }}
VN_VERSION_EXPORT() const char* {component}BuildDate(void) {{ return k{component}BuildDate; }}
VN_VERSION_EXPORT() const char* {component}BuildMachine(void) {{ return k{component}BuildMachine; }}
VN_VERSION_EXPORT() const char* {component}BuildUser(void) {{ return k{component}BuildUser; }}
VN_VERSION_EXPORT() uint8_t {component}IsDirty(void) {{ return k{component}BuildDirty; }}
VN_VERSION_EXPORT() const char* {component}VersionJSON(void) {{ return k{component}VersionJSON + sizeof(VERSION_JSON_PREFIX); }}

#ifdef __cplusplus
}}
#endif'''

VersionTemplateRC = header_msg + r'''

#include <windows.h>
#include <stdint.h>

VS_VERSION_INFO VERSIONINFO
FILEVERSION      {version[major]}, {version[minor]}, {version[revision]}
PRODUCTVERSION   {version[major]}, {version[minor]}, {version[revision]}
FILEFLAGSMASK    VS_FFI_FILEFLAGSMASK
#ifdef DEBUG
FILEFLAGS        VS_FF_DEBUG
#else
FILEFLAGS        0
#endif
FILEOS           VOS_NT_WINDOWS32
FILETYPE         {rc_file_type}
FILESUBTYPE      VFT2_UNKNOWN
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "08090000"
        BEGIN
            VALUE "CompanyName",        "V-Nova Ltd"
            VALUE "FileDescription",    "{description}"
            VALUE "FileVersion",        "{git_version}"
            VALUE "ProductName",        "{binary_name}"
            VALUE "ProductVersion",     "{git_version}"
            VALUE "OriginalFilename",   "{binary_name}.{binary_type}"
            VALUE "InternalName",       "{name}"
            VALUE "LegalCopyright",     "Copyright (C) {date[year]} - V-Nova Ltd"
        END
    END

    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x0809, 0
    END
END
'''


class ComponentGenerator:
    version_re = re.compile(
        r"(dev)?(?P<major>[0-9]+)\.(?P<minor>[0-9]+)\.(?P<revision>[0-9]+)(\-(?P<ahead>[0-9]+))?(\-g(?P<hash>[0-9a-f]+))?(\-(?P<dirty>dirty))?"
    )

    date_re = re.compile(r"(?P<year>[0-9]+)-(?P<month>[0-9]+)-(?P<day>[0-9]+)")

    def _getuser(self):
        try:
            return getpass.getuser()
        except KeyError:
            return "unknown"

    def __init__(self, component, name, git_version, git_hash, git_date, output_src, output_h,
                 output_rc, binary_name, binary_type, description):

        # Fill in instance variable for use by template formatting
        self.component = component
        self.name = name
        self.git_version = git_version
        self.git_hash = git_hash
        self.git_date = git_date
        self.output_src = output_src
        self.output_h = output_h
        self.output_rc = output_rc
        self.binary_name = binary_name
        self.binary_type = binary_type.lower()
        self.description = description

        # Parse 'git describe' output into parts
        assert self.git_version and self.git_version != 'Unknown', \
            "Cannot find version - build from a git clone or disable VN_SDK_BUILD_DETAILS"
        m = re.match(self.version_re, self.git_version)
        if not m:
            raise RuntimeError("Incorrect version string format")
        self.version = m.groupdict()

        # Parse date into parts
        if self.git_date != "Unknown":
            m = re.match(self.date_re, self.git_date)
            if not m:
                raise RuntimeError("Incorrect date format")
            self.date = m.groupdict()
        else:
            self.date = self.git_date

        # Build details
        self.build = {'date': datetime.datetime.now().strftime("%a %d %b %Y"),
                      'machine': platform.node(),
                      'user': self._getuser(),
                      'dirty': self.version['dirty'] and 1 or 0}

        self.rc_file_type = "VFT_DLL" if (
            self.binary_type == "dll") else "VFT_STATIC_LIB" if (
                self.binary_type == "lib") else "VFT_APP"

    def generate(self, template, output_file):
        write_if_changed(output_file, template.format(**vars(self)))


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Main for invoking on an individual component.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Generate version data for a given component")
    parser.add_argument('--component', required=True)
    parser.add_argument('--name', required=True)
    parser.add_argument('--output_src')
    parser.add_argument('--output_h')
    parser.add_argument('--output_rc')
    parser.add_argument('--binary_name')
    parser.add_argument('--binary_type')
    parser.add_argument('--description')
    parser.add_argument('--git_version')
    parser.add_argument('--git_hash')
    parser.add_argument('--git_date')
    args = parser.parse_args()

    # Capture current git version
    source_dir = dir_path = os.path.dirname(os.path.realpath(__file__))
    git_version = args.git_version or git_command(
        ['git', '-C', source_dir, 'describe', '--match', '*.*.*', '--dirty'])
    git_hash = args.git_hash or git_command(
        ['git', '-C', source_dir, 'rev-parse', '--verify', 'HEAD'])
    git_date = args.git_date or git_command(
        ['git', '-C', source_dir, 'log', '-1', '--format=%cd', '--date=format:%Y-%m-%d'])

    # Fill generator with useful keys
    g = ComponentGenerator(args.component, args.name, git_version, git_hash, git_date,
                           args.output_src, args.output_h, args.output_rc,
                           args.binary_name, args.binary_type,
                           args.description)

    # Push through each required output file
    if args.output_src:
        g.generate(VersionTemplateSrc, args.output_src)

    if args.output_h:
        g.generate(VersionTemplateH, args.output_h)

    if args.output_rc:
        g.generate(VersionTemplateRC, args.output_rc)
