#!/usr/bin/python3
# Copyright 2015 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_texture_format_table.py:
#  Code generation for texture format map
#  NOTE: don't run this script directly. Run scripts/run_code_generation.py.
#

import json
import math
import pprint
import os
import sys

sys.path.append('../..')
import angle_format

template_texture_format_table_autogen_cpp = """// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {data_source_name}
//
// Copyright 2015 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.
//
// texture_format_table:
//   Queries for full textureFormat information based in internalFormat
//

#include "libANGLE/renderer/d3d/d3d11/texture_format_table.h"

#include "image_util/copyimage.h"
#include "image_util/generatemip.h"
#include "image_util/loadimage.h"

#include "libANGLE/renderer/d3d/d3d11/formatutils11.h"
#include "libANGLE/renderer/d3d/d3d11/renderer11_utils.h"
#include "libANGLE/renderer/d3d/d3d11/texture_format_table_utils.h"

using namespace angle;

namespace rx
{{

namespace d3d11
{{

// static
const Format &Format::Get(GLenum internalFormat, const Renderer11DeviceCaps &deviceCaps)
{{
    // clang-format off
    switch (internalFormat)
    {{
{angle_format_info_cases}
        default:
            break;
    }}
    // clang-format on

    UNREACHABLE();
    static constexpr Format defaultInfo;
    return defaultInfo;
}}

}}  // namespace d3d11

}}  // namespace rx
"""


def get_swizzle_format_id(internal_format, angle_format):
    angle_format_id = angle_format["formatName"]
    if (internal_format == 'GL_NONE') or (angle_format_id == 'NONE'):
        return 'GL_NONE'

    elif 'swizzleFormat' in angle_format:
        # For some special formats like compressed formats that don't have a clearly defined number
        # of bits per channel, swizzle format needs to be specified manually.
        return angle_format['swizzleFormat']

    if 'bits' not in angle_format:
        raise ValueError('no bits information for determining swizzleformat for format: ' +
                         internal_format)

    bits = angle_format['bits']
    max_component_bits = max(bits.values())
    bits_iter = iter(list(sorted(bits.values())))
    channels_different = not all(
        [component_bits == next(bits_iter) for component_bits in bits.values()])

    # The format itself can be used for swizzles if it can be accessed as a render target and
    # sampled and the bit count for all 4 channels is the same.
    if "rtvFormat" in angle_format and "srvFormat" in angle_format and "uavFormat" in angle_format and not channels_different and len(
            angle_format['channels']) == 4:
        return angle_format[
            "glInternalFormat"] if "glInternalFormat" in angle_format else internal_format

    b = int(math.ceil(float(max_component_bits) / 8) * 8)

    # Depth formats need special handling, since combined depth/stencil formats don't have a clearly
    # defined component type.
    if angle_format['channels'].find('d') >= 0:
        if b == 24 or b == 32:
            return 'GL_RGBA32F'
        if b == 16:
            return 'GL_RGBA16_EXT'

    if b == 24:
        raise ValueError('unexpected 24-bit format when determining swizzleformat for format: ' +
                         internal_format)

    if 'componentType' not in angle_format:
        raise ValueError('no component type information for determining swizzleformat for format: '
                         + internal_format)

    component_type = angle_format['componentType']

    swizzle = "GL_RGBA" + str(b)

    if component_type == 'uint':
        swizzle += "I"
    elif component_type == 'int':
        swizzle += "I"
    elif component_type == 'unorm':
        if (b == 16):
            swizzle += "_EXT"
    elif component_type == 'snorm':
        swizzle += "_SNORM"
        if (b == 16):
            swizzle += "_EXT"
    elif component_type == 'float':
        swizzle += "F"
        if (b == 16):
            swizzle += "_EXT"
    else:
        raise ValueError('could not determine swizzleformat based on componentType for format: ' +
                         internal_format)

    return swizzle


def get_blit_srv_format(angle_format):
    if 'channels' not in angle_format:
        return 'DXGI_FORMAT_UNKNOWN'
    if 'r' in angle_format['channels'] and angle_format['componentType'] in ['int', 'uint']:
        return angle_format['rtvFormat']

    return angle_format["srvFormat"] if "srvFormat" in angle_format else "DXGI_FORMAT_UNKNOWN"


format_entry_template = """{space}{{
{space}    static constexpr Format info({internalFormat},
{space}                                 angle::FormatID::{formatName},
{space}                                 {texFormat},
{space}                                 {srvFormat},
{space}                                 {uavFormat},
{space}                                 {rtvFormat},
{space}                                 {dsvFormat},
{space}                                 {blitSRVFormat},
{space}                                 {swizzleFormat},
{space}                                 {initializer});
{space}    return info;
{space}}}
"""

split_format_entry_template = """{space}    {condition}
{space}    {{
{space}        static constexpr Format info({internalFormat},
{space}                                     angle::FormatID::{formatName},
{space}                                     {texFormat},
{space}                                     {srvFormat},
{space}                                     {uavFormat},
{space}                                     {rtvFormat},
{space}                                     {dsvFormat},
{space}                                     {blitSRVFormat},
{space}                                     {swizzleFormat},
{space}                                     {initializer});
{space}        return info;
{space}    }}
"""


def json_to_table_data(internal_format, format_name, prefix, json):

    table_data = ""

    parsed = {
        "space": "        ",
        "internalFormat": internal_format,
        "formatName": format_name,
        "texFormat": "DXGI_FORMAT_UNKNOWN",
        "srvFormat": "DXGI_FORMAT_UNKNOWN",
        "uavFormat": "DXGI_FORMAT_UNKNOWN",
        "rtvFormat": "DXGI_FORMAT_UNKNOWN",
        "dsvFormat": "DXGI_FORMAT_UNKNOWN",
        "condition": prefix,
    }

    for k, v in sorted(json.items()):
        parsed[k] = v

    # Derived values.
    parsed["blitSRVFormat"] = get_blit_srv_format(parsed)
    parsed["swizzleFormat"] = get_swizzle_format_id(internal_format, parsed)
    parsed["initializer"] = angle_format.get_internal_format_initializer(
        internal_format, parsed["formatName"])

    if len(prefix) > 0:
        return split_format_entry_template.format(**parsed)
    else:
        return format_entry_template.format(**parsed)


def parse_json_angle_format_case(format_name, angle_format, json_data):
    supported_case = {}
    unsupported_case = {}
    support_test = None
    fallback = None

    for k, v in sorted(angle_format.items()):
        if k == "FL10Plus":
            assert support_test is None
            support_test = "OnlyFL10Plus(deviceCaps)"
            for k2, v2 in sorted(v.items()):
                supported_case[k2] = v2
        elif k == "FL9_3":
            split = True
            for k2, v2 in sorted(v.items()):
                unsupported_case[k2] = v2
        elif k == "supportTest":
            assert support_test is None
            support_test = v
        elif k == "fallbackFormat":
            fallback = v
        else:
            supported_case[k] = v
            unsupported_case[k] = v

    if fallback != None:
        unsupported_case, _, _ = parse_json_angle_format_case(fallback, json_data[fallback],
                                                              json_data)
        unsupported_case["formatName"] = fallback

    if support_test != None:
        return supported_case, unsupported_case, support_test
    else:
        return supported_case, None, None


def parse_json_into_switch_angle_format_string(json_map, json_data):
    table_data = ''

    for internal_format, format_name in sorted(json_map.items()):

        if format_name not in json_data:
            continue

        # Typeless internal formats are fake formats just used to fit support
        # for typeless D3D textures into the format system. Their properties
        # should not be queried.
        if 'TYPELESS' in internal_format:
            continue

        angle_format = json_data[format_name]

        supported_case, unsupported_case, support_test = parse_json_angle_format_case(
            format_name, angle_format, json_data)

        table_data += '        case ' + internal_format + ':\n'

        if support_test != None:
            table_data += "        {\n"
            table_data += json_to_table_data(internal_format, format_name,
                                             "if (" + support_test + ")", supported_case)
            table_data += json_to_table_data(internal_format, format_name, "else",
                                             unsupported_case)
            table_data += "        }\n"
        else:
            table_data += json_to_table_data(internal_format, format_name, "", supported_case)

    return table_data


def main():

    # auto_script parameters.
    if len(sys.argv) > 1:
        inputs = ['../../angle_format.py', 'texture_format_data.json', 'texture_format_map.json']
        outputs = ['texture_format_table_autogen.cpp']

        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

    json_map = angle_format.load_with_override(os.path.abspath('texture_format_map.json'))
    data_source_name = 'texture_format_data.json'
    json_data = angle_format.load_json(data_source_name)

    angle_format_cases = parse_json_into_switch_angle_format_string(json_map, json_data)
    output_cpp = template_texture_format_table_autogen_cpp.format(
        script_name=sys.argv[0],
        angle_format_info_cases=angle_format_cases,
        data_source_name=data_source_name)
    with open('texture_format_table_autogen.cpp', 'wt') as out_file:
        out_file.write(output_cpp)
        out_file.close()
    return 0


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