# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Invokes gperf for the GN build.
# Usage: gperf.py gperf ...

import argparse
import os
import re
import subprocess
import sys
import template_expander


def generate_gperf(gperf_path, gperf_input, gperf_args):
    # FIXME: If we could depend on Python 3.4, we would use
    # subprocess.check_output

    # If gperf isn't in the path we get an OSError. We don't want to use
    # the normal solution of shell=True (as this has to run on many
    # platforms), so instead we catch the error and raise a
    # CalledProcessError like subprocess would do when shell=True is set.
    cmd = [gperf_path] + gperf_args
    try:
        gperf = subprocess.Popen(
            cmd,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            universal_newlines=True)
        gperf_output = gperf.communicate(gperf_input)[0]
        # Massage gperf output to be more palatable for modern compilers.
        # TODO(thakis): Upstream these to gperf so we don't need massaging.
        # `register` is deprecated in C++11 and removed in C++17, so remove
        # it from gperf's output.
        # https://savannah.gnu.org/bugs/index.php?53028
        gperf_output = re.sub(r'\bregister ', '', gperf_output)
        # -Wimplicit-fallthrough needs an explicit fallthrough statement,
        # so replace gperf's /*FALLTHROUGH*/ comment with the statement.
        # https://savannah.gnu.org/bugs/index.php?53029
        gperf_output = gperf_output.replace('/*FALLTHROUGH*/',
                                            '  [[fallthrough]];')
        # -Wpointer-to-int-cast warns about casting pointers to smaller ints
        # Replace {(int)(long)&(foo), bar} with
        # {static_cast<int>(reinterpret_cast<uintptr_t>(&(foo)), bar}
        gperf_output = re.sub(
            r'\(int\)\(long\)(.*?),',
            r'static_cast<int>(reinterpret_cast<uintptr_t>(\1)),',
            gperf_output)
        script = 'third_party/blink/renderer/build/scripts/gperf.py'
        return '// Generated by %s\n' % script + gperf_output
    except OSError:
        raise subprocess.CalledProcessError(
            127, cmd, output='Command not found.')


def use_jinja_gperf_template(template_path, gperf_extra_args=None):
    def wrapper(generator):
        def generator_internal(*args, **kwargs):
            parameters = generator(*args, **kwargs)
            assert 'gperf_path' in parameters, 'Must specify gperf_path in ' \
                'template map returned from decorated function'
            gperf_path = parameters['gperf_path']
            gperf_input = template_expander.apply_template(
                template_path, parameters)
            gperf_args = ['--key-positions=*', '-P', '-n']
            gperf_args.extend(['-m', '50'])  # Pick best of 50 attempts.
            # Allow duplicate hashes -> More compact code.
            gperf_args.append('-D')
            if gperf_extra_args:
                gperf_args.extend(gperf_extra_args)
            return generate_gperf(gperf_path, gperf_input, gperf_args)

        generator_internal.__name__ = generator.__name__
        return generator_internal

    return wrapper


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--output-file")
    args, unknownargs = parser.parse_known_args()

    gperf_path, gperf_args = unknownargs[0], unknownargs[1:]
    infile = None
    for arg in gperf_args:
        if os.path.isfile(arg):
            assert infile is None, 'duplicate inputs? %s, %s' % (infile, arg)
            infile = arg
    assert infile is not None, 'no input found'

    # Since we're passing the input file on stdin, remove it from the args.
    gperf_args.remove(infile)

    open(args.output_file, 'wb').write(
        generate_gperf(gperf_path,
                       open(infile).read(), gperf_args).encode('utf-8'))


if __name__ == '__main__':
    main()
