#!/usr/bin/python2
#
# Copyright 2020 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_restricted_traces.py:
#   Generates integration code for the restricted trace tests.

import fnmatch
import json
import os
import sys

gni_template = """# GENERATED FILE - DO NOT EDIT.
# Generated by {script_name} using data from {data_source_name}
#
# Copyright 2020 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.
#
# A list of all restricted trace tests, paired with their context.
# Can be consumed by tests/BUILD.gn.

angle_restricted_traces = [
{test_list}
]
"""

header_template = """// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {data_source_name}
//
// Copyright 2020 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.
//
// Types and enumerations for trace tests.

#ifndef ANGLE_RESTRICTED_TRACES_H_
#define ANGLE_RESTRICTED_TRACES_H_

{includes}

namespace angle
{{
enum class RestrictedTraceID
{{
{trace_ids}, InvalidEnum, EnumCount = InvalidEnum
}};

using ReplayFunc = void (*)(uint32_t);
using ResetFunc = void (*)();
using SetupFunc = void (*)();
using DecompressFunc = uint8_t *(*)(const std::vector<uint8_t> &);
using SetBinaryDataDirFunc = void (*)(const char *);

static constexpr size_t kTraceInfoMaxNameLen = 32;

struct TraceInfo
{{
    uint32_t startFrame;
    uint32_t endFrame;
    char name[kTraceInfoMaxNameLen];
}};

constexpr angle::PackedEnumMap<RestrictedTraceID, TraceInfo> kTraceInfos = {{
{trace_infos}
}};

using DecompressCallback = uint8_t *(*)(const std::vector<uint8_t> &);
using FramebufferChangeCallback = void(*)(void *userData, GLenum target, GLuint framebuffer);

inline void ReplayFrame(RestrictedTraceID traceID, uint32_t frameIndex)
{{
    switch (traceID)
    {{
{replay_func_cases}
        default:
            fprintf(stderr, "Error in switch.\\n");
            assert(0);
            break;
    }}
}}

inline void ResetReplay(RestrictedTraceID traceID)
{{
    switch (traceID)
    {{
{reset_func_cases}
        default:
            fprintf(stderr, "Error in switch.\\n");
            assert(0);
            break;
    }}
}}

inline void SetupReplay(RestrictedTraceID traceID)
{{
    switch (traceID)
    {{
{setup_func_cases}
        default:
            fprintf(stderr, "Error in switch.\\n");
            assert(0);
            break;
    }}
}}

inline void SetBinaryDataDir(RestrictedTraceID traceID, const char *dataDir)
{{
    switch (traceID)
    {{
{set_binary_data_dir_cases}
        default:
            fprintf(stderr, "Error in switch.\\n");
            assert(0);
            break;
    }}
}}

inline void SetBinaryDataDecompressCallback(RestrictedTraceID traceID, DecompressCallback callback)
{{
    switch (traceID)
    {{
{decompress_callback_cases}
        default:
            fprintf(stderr, "Error in switch.\\n");
            assert(0);
            break;
    }}
}}

inline void SetFramebufferChangeCallback(RestrictedTraceID traceID, void *userData, FramebufferChangeCallback callback)
{{
    switch (traceID)
    {{
{on_fb_change_callback_cases}
        default:
            fprintf(stderr, "Error in switch.\\n");
            assert(0);
            break;
    }}
}}
}}  // namespace angle

#endif  // ANGLE_RESTRICTED_TRACES_H_
"""


def reject_duplicate_keys(pairs):
    found_keys = {}
    for key, value in pairs:
        if key in found_keys:
            raise ValueError("duplicate key: %r" % (key,))
        else:
            found_keys[key] = value
    return found_keys


def gen_gni(traces, gni_file, format_args):
    format_args["test_list"] = ",\n".join(
        ['"%s %s"' % (trace, get_context(trace)) for trace in traces])
    gni_data = gni_template.format(**format_args)
    with open(gni_file, "w") as out_file:
        out_file.write(gni_data)
    return True


def get_trace_info(trace):
    info = ["%s::kReplayFrameStart", "%s::kReplayFrameEnd", "\"%s\""]
    return ", ".join([element % trace for element in info])


def get_context(trace):
    "Returns the context number used by trace header file"
    for file in os.listdir(trace):
        # Load up the only header present for each trace
        if fnmatch.fnmatch(file, '*.h'):
            # Strip the extension to isolate the context
            context = file[len(file) - 3]
            assert context.isdigit() == True, "Failed to find trace context number"
            assert file[len(file) - 4].isdigit() == False, "Context number is higher than 9"
            return context


def get_cases(traces, function, args):
    funcs = [
        "case RestrictedTraceID::%s: %s::%s(%s); break;" % (trace, trace, function, args)
        for trace in traces
    ]
    return "\n".join(funcs)


def get_header_name(trace):
    return "%s/%s_capture_context%s.h" % (trace, trace, get_context(trace))


def get_sha1_name(trace):
    return "%s.tar.gz.sha1" % trace


def get_cases_with_context(traces, function_start, function_end, args):
    funcs = [
        "case RestrictedTraceID::%s: %s::%s%s%s(%s); break;" %
        (trace, trace, function_start, get_context(trace), function_end, args) for trace in traces
    ]
    return "\n".join(funcs)


def gen_header(traces, header_file, format_args):

    includes = ["#include \"%s\"" % get_header_name(trace) for trace in traces]
    trace_infos = [
        "{RestrictedTraceID::%s, {%s}}" % (trace, get_trace_info(trace)) for trace in traces
    ]

    format_args["includes"] = "\n".join(includes)
    format_args["trace_ids"] = ",\n".join(traces)
    format_args["trace_infos"] = ",\n".join(trace_infos)
    format_args["replay_func_cases"] = get_cases_with_context(traces, "ReplayContext", "Frame",
                                                              "frameIndex")
    format_args["reset_func_cases"] = get_cases_with_context(traces, "ResetContext", "Replay", "")
    format_args["setup_func_cases"] = get_cases_with_context(traces, "SetupContext", "Replay", "")
    format_args["set_binary_data_dir_cases"] = get_cases(traces, "SetBinaryDataDir", "dataDir")
    format_args["decompress_callback_cases"] = get_cases(traces, "SetBinaryDataDecompressCallback",
                                                         "callback")
    format_args["on_fb_change_callback_cases"] = get_cases(traces, "SetFramebufferChangeCallback",
                                                           "userData, callback")
    header_data = header_template.format(**format_args)
    with open(header_file, "w") as out_file:
        out_file.write(header_data)
    return True


def read_json(json_file):
    with open(json_file) as map_file:
        return json.loads(map_file.read(), object_pairs_hook=reject_duplicate_keys)


def main():
    json_file = 'restricted_traces.json'
    gni_file = 'restricted_traces_autogen.gni'
    header_file = 'restricted_traces_autogen.h'

    json_data = read_json(json_file)
    if 'traces' not in json_data:
        print('Trace data missing traces key.')
        return 1
    traces = json_data['traces']

    # auto_script parameters.
    if len(sys.argv) > 1:
        inputs = [json_file] + [get_sha1_name(trace) for trace in traces]
        outputs = [gni_file, header_file]

        if sys.argv[1] == 'inputs':
            print ','.join(inputs)
        elif sys.argv[1] == 'outputs':
            print ','.join(outputs)
        else:
            print('Invalid script parameters.')
            return 1
        return 0

    format_args = {
        "script_name": __file__,
        "data_source_name": json_file,
    }

    if not gen_gni(traces, gni_file, format_args):
        print('.gni file generation failed.')
        return 1

    if not gen_header(traces, header_file, format_args):
        print('.h file generation failed.')
        return 1

    return 0


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