#!/usr/bin/env python
import json
import re
import sys
from pathlib import Path

enum_types = []
enum_types_names = []


def create_attribute(name):
    """
    Gets an attribute name from the enum and convert it to snake_case
    """
    # Removes any hyphens or dots from the name, substituting by an underscore
    name_normalized = re.sub("[.]", "", name)
    name_normalized = re.sub("[-]", "_", name_normalized)
    # The following substitution will add an underscore between
    # any character and a word starting with a capital letter, followed by
    # a lowercase letters. For example:
    # 1) "EVConnected" -> EV_Connected
    # 2) "SuspendedEVSE" -> SuspendedEVSE
    # 3) "CSMSRootCertificate" -> CSMS_RootCertificate
    # For names with numbers within, this conversion does not work so well
    # 4) "Other1PhMax16A" -> Other1_PhMax16A
    name_normalized = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name_normalized)
    # This one  will add an underscore between a word ending with non-capital
    # letter and one starting with a capital. It will also lowercase it.
    # For example:
    # 1) "EV_Connected" -> ev_connected
    # 2) "SuspendedEVSE" -> suspended_evse
    # 3) "CSMS_RootCertificate" -> csms_root_certificate
    # 4) "Other1_PhMax16A" -> other1_ph_max16a  (ideally should be
    # other_1ph_max_16a or similar.
    name_converted = re.sub("([a-z])([A-Z])", r"\1_\2", name_normalized).lower()
    return Attribute(name, name_converted)


class NormalClass:
    def __init__(self, name):
        self.name = name
        self.attrs = []

    def add_attr(self, attr):
        self.attrs.append(attr)

    def __str__(self):
        output = f"class {self.name}(str, Enum):\n"

        if len(self.attrs) == 0:
            return output + "    pass\n"

        for attr in self.attrs:
            output += str(attr)

        return output


class Attribute:
    def __init__(self, name, name_converted):
        self.name = name
        self.name_converted = name_converted

    def __str__(self):
        name = self.name
        if not re.match("^[a-zA-Z_]", self.name):
            name = "_" + self.name

        # The 4 spaces after the start of the string is to guarantee the proper
        # indentation
        return f'    {self.name_converted} = "{name}"\n'

    def __repr__(self):
        return f"<{self.name}, {self.name_converted}> "


def parse_schema(schema):
    with open(schema, "r") as f:
        schema = json.loads(f.read())

    try:
        definitions = schema["definitions"]
    except KeyError:
        print("Error: No definitions field found on the schema")
        return

    for enum_type, value in definitions.items():
        if "Enum" in enum_type:
            type_name = enum_type.replace("Enum", "")
            if type_name not in enum_types_names:
                nc = NormalClass(type_name)
                for enum_attr in value["enum"]:
                    attr = create_attribute(enum_attr)
                    nc.add_attr(attr)
                enum_types.append(nc)
                enum_types_names.append(nc.name)


if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Pass path to folder with schemas")
        sys.exit(-1)

    # The second argument of argv is the path for the schemas
    p = Path(sys.argv[1])
    schemas = list(p.glob("*.json"))

    # This loop will update the lists enum_types and enum_types_names:
    # The former contains the enum objects that are later parsed to the
    # enum.py, using the str representation. And the latter, is used to keep
    # track of the already processed Enums, avoiding data duplication
    for schema in schemas:
        parse_schema(schema)

    with open("enums.py", "wb+") as f:
        f.write(b"from enum import Enum\n")
        for enum_type_ in sorted(enum_types, key=lambda enum_type: enum_type.name):
            f.write(b"\n\n")
            f.write(str(enum_type_).encode("utf-8"))
