#!/usr/bin/python
# Copyright 2016 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_angle_format_table.py:
#  Code generation for ANGLE format map.
#

from datetime import date
import json
import math
import pprint
import re
import sys

template_autogen_h = """// GENERATED FILE - DO NOT EDIT.
// Generated by gen_angle_format_table.py using data from angle_format_data.json
//
// 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.
//
// ANGLE format enumeration.

namespace angle
{{

enum class Format::ID
{{
{angle_format_enum}
}};

}}  // namespace angle
"""

template_autogen_cpp = """// GENERATED FILE - DO NOT EDIT.
// Generated by gen_angle_format_table.py using data from angle_format_data.json
//
// 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.
//
// ANGLE Format table:
//   Queries for typed format information from the ANGLE format enum.

#include "libANGLE/renderer/Format.h"

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

namespace angle
{{

// static
const Format &Format::Get(ID id)
{{
    // clang-format off
    switch (id)
    {{
{angle_format_info_cases}
        default:
            UNREACHABLE();
            break;
    }}
    // clang-format on

    static const Format noneInfo(ID::NONE, GL_NONE, GL_NONE, nullptr, nullptr);
    return noneInfo;
}}

}}  // namespace angle
"""

def get_channel_struct(angle_format):
    if 'bits' not in angle_format:
        return None
    bits = angle_format['bits']
    if 'depth' in bits or 'stencil' in bits:
        return None

    if 'channelStruct' in angle_format:
        return angle_format['channelStruct']

    struct_name = ''
    for channel in angle_format['channels']:
        if channel == 'r':
            struct_name += 'R{}'.format(bits['red'])
        if channel == 'g':
            struct_name += 'G{}'.format(bits['green'])
        if channel == 'b':
            struct_name += 'B{}'.format(bits['blue'])
        if channel == 'a':
            struct_name += 'A{}'.format(bits['alpha'])
        if channel == 'l':
            struct_name += 'L{}'.format(bits['luma'])
    if angle_format['componentType'] == 'float':
        struct_name += 'F'
    if angle_format['componentType'] == 'int' or angle_format['componentType'] == 'snorm':
        struct_name += 'S'
    return struct_name

def get_mip_generation_function(angle_format):
    channel_struct = get_channel_struct(angle_format)
    if channel_struct == None:
        return 'nullptr'
    return 'GenerateMip<' + channel_struct + '>'

def get_color_read_function(angle_format):
    channel_struct = get_channel_struct(angle_format)
    if channel_struct == None:
        return 'nullptr'
    component_type_map = {
        'uint': 'GLuint',
        'int': 'GLint',
        'unorm': 'GLfloat',
        'snorm': 'GLfloat',
        'float': 'GLfloat'
    }
    return 'ReadColor<' + channel_struct + ', '+ component_type_map[angle_format['componentType']] + '>'

format_entry_template = """{space}{{
{space}    static const Format info(ID::{id},
{space}                             {glInternalFormat},
{space}                             {fboImplementationInternalFormat},
{space}                             {mipGenerationFunction},
{space}                             {colorReadFunction});
{space}    return info;
{space}}}
"""

def json_to_table_data(format_id, json):

    table_data = ""

    parsed = {
        "space": "        ",
        "id": format_id,
        "glInternalFormat": "GL_NONE",
    }

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

    if (format_id != "NONE") and (parsed["glInternalFormat"] == "GL_NONE"):
        print("Missing 'glInternalFormat' from " + format_id)
        sys.exit(1)

    if "fboImplementationInternalFormat" not in parsed:
        parsed["fboImplementationInternalFormat"] = parsed["glInternalFormat"]

    # Derived values.
    parsed["mipGenerationFunction"] = get_mip_generation_function(parsed)
    parsed["colorReadFunction"] = get_color_read_function(parsed)

    return format_entry_template.format(**parsed)

def parse_json_into_angle_format_switch_string(json_data):
    table_data = ''
    for format_id, angle_format in sorted(json_data.iteritems()):
        table_data += '        case ID::' + format_id + ':\n'
        table_data += json_to_table_data(format_id, angle_format)

    return table_data

def parse_json_into_angle_format_enum_string(json_data):
    enum_data = '    NONE'
    for format_id, _ in sorted(json_data.iteritems()):
        if format_id == 'NONE':
            continue
        enum_data += ',\n    ' + format_id
    return enum_data

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

with open('angle_format_data.json') as format_json_file:
    format_data = format_json_file.read()
    format_json_file.close()
    json_data = json.loads(format_data, object_pairs_hook=reject_duplicate_keys)

    angle_format_cases = parse_json_into_angle_format_switch_string(json_data)
    output_cpp = template_autogen_cpp.format(
        copyright_year=date.today().year,
        angle_format_info_cases=angle_format_cases)
    with open('Format_autogen.cpp', 'wt') as out_file:
        out_file.write(output_cpp)
        out_file.close()

    enum_data = parse_json_into_angle_format_enum_string(json_data)
    output_h = template_autogen_h.format(
        copyright_year=date.today().year,
        angle_format_enum=enum_data)
    with open('Format_ID_autogen.inl', 'wt') as out_file:
        out_file.write(output_h)
        out_file.close()
