# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A tool for generating IDL files from a template, with distinct user and
system identities for interfaces that are decorated with `BEGIN_INTERFACE` and
`END_INTERFACE`.

`BEGIN_INTERFACE` takes the placeholder guid, the interface that needs distinct
identities, as well as any other items that need to be distinct for `user` and
`system` respectively.

`addToLibrary` specifies the interfaces that should be listed in the `library`
section of the generated IDL. For instance:
* `"addToLibrary": ["user", "system"]` will add the `IInterfaceUser` and
the `IInterfaceSystem` to the `library` section.
* `"addToLibrary": ["", "user", "system"]` will add `IInterface`,
`IInterfaceUser` and `IInterfaceSystem` to the `library` section.
* `"addToLibrary": ["user"]` will add only `IInterfaceUser` to the
`library` section.

`includeFor` is an optional qualifier that indicates what scope the interface
and the specializations should be included for, when used in conjunction with
the optional `--generate_for` command line option. `includeFor` is ignored if
no `--generate_for` command line option is specified. For instance:
* `"includeFor": ["system"]` will add the interface and the specializations only
when using the `--generate_for=system` command line option, but not when using
the `--generate_for=user` command line option.

Here is an example:

```
BEGIN_INTERFACE(
  {
    "uuid": {
      "user":"PLACEHOLDER-GUID-9AD1A645-5A4B-4D36-BC21-F0059482E6EA",
      "system":"PLACEHOLDER-GUID-E2BD9A6B-0A19-4C89-AE8B-B7E9E51D9A07"
    },
    "tokensToSuffix": ["ICompleteStatus"],
    "addToLibrary": ["", "user", "system"]
  }
)
[
  uuid(PLACEHOLDER-GUID-2FCD14AF-B645-4351-8359-E80A0E202A0B),
  oleautomation,
  pointer_default(unique)
]
interface ICompleteStatus : IUnknown {
  [propget] HRESULT statusCode([out, retval] LONG*);
  [propget] HRESULT statusMessage([out, retval] BSTR*);
};
END_INTERFACE

[
...
]
library UpdaterLib {
  INTERFACES_IN_LIBRARY;
};
```

The example input above will produce the following output in the IDL:

```
[
  uuid(PLACEHOLDER-GUID-2FCD14AF-B645-4351-8359-E80A0E202A0B),
  oleautomation,
  pointer_default(unique)
]
interface ICompleteStatus : IUnknown {
  [propget] HRESULT statusCode([out, retval] LONG*);
  [propget] HRESULT statusMessage([out, retval] BSTR*);
};
[
  uuid(PLACEHOLDER-GUID-9AD1A645-5A4B-4D36-BC21-F0059482E6EA),
  oleautomation,
  pointer_default(unique)
]
interface ICompleteStatusUser : IUnknown {
  [propget] HRESULT statusCode([out, retval] LONG*);
  [propget] HRESULT statusMessage([out, retval] BSTR*);
};
[
  uuid(PLACEHOLDER-GUID-E2BD9A6B-0A19-4C89-AE8B-B7E9E51D9A07),
  oleautomation,
  pointer_default(unique)
]
interface ICompleteStatusSystem : IUnknown {
  [propget] HRESULT statusCode([out, retval] LONG*);
  [propget] HRESULT statusMessage([out, retval] BSTR*);
};

[
...
]
library UpdaterLib {
  interface ICompleteStatus;
  interface ICompleteStatusUser;
  interface ICompleteStatusSystem;
};
```

Usage:
    python3 generate_user_system_idl.py --idl_template_file updater_idl.template
        --idl_output_file updater_idl_new.idl
"""

import argparse
import json
import re


def _GenerateIDLFile(idl_template_filename, idl_output_filename, generate_for):
    pattern = re.compile(
        r'''BEGIN_INTERFACE\(
            (.*?)    # Group for the replacement dictionary.
            \)
            (.*?)    # Group for interface text.
            END_INTERFACE''',
        re.DOTALL | re.X,
    )

    with open(idl_template_filename, 'rt') as f:
        matches = re.split(pattern, f.read())

        # Copy anything before the first 'BEGIN_INTERFACE' to output.
        idl_output = [matches[0]]
        interfaces_in_library = []

        for i in range(1, len(matches), 3):
            replacement_dict = json.loads(matches[i])
            interface_text = matches[i + 1]
            trailer = matches[i + 2]
            interface_base_name = re.search(
                r'interface (\w+) :', interface_text
            ).group(1)
            if (
                not generate_for
                or generate_for in replacement_dict.get('includeFor', {})
                or generate_for in replacement_dict['addToLibrary']
            ):
                idl_output.append(interface_text)
                if "" in replacement_dict['addToLibrary']:
                    interfaces_in_library.append(
                        "interface " + interface_base_name
                    )

                for scope, placeholder_guid in replacement_dict.get(
                    'uuid', {}
                ).items():
                    if generate_for and generate_for != scope:
                        continue
                    interfaces_in_library.append(
                        "interface " + interface_base_name + scope.title()
                    )
                    interface_gen = re.sub(
                        r'(uuid\().*?(\))',
                        r'\1%s\2' % placeholder_guid,
                        interface_text,
                    )
                    for k in replacement_dict['tokensToSuffix']:
                        interface_gen = re.sub(
                            r'\b%s\b' % k, k + scope.title(), interface_gen
                        )
                    idl_output.append(interface_gen)

            if trailer.strip():
                trailer = re.sub(
                    r'INTERFACES_IN_LIBRARY',
                    ';\n  '.join(interfaces_in_library),
                    trailer,
                )
                idl_output.append(trailer)

        with open(idl_output_filename, 'w') as f_out:
            f_out.write('// *** AUTO-GENERATED IDL FILE. ***\n\n')
            for item in idl_output:
                f_out.write(item)


def _Main():
    """Generates IDL files from a template for user and system marshaling."""
    cmd_parser = argparse.ArgumentParser(
        description='Tool to generate IDL from template.'
    )

    cmd_parser.add_argument(
        '--idl_template_file',
        dest='idl_template_file',
        type=str,
        required=True,
        help='Input IDL template file.',
    )
    cmd_parser.add_argument(
        '--idl_output_file', type=str, required=True, help='Output IDL file.'
    )
    cmd_parser.add_argument(
        '--generate_for',
        type=str,
        help='Generate the IDL for `user` or `system`.',
    )
    flags = cmd_parser.parse_args()

    _GenerateIDLFile(
        flags.idl_template_file, flags.idl_output_file, flags.generate_for
    )


if __name__ == '__main__':
    _Main()
