#!/usr/bin/env python3

# Copyright (c) 2023 Apple Inc. All rights reserved.
# Copyright (c) 2010-2014 Google 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:
# 
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * 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.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
# OWNER OR 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 csv
import os.path
import string
import sys

ENTITY = 0
VALUE = 1
OFFSET = 2

def convert_value_to_int(value):
    if not value:
        return 0;
    assert(value[0] == "U")
    assert(value[1] == "+")
    return int(value[2:], 16)

def offset_table_entry(offset):
    return "    &staticEntityTable[%s]," % offset

program_name = os.path.basename(__file__)
if len(sys.argv) < 4 or sys.argv[1] != "-o":
    print("Usage: %s -o OUTPUT_FILE INPUT_FILE" % program_name, file=sys.stderr)
    exit(1)

output_path = sys.argv[2]
input_path = sys.argv[3]

with open(input_path) as html_entity_names_file:
    entries = list(csv.reader(html_entity_names_file))

entries.sort(key = lambda entry: entry[ENTITY])
entity_count = len(entries)

output_file = open(output_path, "w")

output_file.write("""/*
 * Copyright (C) 2010-2014 Google, 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. ``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
 * 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. 
 */

// THIS FILE IS GENERATED BY WebCore/html/parser/create-html-entity-table
// DO NOT EDIT!

#include "config.h"
#include "HTMLEntityTable.h"

#include <array>
#include <wtf/text/ASCIILiteral.h>

namespace WebCore {

static constexpr auto staticEntityStringStorage = """)

assert len(entries) > 0, "Code assumes a non-empty entity array."
def check_ascii(entity_string):
    for c in entity_string:
        code = ord(c)
        assert 0 <= code <= 127, (c + " is not ASCII.")

all_data = ""
entity_offset = 0
saved_by_reusing = 0
for entry in entries:
    # Reuse substrings from earlier entries. This saves characters, but it's O(n^2).
    # The optimal solution has to solve the "Shortest Common Superstring" problem
    # and that is NP-Complete or worse.
    entity = entry[ENTITY]
    check_ascii(entity)
    if entity[-1] == ';':
        entity = entity[:-1]
    already_existing_offset = all_data.find(entity)
    if already_existing_offset != -1:
        # Reusing space.
        this_offset = already_existing_offset
        saved_by_reusing += len(entity)
    else:
        output_file.write("\n\"")

        # Try the end of the string and see if we can reuse that to
        # fit the start of the new entity.
        data_to_add = entity
        this_offset = entity_offset
        for truncated_len in range(len(entity) - 1, 0, -1):
            if all_data.endswith(entity[:truncated_len]):
                data_to_add = entity[truncated_len:]
                this_offset = entity_offset - truncated_len
                saved_by_reusing += truncated_len
                break

        output_file.write(data_to_add)
        all_data += data_to_add
        output_file.write("\"")
        entity_offset += len(data_to_add)
    assert len(entry) == 2, "We will use slot [2] in the list for the offset."
    entry.append(this_offset)

output_file.write("_s;\n")

index = {}
for offset, entry in enumerate(entries):
    starting_letter = entry[ENTITY][0]
    if starting_letter not in index:
        index[starting_letter] = offset

output_file.write("""
static constexpr std::array<HTMLEntityTableEntry, %s> staticEntityTable {\n""" % entity_count)

for entry in entries:
    values = entry[VALUE].split(' ')
    assert len(values) <= 2, values
    first_character = convert_value_to_int(values[0])
    second_character = convert_value_to_int(values[1] if len(values) >= 2 else "")
    name_length = len(entry[ENTITY])
    name_includes_trailing_semicolon = int(entry[ENTITY][-1] == ';')
    if name_includes_trailing_semicolon:
        name_length -= 1
    # These checks correspond to the sizes of the bitfields in HTMLEntityTableEntry in HTMLEntityTable.h.
    assert first_character < (1 << 21)
    assert second_character < (1 << 16)
    assert entry[OFFSET] < (1 << 14)
    assert name_length < (1 << 5)
    output_file.write('    HTMLEntityTableEntry { %s, %s, %s, %s, %s }, // &%s\n' % (
        first_character, second_character, entry[OFFSET], name_length, name_includes_trailing_semicolon, entry[ENTITY]))

output_file.write("""};

""")

output_file.write("static constexpr auto uppercaseOffset = std::to_array<uint16_t>({\n")
for letter in string.ascii_uppercase:
    output_file.write("%d,\n" % index[letter])
output_file.write("%d\n" % index['a'])
output_file.write("""});

static constexpr auto lowercaseOffset = std::to_array<uint16_t>({\n""")
for letter in string.ascii_lowercase:
    output_file.write("%d,\n" % index[letter])
output_file.write("%d\n" % entity_count)
output_file.write("""});

std::span<const char> HTMLEntityTableEntry::nameCharacters() const
{
    return staticEntityStringStorage.span().subspan(nameCharactersOffset);
}

std::span<const HTMLEntityTableEntry> HTMLEntityTable::entriesStartingWith(char16_t c)
{
    if (c >= 'A' && c <= 'Z') {
        auto* start = &staticEntityTable[uppercaseOffset[c - 'A']];
        return unsafeMakeSpan(start, &staticEntityTable[uppercaseOffset[c - 'A' + 1] - 1] - start + 1);
    }
    if (c >= 'a' && c <= 'z') {
        auto* start = &staticEntityTable[lowercaseOffset[c - 'a']];
        return unsafeMakeSpan(start, &staticEntityTable[lowercaseOffset[c - 'a' + 1] - 1] - start + 1);
    }
    return { };
}

std::span<const HTMLEntityTableEntry> HTMLEntityTable::entries()
{
    return staticEntityTable;
}

}

""")
