#!/usr/bin/python
# Copyright 2019 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_mtl_internal_shaders.py:
#   Code generation for Metal backend's default shaders.
#   NOTE: don't run this script directly. Run scripts/run_code_generation.py.

import os
import sys
import json
from datetime import datetime

template_header_boilerplate = """// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name}
//
// Copyright {copyright_year} 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.
//
"""


# Convert content of a file to byte array and store in a header file.
# variable_name: name of C++ variable that will hold the file content as byte array.
# filename: the file whose content will be converted to C++ byte array.
# dest_src_file: destination header file that will contain the byte array.
def append_file_as_byte_array_string(variable_name, filename, dest_src_file):
    string = '// Generated from {0}:\n'.format(filename)
    string += 'constexpr\nunsigned char {0}[] = {{\n'.format(variable_name)
    bytes_ = open(filename, "rb").read()
    byteCounter = 0
    for byte in bytes_:
        if byteCounter == 0:
            string += "  "
        string += '0x{:02x}'.format(ord(byte)) + ","
        byteCounter += 1
        if byteCounter == 12:
            byteCounter = 0
            string += "\n"
        else:
            string += " "

    string += "\n};\n"
    with open(dest_src_file, "a") as out_file:
        out_file.write(string)


# Compile metal shader.
# mac_version: target version of macOS
# ios_version: target version of iOS
# variable_name: name of C++ variable that will hold the compiled binary data as a C array.
# additional_flags: additional shader compiler flags
# src_files: metal source files
def gen_precompiled_shaders(mac_version, ios_version, variable_name, additional_flags, src_files):
    print('Generating default shaders with flags=\'{0}\' ...'.format(additional_flags))

    # Mac version's compilation
    print('Compiling macos {0} version of default shaders ...'.format(mac_version))
    object_files = ''
    for src_file in src_files:
        object_file = 'compiled/default.{0}.{1}.air'.format(mac_version, src_file)
        object_files += ' ' + object_file
        os.system('xcrun -sdk macosx metal -mmacosx-version-min={0} {1} {2} -c -o {3}'.format(
            mac_version, additional_flags, src_file, object_file))
    os.system(
        'xcrun -sdk macosx metallib {object_files} -o compiled/default.{mac_version}.metallib'
        .format(mac_version=mac_version, object_files=object_files))

    # iOS device version's compilation
    print('Compiling ios {0} version of default shaders ...'.format(ios_version))
    object_files = ''
    for src_file in src_files:
        object_file = 'compiled/default.ios.{0}.{1}.air'.format(ios_version, src_file)
        object_files += ' ' + object_file
        os.system('xcrun -sdk iphoneos metal -mios-version-min={0} {1} {2} -c -o {3}'.format(
            ios_version, additional_flags, src_file, object_file))
    os.system(
        'xcrun -sdk iphoneos metallib {object_files} -o compiled/default.ios.{ios_version}.metallib'
        .format(ios_version=ios_version, object_files=object_files))

    # iOS simulator version's compilation
    print('Compiling ios {0} simulator version of default shaders ...'.format(ios_version))
    object_files = ''
    object_files = ''
    for src_file in src_files:
        object_file = 'compiled/default.ios_sim.{0}.{1}.air'.format(ios_version, src_file)
        object_files += ' ' + object_file
        os.system('xcrun -sdk iphonesimulator metal {0} {1} -c -o {2}'.format(
            additional_flags, src_file, object_file))
    os.system(
        'xcrun -sdk iphonesimulator metallib {object_files} -o compiled/default.ios_sim.{ios_version}.metallib'
        .format(ios_version=ios_version, object_files=object_files))

    # Mac version's byte array string
    os.system(
        'echo "#if TARGET_OS_OSX || TARGET_OS_MACCATALYST\n" >> compiled/mtl_default_shaders_autogen.inc'
    )
    append_file_as_byte_array_string(variable_name,
                                     'compiled/default.{0}.metallib'.format(mac_version),
                                     'compiled/mtl_default_shaders_autogen.inc')
    os.system(
        'echo "constexpr\nsize_t {0}_len = sizeof({0});" >> compiled/mtl_default_shaders_autogen.inc'
        .format(variable_name))

    # iOS simulator version's byte array string
    os.system(
        'echo "\n#elif TARGET_OS_IOS && TARGET_OS_SIMULATOR  // TARGET_OS_OSX || TARGET_OS_MACCATALYST\n" >> compiled/mtl_default_shaders_autogen.inc'
    )

    append_file_as_byte_array_string(variable_name,
                                     'compiled/default.ios_sim.{0}.metallib'.format(ios_version),
                                     'compiled/mtl_default_shaders_autogen.inc')
    os.system(
        'echo "constexpr\nsize_t {0}_len = sizeof({0});" >> compiled/mtl_default_shaders_autogen.inc'
        .format(variable_name))

    # iOS version's byte array string
    os.system(
        'echo "\n#elif TARGET_OS_IOS  // TARGET_OS_OSX || TARGET_OS_MACCATALYST\n" >> compiled/mtl_default_shaders_autogen.inc'
    )

    append_file_as_byte_array_string(variable_name,
                                     'compiled/default.ios.{0}.metallib'.format(ios_version),
                                     'compiled/mtl_default_shaders_autogen.inc')
    os.system(
        'echo "constexpr\nsize_t {0}_len = sizeof({0});" >> compiled/mtl_default_shaders_autogen.inc'
        .format(variable_name))

    os.system(
        'echo "#endif  // TARGET_OS_OSX || TARGET_OS_MACCATALYST\n" >> compiled/mtl_default_shaders_autogen.inc'
    )

    os.system('rm -rfv compiled/default.*')


def main():
    src_files = ['blit.metal', 'clear.metal', 'gen_indices.metal']
    # auto_script parameters.
    if len(sys.argv) > 1:
        inputs = src_files + ['common.h', 'constants.h']
        outputs = ['compiled/mtl_default_shaders_autogen.inc']

        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

    os.chdir(sys.path[0])

    boilerplate_code = template_header_boilerplate.format(
        script_name=sys.argv[0], copyright_year=datetime.today().year)

    # -------- Compile shaders -----------
    # boilerplate code
    os.system("echo \"{0}\" > compiled/mtl_default_shaders_autogen.inc".format(boilerplate_code))
    os.system(
        'echo "// Compiled binary for Metal default shaders.\n\n" >> compiled/mtl_default_shaders_autogen.inc'
    )
    os.system(
        'echo "#include <TargetConditionals.h>\n\n" >> compiled/mtl_default_shaders_autogen.inc')

    os.system('echo "// clang-format off" >> compiled/mtl_default_shaders_autogen.inc')

    # pre-compiled shaders
    gen_precompiled_shaders(10.13, 11.0, 'compiled_default_metallib', '', src_files)
    gen_precompiled_shaders(10.13, 11.0, 'compiled_default_metallib_debug',
                            '-gline-tables-only -MO', src_files)

    os.system('echo "// clang-format on" >> compiled/mtl_default_shaders_autogen.inc')


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