#!/usr/bin/env python3
#
# Copyright (C) 2024-2025 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import copy
import os
import re
import sys

_header_license = """/*
 * Copyright (C) 2025 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
"""


class SyncedData(object):
    def __init__(self, name, underlying_type_namespace, underlying_type, options):
        self.conditional = None
        self.headers = None
        self.variant_index = None

        if options is not None:
            option_list = options.split()
            for option in option_list:
                if option.startswith('Conditional='):
                    self.conditional = option[12:]
                elif option.startswith('Headers='):
                    self.headers = option[8:].split(",")
                else:
                    raise Exception("Invalid option argument '%s' found" % option)

        self.name = name
        self.underlying_type_namespace = underlying_type_namespace
        self.underlying_type = underlying_type
        if underlying_type_namespace is None:
            self.fully_qualified_type = underlying_type
        else:
            self.fully_qualified_type = underlying_type_namespace + '::' + underlying_type


def headers_from_datas(datas):
    header_list = []
    for data in datas:
        if data.headers is None:
            continue
        header_list.extend(data.headers)
    return header_list


def parse_process_sync_data(file):
    synched_datas = []
    headers = []
    for line in file:
        line = line.strip()

        # Skip comments
        if line.startswith('#'):
            continue

        match = re.search(r'(.*) : (.*)::(.*) \[(.*)\]', line)
        if match:
            synched_datas.append(SyncedData(match.groups()[0], match.groups()[1], match.groups()[2], match.groups()[3]))
            continue

        match = re.search(r'(.*) : (.*)::(.*)', line)
        if match:
            synched_datas.append(SyncedData(match.groups()[0], match.groups()[1], match.groups()[2], None))
            continue

        match = re.search(r'(.*) : (.*) \[(.*)\]', line)
        if match:
            synched_datas.append(SyncedData(match.groups()[0], None, match.groups()[1], match.groups()[2]))
            continue

        match = re.search(r'(.*) : (.*)', line)
        if match:
            synched_datas.append(SyncedData(match.groups()[0], None, match.groups()[1], None))
            continue

    return synched_datas


_process_sync_client_header_prefix = """
namespace WebCore {{

class {prefix}SyncData;
struct {prefix}SyncSerializationData;

class {prefix}SyncClient {{
    WTF_MAKE_TZONE_ALLOCATED_INLINE({prefix}SyncClient);

public:
    {prefix}SyncClient() = default;
    virtual ~{prefix}SyncClient() = default;

    virtual void broadcastAll{prefix}SyncDataToOtherProcesses({prefix}SyncData&) {{ }}
"""

_process_sync_client_header_suffix = """
protected:
    virtual void broadcast{prefix}SyncDataToOtherProcesses(const {prefix}SyncSerializationData&) {{ }}
}};

}} // namespace WebCore
"""


def generate_process_sync_client_header(prefix, synched_datas):
    result = []
    result.append(_header_license)
    result.append('#pragma once\n')

    headers = headers_from_datas(synched_datas)
    headers.append('<wtf/TZoneMallocInlines.h>')
    for header in headers:
        result.append('#include %s' % header)

    result.append(_process_sync_client_header_prefix.format(prefix=prefix))

    for data in synched_datas:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)
        result.append('    WEBCORE_EXPORT void broadcast%sToOtherProcesses(const %s&);' % (data.name, data.fully_qualified_type))
        if data.conditional is not None:
            result.append('#endif')

    result.append(_process_sync_client_header_suffix.format(prefix=prefix))
    return '\n'.join(result)


_process_sync_client_impl_prefix = """
#include "config.h"
#include "{prefix}SyncClient.h"

#include "{prefix}SyncData.h"
#include <wtf/EnumTraits.h>

namespace WebCore {{
"""


def generate_process_sync_client_impl(prefix, synched_datas):
    result = []
    result.append(_header_license)
    result.append(_process_sync_client_impl_prefix.format(prefix=prefix))

    for data in synched_datas:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)
        result.append('void %sSyncClient::broadcast%sToOtherProcesses(const %s& data)' % (prefix, data.name, data.fully_qualified_type))
        result.append('{')
        result.append('    broadcast%sSyncDataToOtherProcesses({ %sSyncDataVariant { WTF::InPlaceIndex<std::to_underlying(%sSyncDataType::%s)>, data } });' % (prefix, prefix, prefix, data.name))

        result.append('}')
        if data.conditional is not None:
            result.append('#endif')

    result.append('\n} // namespace WebCore\n')
    return '\n'.join(result)


_process_sync_data_header_suffix = """
struct {prefix}SyncSerializationData {{
    {prefix}SyncDataVariant value;
}};

"""


def generate_process_sync_data_header(prefix, variant_sorted_synched_datas, sync_data_sorted_synched_datas):
    result = []

    result.append("enum class %sSyncDataType : uint8_t {" % prefix)
    for data in variant_sorted_synched_datas:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)
        result.append('    %s = %s,' % (data.name, data.variant_index))
        if data.conditional is not None:
            result.append('#endif')

    result.append("};")
    result.append("")

    result.append("static const %sSyncDataType all%sSyncDataTypes[] = {" % (prefix, prefix))
    data = sync_data_sorted_synched_datas[0]
    if data.conditional is not None:
        result.append('#if %s' % data.conditional)
    result.append('    %sSyncDataType::%s' % (prefix, data.name))
    if data.conditional is not None:
        result.append('#endif')

    for data in sync_data_sorted_synched_datas[1:]:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)
        result.append('    , %sSyncDataType::%s' % (prefix, data.name))
        if data.conditional is not None:
            result.append('#endif')
    result.append("};")
    result.append("")

    for data in variant_sorted_synched_datas:
        if data.conditional is not None:
            result.append('#if !%s' % data.conditional)
            result.append('using %s = bool;' % data.underlying_type)
            result.append('#endif')

    result.append("")
    result.append("using %sSyncDataVariant = Variant<" % prefix)
    for data in variant_sorted_synched_datas[:-1]:
        result.append('    %s,' % data.fully_qualified_type)

    data = variant_sorted_synched_datas[-1]
    result.append('    %s' % data.fully_qualified_type)

    result.append(">;")
    result.append(_process_sync_data_header_suffix.format(prefix=prefix))
    return '\n'.join(result)


_synced_data_header_midfix = """
namespace WebCore {{

struct {prefix}SyncSerializationData;

class data_type_name : public RefCounted<data_type_name> {{
WTF_MAKE_TZONE_ALLOCATED_INLINE(data_type_name);
public:
    template<typename... Args>
    static Ref<data_type_name> create(Args&&... args)
    {{
        return adoptRef(*new data_type_name(std::forward<Args>(args)...));
    }}
    static Ref<data_type_name> create() {{ return adoptRef(*new data_type_name); }}
    WEBCORE_EXPORT void update(const {prefix}SyncSerializationData&);
"""


def generate_synched_data_header(prefix, variant_sorted_synched_datas, sync_data_sorted_synched_datas):
    result = []
    result.append(_header_license)
    result.append('#pragma once\n')

    data_type_name = prefix + 'SyncData'

    headers = []
    headers.append('<wtf/TZoneMallocInlines.h>')
    headers.append('<wtf/Ref.h>')
    headers.append('<wtf/RefCounted.h>')
    for data in sync_data_sorted_synched_datas:
        if data.headers is None:
            continue
        headers.extend(data.headers)

    for header in headers:
        result.append('#include %s' % header)

    result.append(_synced_data_header_midfix.format(prefix=prefix))

    for data in sync_data_sorted_synched_datas:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)
        name = data.name[0].lower() + data.name[1:]
        result.append('    %s %s { };' % (data.fully_qualified_type, name))
        if data.conditional is not None:
            result.append('#endif')

    result.append('')
    result.append('private:')
    result.append('    data_type_name() = default;')
    result.append('    WEBCORE_EXPORT data_type_name(')

    data = sync_data_sorted_synched_datas[0]
    if data.conditional is not None:
        result.append('#if %s' % data.conditional)
    result.append('        %s' % data.fully_qualified_type)
    if data.conditional is not None:
        result.append('#endif')

    for data in sync_data_sorted_synched_datas[1:]:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)
        result.append('      , %s' % data.fully_qualified_type)
        if data.conditional is not None:
            result.append('#endif')

    result.append('    );')
    result.append('};')
    result.append('')

    result.append(generate_process_sync_data_header(prefix, variant_sorted_synched_datas, sync_data_sorted_synched_datas))

    result.append('} // namespace WebCore')
    result.append('')

    return '\n'.join(result).replace('data_type_name', data_type_name)


_synched_data_impl_prefix = """
#include "config.h"
#include "data_type_name.h"

#include <wtf/EnumTraits.h>

namespace WebCore {{

void data_type_name::update(const {prefix}SyncSerializationData& data)
{{
    switch (static_cast<{prefix}SyncDataType>(data.value.index())) {{"""

_synched_data_impl_midfix = """    default:
        RELEASE_ASSERT_NOT_REACHED();
    }
}
"""


def generate_synched_data_impl(prefix, synched_datas):
    result = []
    result.append(_header_license)
    result.append(_synched_data_impl_prefix.format(prefix=prefix))

    data_type_name = prefix + 'SyncData'

    for data in synched_datas:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)

        lowercase_name = data.name[0].lower() + data.name[1:]
        result.append('    case %sSyncDataType::%s:' % (prefix, data.name))
        result.append('        %s = std::get<std::to_underlying(%sSyncDataType::%s)>(data.value);' % (lowercase_name, prefix, data.name))
        result.append('        break;')

        if data.conditional is not None:
            result.append('#endif')

    result.append(_synched_data_impl_midfix)

    result.append('data_type_name::data_type_name(')

    data = synched_datas[0]
    if data.conditional is not None:
        raise Exception("First argument to constructor cannot be conditional")

    lowercase_name = data.name[0].lower() + data.name[1:]
    result.append('      %s %s' % (data.fully_qualified_type, lowercase_name))

    for data in synched_datas[1:]:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)

        lowercase_name = data.name[0].lower() + data.name[1:]
        result.append('    , %s %s' % (data.fully_qualified_type, lowercase_name))

        if data.conditional is not None:
            result.append('#endif')

    result.append(')')

    data = synched_datas[0]
    if data.conditional is not None:
        raise Exception("First argument to constructor cannot be conditional")

    lowercase_name = data.name[0].lower() + data.name[1:]
    result.append('    : %s(%s)' % (lowercase_name, lowercase_name))

    for data in synched_datas[1:]:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)

        lowercase_name = data.name[0].lower() + data.name[1:]
        result.append('    , %s(%s)' % (lowercase_name, lowercase_name))

        if data.conditional is not None:
            result.append('#endif')

    result.append('{')
    result.append('}')
    result.append('')
    result.append('} // namespace WebCore')
    result.append('')

    return '\n'.join(result).replace('data_type_name', data_type_name)


_serialization_in_license = """#
# Copyright (C) 2025 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
"""
_process_sync_data_serialization_in_prefix = """
header: <WebCore/{prefix}SyncData.h>
"""

_process_sync_data_serialization_in_suffix = """
[CustomHeader] struct WebCore::{prefix}SyncSerializationData {{
    WebCore::{prefix}SyncDataVariant value;
}};
"""


def generate_process_sync_data_serialiation_in(prefix, variant_sorted_synched_datas, sync_data_sorted_synched_datas):
    result = []
    result.append(_serialization_in_license)
    result.append(_process_sync_data_serialization_in_prefix.format(prefix=prefix))

    result.append('[RefCounted] class WebCore::%sSyncData {' % (prefix))
    for data in sync_data_sorted_synched_datas:
        if data.conditional is not None:
            result.append('#if %s' % data.conditional)
        name = data.name[0].lower() + data.name[1:]
        result.append('    %s %s;' % (data.fully_qualified_type, name))
        if data.conditional is not None:
            result.append('#endif')

    result.append('};')
    result.append('')

    for data in variant_sorted_synched_datas:
        if data.conditional is not None:
            result.append('#if !%s' % data.conditional)
            result.append('using %s = bool;' % data.fully_qualified_type)
            result.append('#endif')

    result.append("")
    variant_string = "using WebCore::%sSyncDataVariant = Variant<" % prefix
    for data in variant_sorted_synched_datas[:-1]:
        variant_string += data.fully_qualified_type + ', '
    variant_string += variant_sorted_synched_datas[-1].fully_qualified_type + '>;'
    result.append(variant_string)

    result.append(_process_sync_data_serialization_in_suffix.format(prefix=prefix))
    return '\n'.join(result)


def sort_data_lists(synched_datas):
    type_list = []
    conditional_type_list = []

    for data in synched_datas:
        if data.conditional is None:
            type_list.append(data)
        else:
            conditional_type_list.append(data)

    return type_list, conditional_type_list


def sort_datas_for_variant_order(synched_datas):
    type_list, conditional_type_list = sort_data_lists(synched_datas)

    if not type_list:
        raise Exception("Surprisingly, no unconditional types found (this will make it hard to construct the variant in a way that will compile)")

    full_list = conditional_type_list + type_list
    current_variant_index = 0
    for data in full_list:
        data.variant_index = current_variant_index
        current_variant_index += 1

    return full_list


def sort_datas_for_sync_data_order(synched_datas):
    type_list, conditional_type_list = sort_data_lists(synched_datas)

    # FIXME: We play tricks with our c++ native interface and implementation of DocumentSyncData to put commas at the start of the line and not the end,
    # allowing us to gracefully handle the case where the final member is conditional with proper syntax.
    # However, the serializer generator up at the WebKit layer does *not* play these tricks, and therefore if the final member is conditional and not defined for
    # the platform, the build fails.
    # We should fix the serializer generator to use leading commas instead of trailing commas then remove this weird construct of making sure the conditionals
    # are sandwiched between unconditionals.
    if len(type_list) > 1:
        full_list = type_list[:-1] + conditional_type_list + [type_list[-1]]
    else:
        full_list = type_list
    return full_list


def main(argv):
    synched_datas = []
    headers = []

    if len(argv) < 3:
        return -1

    output_directory = ''
    if len(argv) > 3:
        output_directory = argv[3] + '/'

    prefix = argv[1]

    with open(argv[2]) as file:
        new_synched_datas = parse_process_sync_data(file)
        for synched_data in new_synched_datas:
            synched_datas.append(synched_data)

    variant_sorted_synched_datas = sort_datas_for_variant_order(synched_datas)
    sync_data_sorted_synched_datas = sort_datas_for_sync_data_order(synched_datas)

    with open(output_directory + prefix + 'SyncClient.h', "w+") as output:
        output.write(generate_process_sync_client_header(prefix, variant_sorted_synched_datas))

    with open(output_directory + prefix + 'SyncClient.cpp', "w+") as output:
        output.write(generate_process_sync_client_impl(prefix, variant_sorted_synched_datas))

    sorted_synched_datas = sort_datas_for_sync_data_order(synched_datas)

    with open(output_directory + prefix + 'SyncData.serialization.in', "w+") as output:
        output.write(generate_process_sync_data_serialiation_in(prefix, variant_sorted_synched_datas, sync_data_sorted_synched_datas))

    with open(output_directory + prefix + 'SyncData.h', "w+") as output:
        output.write(generate_synched_data_header(prefix, variant_sorted_synched_datas, sync_data_sorted_synched_datas))

    with open(output_directory + prefix + 'SyncData.cpp', "w+") as output:
        output.write(generate_synched_data_impl(prefix, sync_data_sorted_synched_datas))

    return 0


if __name__ == '__main__':
    sys.exit(main(sys.argv))
