#! /usr/bin/python3

# Copyright 2022 The ANGLE Project Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# gen_features.py:
#  Code generation for ANGLE features.
#  NOTE: don't run this script directly. Run scripts/run_code_generation.py.

from collections import namedtuple
import json
import os
import re
import sys

feature_files = {
    'd3d_features.json': ('D3D', 'FeaturesD3D'),
    'frontend_features.json': ('Frontend', 'FrontendFeatures'),
    'gl_features.json': ('OpenGL', 'FeaturesGL'),
    'mtl_features.json': ('Metal', 'FeaturesMtl'),
    'vk_features.json': ('Vulkan', 'FeaturesVk'),
}
feature_list_header_file = '../../util/autogen/angle_features_autogen.h'
feature_list_source_file = '../../util/autogen/angle_features_autogen.cpp'

template_header = u"""// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {input_file_name}.
//
{description}

#ifndef ANGLE_PLATFORM_AUTOGEN_{NAME}_H_
#define ANGLE_PLATFORM_AUTOGEN_{NAME}_H_

#include "platform/Feature.h"

namespace angle
{{

struct {name} : FeatureSetBase
{{
    {name}();
    ~{name}();

{features}
}};

inline {name}::{name}()  = default;
inline {name}::~{name}() = default;

}}  // namespace angle

#endif  // ANGLE_PLATFORM_AUTOGEN_{NAME}_H_
"""

template_feature = u"""    FeatureInfo {var_name} = {{
        "{display_name}",
        FeatureCategory::{category},
        {description},
        &members,{issue}
    }};
"""

template_feature_list_header = u"""// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {input_file_name}.
//
// Copyright 2022 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// angle_features_autogen.h: List of ANGLE features to help enable/disable them in tests.

#ifndef ANGLE_SRC_TESTS_TEST_UTIL_AUTOGEN_ANGLE_FEATURES_AUTOGEN_H_
#define ANGLE_SRC_TESTS_TEST_UTIL_AUTOGEN_ANGLE_FEATURES_AUTOGEN_H_

#include "../util_export.h"

namespace angle
{{
enum class Feature
{{
{features}

    InvalidEnum,
    EnumCount = InvalidEnum,
}};

ANGLE_UTIL_EXPORT extern const char *GetFeatureName(Feature feature);

}}  // namespace angle

#endif  // ANGLE_SRC_TESTS_TEST_UTIL_AUTOGEN_ANGLE_FEATURES_AUTOGEN_H_
"""

template_feature_enum = u"""{VarName},"""

template_feature_list_source = u"""// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {input_file_name}.
//
// Copyright 2022 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// angle_features_autogen.cpp: List of ANGLE features to help enable/disable them in tests.

#include "angle_features_autogen.h"

#include "common/PackedEnums.h"

namespace angle
{{
namespace
{{
constexpr PackedEnumMap<Feature, const char *> kFeatureNames = {{{{
{features}
}}}};
}}  // anonymous namespace

const char *GetFeatureName(Feature feature)
{{
    return kFeatureNames[feature];
}}

}}  // namespace angle
"""

template_feature_string = u"""    {{Feature::{VarName}, "{display_name}"}},"""


def make_camel_case(json_name):
    assert '_' in json_name, 'feature names in the json file are expected to be in snake_case'
    return re.sub('_(.)', lambda m: m.group(1).upper(), json_name)


def make_header_name(class_name):
    return class_name + '_autogen.h'


def header_path(class_name):
    return 'autogen/' + make_header_name(class_name)


def write_or_verify_file(filename, content, verify_only):
    if verify_only:
        try:
            with open(filename) as f:
                # Note: .gitattributes "* text=auto" handles LF <-> CRLF on Windows
                return f.read() == content
        except FileNotFoundError:
            return False
    else:
        with open(filename, 'w') as fout:
            fout.write(content)
            return True


def main():
    if len(sys.argv) == 2 and sys.argv[1] == 'inputs':
        print(','.join(list(feature_files.keys())))
        return
    if len(sys.argv) == 2 and sys.argv[1] == 'outputs':
        print(','.join([header_path(class_name) for (_, class_name) in feature_files.values()]) +
              ',' + feature_list_header_file + ',' + feature_list_source_file)
        return

    # --verify-only enables dirty checks without relying on checked in hashes.
    # Compares the content of the existing file with the generated content.
    verify_only = '--verify-only' in sys.argv

    name_map = {}

    for src_file, (category_prefix, class_name) in feature_files.items():
        with open(src_file) as fin:
            src = json.loads(fin.read())

        features_json = src['features']
        features = []

        # Go over the list of features and write the header file that declares the features struct
        for feature_json in features_json:
            json_name = feature_json['name']
            var_name = make_camel_case(json_name)
            # Use the same (camelCase) name for display as well
            display_name = var_name
            issue = feature_json.get('issue', None)
            feature = template_feature.format(
                var_name=var_name,
                display_name=display_name,
                category=category_prefix + feature_json['category'],
                description='\n        '.join(
                    '"' + line + '"' for line in feature_json['description']),
                issue='' if issue is None else ' "' + issue + '"')

            features.append(feature)

            # Keep track of the feature names.  Sometimes the same feature name is present in
            # multiple backends.  That's ok for the purposes of feature overriding.
            name_map[var_name] = display_name

        description = '\n'.join(
            ['//' + (' ' + line if line else '') for line in src['description']])
        header_file = make_header_name(class_name)

        header = template_header.format(
            script_name=os.path.basename(__file__),
            input_file_name=src_file,
            description=description.replace(src_file, header_file),
            name=class_name,
            NAME=class_name.upper(),
            features='\n'.join(features))

        if not write_or_verify_file(header_path(class_name), header, verify_only):
            return 1

    # Generate helpers for use by tests to override a feature or not.
    feature_enums = []
    feature_strings = []
    for var_name, display_name in sorted(name_map.items(), key=lambda item: item[0].lower()):
        VarName = var_name[0].upper() + var_name[1:]

        feature_enums.append(template_feature_enum.format(VarName=VarName))

        feature_strings.append(
            template_feature_string.format(VarName=VarName, display_name=display_name))

    if not write_or_verify_file(
            feature_list_header_file,
            template_feature_list_header.format(
                script_name=os.path.basename(__file__),
                input_file_name='*_features.json',
                features='\n'.join('    ' + v for v in feature_enums)), verify_only):
        return 1

    if not write_or_verify_file(
            feature_list_source_file,
            template_feature_list_source.format(
                script_name=os.path.basename(__file__),
                input_file_name='*_features.json',
                features='\n'.join(feature_strings)), verify_only):
        return 1


if __name__ == '__main__':
    sys.exit(main())
