#!/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.
#

import angle_format
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 or angle_format['bits'] is None:
        return None
    if 'BLOCK' in angle_format['id']:
        return None
    bits = angle_format['bits']
    if 'D' in bits or 'S' 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['R'])
        if channel == 'g':
            struct_name += 'G{}'.format(bits['G'])
        if channel == 'b':
            struct_name += 'B{}'.format(bits['B'])
        if channel == 'a':
            struct_name += 'A{}'.format(bits['A'])
        if channel == 'l':
            struct_name += 'L{}'.format(bits['L'])
    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 or "BLOCK" in angle_format["id"]:
        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 get_component_type(format_id):
    if "SNORM" in format_id:
        return "snorm"
    elif "UNORM" in format_id:
        return "unorm"
    elif "FLOAT" in format_id:
        return "float"
    elif "UINT" in format_id:
        return "uint"
    elif "SINT" in format_id:
        return "int"
    elif format_id == "NONE":
        return "none"
    elif "SRGB" in format_id:
        return "unorm"
    else:
        raise ValueError("Unknown component type for " + format_id)

def get_channel_tokens(format_id):
    r = re.compile(r'([ABDGLRS][\d]+)')
    return filter(r.match, r.split(format_id))

def get_channels(format_id):
    channels = ''
    tokens = get_channel_tokens(format_id)
    if len(tokens) == 0:
        return None
    for token in tokens:
        channels += token[0].lower()

    return channels

def get_bits(format_id):
    bits = {}
    tokens = get_channel_tokens(format_id)
    if len(tokens) == 0:
        return None
    for token in tokens:
        bits[token[0]] = int(token[1:])
    return bits

def json_to_table_data(format_id, json, angle_to_gl):

    table_data = ""

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

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

    if "glInternalFormat" not in parsed:
        parsed["glInternalFormat"] = angle_to_gl[format_id]

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

    if "componentType" not in parsed:
        parsed["componentType"] = get_component_type(format_id)

    if "channels" not in parsed:
        parsed["channels"] = get_channels(format_id)

    if "bits" not in parsed:
        parsed["bits"] = get_bits(format_id)

    # 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(all_angle, json_data, angle_to_gl):
    table_data = ''
    for format_id in sorted(all_angle):
        format_info = json_data[format_id] if format_id in json_data else {}
        table_data += '        case ID::' + format_id + ':\n'
        table_data += json_to_table_data(format_id, format_info, angle_to_gl)

    return table_data

def gen_enum_string(all_angle):
    enum_data = '    NONE'
    for format_id in sorted(all_angle):
        if format_id == 'NONE':
            continue
        enum_data += ',\n    ' + format_id
    return enum_data

gl_to_angle = angle_format.load_forward_table('angle_format_map.json')
angle_to_gl = angle_format.load_inverse_table('angle_format_map.json')
json_data = angle_format.load_json('angle_format_data.json')
all_angle = angle_to_gl.keys()

angle_format_cases = parse_json_into_angle_format_switch_string(
    all_angle, json_data, angle_to_gl)
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 = gen_enum_string(all_angle)
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()
