#!/usr/bin/env python3
#
# xdt-gen-visibility -- generates GNU visibility aliases and attributes
#
# Copyright (C) 2024 Brian Tarricone <brian@tarricone.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

from argparse import ArgumentParser
import sys
from typing import Any, Dict, List, Optional


def read_symbols_file(input_filename: Optional[str]) -> Dict[str, Any]:
    if input_filename is not None:
        inp = open(input_filename, 'r')
    else:
        inp = sys.stdin

    files = {}
    cur_symbols = None
    for line in inp:
        line = line.strip()

        if line.startswith('# file:'):
            cur_symbols = []
            files[line[7:]] = cur_symbols
            continue
        elif line == '' or line.startswith('#'):
            continue

        symbol = {}

        if line.startswith('var:'):
            symbol['type'] = 'var'
            line = line[4:]
        else:
            symbol['type'] = 'func'

        parts = line.split()
        symbol['name'] = parts[0]

        symbol['attrs'] = []
        for extra in parts[1:]:
            if extra.startswith('attr:'):
                symbol['attrs'].append(extra[5:])

        cur_symbols.append(symbol)

    if input_filename is not None:
        inp.close()

    return files


def build_attrs_str(attrs: List[str]) -> str:
    attrs_str = ' '.join(symbol['attrs'])
    if attrs_str != '':
        attrs_str = ' ' + attrs_str
    return attrs_str


def header_decls_for_symbol(symbol: Dict[str, Any]) -> str:
    name = symbol['name']
    attrs_str = build_attrs_str(symbol['attrs'])

    s = f'extern __typeof({name}) IA__{name} ' \
        + f'__attribute__((visibility("hidden"))){attrs_str};\n'
    s += f'#define {name} IA__{name}\n'
    s += '\n'

    return s


def source_decls_for_symbol(symbol: Dict[str, Any]) -> str:
    name = symbol['name']
    attrs_str = build_attrs_str(symbol['attrs'])

    need_disable_warnings = symbol['type'] == 'var'

    s = f'#undef {name}\n'
    if need_disable_warnings:
        s += '#pragma GCC diagnostic push\n'
        s += '#pragma GCC diagnostic ignored "-Wredundant-decls"\n'
    s += f'extern __typeof({name}) {name} ' \
        + f'__attribute__((alias("IA__{name}"), ' \
        + f'visibility("default"))){attrs_str};\n'
    if need_disable_warnings:
        s += '#pragma GCC diagnostic pop\n'
    s += '\n'
    return s


parser = ArgumentParser(
    prog='xdt-gen-visibility',
    description='Generates GNU visibility headers and source'
)

parser.add_argument(
    '--kind',
    action='store',
    choices=['header', 'source'],
    required=True,
)
parser.add_argument(
    '--ifdef-guard-format',
    action='store',
    metavar='FMT',
    default='__{file_stem_upper_snake}_{file_type_upper}__',
    help='String format for #ifndef file guards. Possible replacements are '
            + '{file_stem_upper_snake}, '
            + '{file_stem_lower_snake}, '
            + '{file_type_upper}, '
            + '{file_type_lower}'
)
parser.add_argument('input', nargs='?', metavar='INPUT')
parser.add_argument('output', nargs='?', metavar='OUTPUT')

args = parser.parse_args()

if args.kind == 'source':
    deftype = 'c'
elif args.kind == 'header':
    deftype = 'h'
else:
    raise f'Invalid --kind "{args.kind}"'

files = read_symbols_file(args.input)

output_s = """/*
 * This file was generated with xdt-gen-visibility.  Do not edit it directly.
 */

#include <glib.h>

#if defined(ENABLE_SYMBOL_VISIBILITY) \
    && defined(__GNUC__) \
    && defined(G_HAVE_GNUC_VISIBILITY)

G_GNUC_BEGIN_IGNORE_DEPRECATIONS

"""

for filename, symbols in files.items():
    guard_format_params = {
        'file_stem_upper_snake': filename.replace('-', '_').upper(),
        'file_stem_lower_snake': filename.replace('-', '_'),
        'file_type_upper': deftype.upper(),
        'file_type_lower': deftype,
    }
    ifdef_guard = args.ifdef_guard_format.format(**guard_format_params)

    output_s += f'#ifdef {ifdef_guard}\n'
    output_s += '\n'

    for symbol in symbols:
        if args.kind == 'header':
            output_s += header_decls_for_symbol(symbol)
        elif args.kind == 'source':
            output_s += source_decls_for_symbol(symbol)

    output_s += f'#endif /* {ifdef_guard} */\n'
    output_s += '\n'
    output_s += '\n'

output_s += """G_GNUC_END_IGNORE_DEPRECATIONS

#endif /* ENABLE_SYMBOL_VISIBILITY && __GNUC__ && G_HAVE_GNUC_VISIBILITY */
"""

if args.output is not None:
    with open(args.output, 'w') as outp:
        outp.write(output_s)
else:
    print(output_s, end='')
