#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import os

ENUMS = {
    "Direction": ["Inherit", "LTR", "RTL"],
    "Unit": ["Undefined", "Point", "Percent", "Auto"],
    "FlexDirection": ["Column", "ColumnReverse", "Row", "RowReverse"],
    "Justify": [
        "FlexStart",
        "Center",
        "FlexEnd",
        "SpaceBetween",
        "SpaceAround",
        "SpaceEvenly",
    ],
    "Overflow": ["Visible", "Hidden", "Scroll"],
    "Align": [
        "Auto",
        "FlexStart",
        "Center",
        "FlexEnd",
        "Stretch",
        "Baseline",
        "SpaceBetween",
        "SpaceAround",
        "SpaceEvenly",
    ],
    "PositionType": ["Static", "Relative", "Absolute"],
    "Display": ["Flex", "None", "Contents"],
    "Wrap": ["NoWrap", "Wrap", "WrapReverse"],
    "BoxSizing": ["BorderBox", "ContentBox"],
    "MeasureMode": ["Undefined", "Exactly", "AtMost"],
    "Dimension": ["Width", "Height"],
    "Edge": [
        "Left",
        "Top",
        "Right",
        "Bottom",
        "Start",
        "End",
        "Horizontal",
        "Vertical",
        "All",
    ],
    "NodeType": ["Default", "Text"],
    "LogLevel": ["Error", "Warn", "Info", "Debug", "Verbose", "Fatal"],
    "ExperimentalFeature": [
        # Mimic web flex-basis behavior (experiment may be broken)
        "WebFlexBasis",
    ],
    "Gutter": ["Column", "Row", "All"],
    # Known incorrect behavior which can be enabled for compatibility
    "Errata": [
        # Default: Standards conformant mode
        ("None", 0),
        # Allows main-axis flex basis to be stretched without flexGrow being
        # set (previously referred to as "UseLegacyStretchBehaviour")
        ("StretchFlexBasis", 1 << 0),
        # Absolute position in a given axis will be relative to the padding
        # edge of the parent container instead of the content edge when a
        # specific inset (top/bottom/left/right) is not set.
        ("AbsolutePositionWithoutInsetsExcludesPadding", 1 << 1),
        # Absolute nodes will resolve percentages against the inner size of
        # their containing node, not the padding box
        ("AbsolutePercentAgainstInnerSize", 1 << 2),
        # Enable all incorrect behavior (preserve compatibility)
        ("All", 0x7FFFFFFF),
        # Enable all errata except for "StretchFlexBasis" (Defaults behavior
        # before Yoga 2.0)
        ("Classic", 0x7FFFFFFF & (~(1 << 0))),
    ],
}

DO_NOT_STRIP = ["LogLevel"]

BITSET_ENUMS = ["Errata"]


def get_license(ext):
    return f"""{"/**" if ext == "js" else "/*"}
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

// @{"generated"} by enums.py
{"// clang-format off" if ext == "cpp" else ""}
"""


def _format_name(symbol, delimiter=None, transform=None):
    symbol = str(symbol)
    out = ""
    for i in range(0, len(symbol)):
        c = symbol[i]
        if str.istitle(c) and i != 0 and not str.istitle(symbol[i - 1]):
            out += delimiter or ""
        if transform is None:
            out += c
        else:
            out += getattr(c, transform)()
    return out


def to_java_upper(symbol):
    return _format_name(symbol, "_", "upper")


def to_hyphenated_lower(symbol):
    return _format_name(symbol, "-", "lower")


root = os.path.dirname(os.path.abspath(__file__))

# write out C & Objective-C headers
with open(root + "/yoga/YGEnums.h", "w") as f:
    f.write(get_license("cpp"))
    f.write("#pragma once\n")
    f.write("#include <yoga/YGMacros.h>\n\n")

    f.write("YG_EXTERN_C_BEGIN\n\n")
    items = sorted(ENUMS.items())
    for name, values in items:
        f.write("YG_ENUM_DECL(\n")

        f.write("    YG%s,\n" % name)
        for value in values:
            if isinstance(value, tuple):
                f.write("    YG%s%s = %d" % (name, value[0], value[1]))
            else:
                f.write("    YG%s%s" % (name, value))
            if value == values[-1]:
                f.write(")\n")
            else:
                f.write(",\n")
        if name in BITSET_ENUMS:
            f.write("YG_DEFINE_ENUM_FLAG_OPERATORS(YG%s)\n" % name)
        f.write("\n")
    f.write("YG_EXTERN_C_END\n")

# Write out C++ scoped enums
for name, values in sorted(ENUMS.items()):
    with open(f"{root}/yoga/enums/{name}.h", "w") as f:
        f.write(get_license("cpp"))
        f.write("#pragma once\n\n")

        f.write("#include <cstdint>\n")
        f.write("#include <yoga/YGEnums.h>\n")
        f.write("#include <yoga/enums/YogaEnums.h>\n\n")

        f.write("namespace facebook::yoga {\n\n")

        width = "uint32_t" if name in BITSET_ENUMS else "uint8_t"
        f.write(f"enum class {name} : {width} {{\n")
        for value in values:
            ordinal = value[0] if isinstance(value, tuple) else value
            f.write(f"  {ordinal} = YG{name}{ordinal},\n")
        f.write("};\n\n")

        if name in BITSET_ENUMS:
            f.write(f"YG_DEFINE_ENUM_FLAG_OPERATORS({name})\n\n")
        else:
            f.write("template <>\n")
            f.write(f"constexpr int32_t ordinalCount<{name}>() {{\n")
            f.write(f"  return {len(values)};\n")
            f.write("}\n\n")

        f.write(f"constexpr {name} scopedEnum(YG{name} unscoped) {{\n")
        f.write(f"  return static_cast<{name}>(unscoped);\n")
        f.write("}\n\n")

        f.write(f"constexpr YG{name} unscopedEnum({name} scoped) {{\n")
        f.write(f"  return static_cast<YG{name}>(scoped);\n")
        f.write("}\n\n")

        f.write(f"inline const char* toString({name} e) {{\n")
        f.write(f"  return YG{name}ToString(unscopedEnum(e));\n")
        f.write("}\n\n")

        f.write("} // namespace facebook::yoga\n")

# write out C body for printing
with open(root + "/yoga/YGEnums.cpp", "w") as f:
    f.write(get_license("cpp"))
    f.write("#include <yoga/YGEnums.h>\n\n")
    items = sorted(ENUMS.items())
    for name, values in items:
        f.write("const char* YG%sToString(const YG%s value) {\n" % (name, name))
        f.write("  switch (value) {\n")
        for value in values:
            if isinstance(value, tuple):
                f.write("    case YG%s%s:\n" % (name, value[0]))
                f.write('      return "%s";\n' % to_hyphenated_lower(value[0]))
            else:
                f.write("    case YG%s%s:\n" % (name, value))
                f.write('      return "%s";\n' % to_hyphenated_lower(value))
        f.write("  }\n")
        f.write('  return "unknown";\n')
        f.write("}\n")
        if name != items[-1][0]:
            f.write("\n")

# write out java files
for name, values in sorted(ENUMS.items()):
    with open(root + "/java/com/facebook/yoga/Yoga%s.java" % name, "w") as f:
        f.write(get_license("java"))
        f.write("package com.facebook.yoga;\n\n")
        if name in DO_NOT_STRIP:
            f.write("import com.facebook.yoga.annotations.DoNotStrip;\n\n")
            f.write("@DoNotStrip\n")
        f.write("public enum Yoga%s {\n" % name)
        if len(values) > 0:
            for value in values:
                if isinstance(value, tuple):
                    f.write("  %s(%d)" % (to_java_upper(value[0]), value[1]))
                else:
                    f.write("  %s(%d)" % (to_java_upper(value), values.index(value)))
                if values.index(value) is len(values) - 1:
                    f.write(";\n")
                else:
                    f.write(",\n")
        else:
            f.write("__EMPTY(-1);")
        f.write("\n")
        f.write("  private final int mIntValue;\n")
        f.write("\n")
        f.write("  Yoga%s(int intValue) {\n" % name)
        f.write("    mIntValue = intValue;\n")
        f.write("  }\n")
        f.write("\n")
        f.write("  public int intValue() {\n")
        f.write("    return mIntValue;\n")
        f.write("  }\n")
        f.write("\n")
        if name in DO_NOT_STRIP:
            f.write("  @DoNotStrip\n")
        f.write("  public static Yoga%s fromInt(int value) {\n" % name)
        f.write("    switch (value) {\n")
        for value in values:
            if isinstance(value, tuple):
                f.write(
                    "      case %d: return %s;\n" % (value[1], to_java_upper(value[0]))
                )
            else:
                f.write(
                    "      case %d: return %s;\n"
                    % (values.index(value), to_java_upper(value))
                )
        f.write(
            '      default: throw new IllegalArgumentException("Unknown enum value: " + value);\n'
        )
        f.write("    }\n")
        f.write("  }\n")
        f.write("}\n")

# write out TypeScript file
with open(root + "/javascript/src/generated/YGEnums.ts", "w") as f:
    f.write(get_license("js"))
    enums = sorted(ENUMS.items())
    for enum_name, ordinals in enums:
        f.write(f"export enum {enum_name} {{\n")
        for ordinal_index, ordinal in enumerate(ordinals):
            ordinal_name = ordinal[0] if isinstance(ordinal, tuple) else ordinal
            ordinal_value = ordinal[1] if isinstance(ordinal, tuple) else ordinal_index
            f.write(f"  {ordinal_name} = {ordinal_value},\n")

        f.write("}\n\n")

    f.write("const constants = {\n")
    for enum_name, ordinals in enums:
        for ordinal in ordinals:
            ordinal_name = ordinal[0] if isinstance(ordinal, tuple) else ordinal
            ordinal_value = ordinal[1] if isinstance(ordinal, tuple) else ordinal_index
            f.write(
                f"  {to_java_upper(enum_name)}_{to_java_upper(ordinal_name)}: {enum_name}.{ordinal_name},\n"
            )

    f.write("}\n")
    f.write("export default constants")
