import os
import json
import re

targets = [{'source': 'extension/json/include/', 'target': 'extension/json'}]

file_list = []
for target in targets:
    source_base = os.path.sep.join(target['source'].split('/'))
    target_base = os.path.sep.join(target['target'].split('/'))
    for fname in os.listdir(source_base):
        if '_enums.json' not in fname:
            continue
        file_list.append(
            {
                'source': os.path.join(source_base, fname),
                'include_path': fname.replace('.json', '.hpp'),
                'target_hpp': os.path.join(source_base, fname.replace('.json', '.hpp')),
                'target_cpp': os.path.join(target_base, fname.replace('.json', '.cpp')),
            }
        )

header = '''//===----------------------------------------------------------------------===//
// This file is automatically generated by scripts/generate_enums.py
// Do not edit this file manually, your changes will be overwritten
//===----------------------------------------------------------------------===//

${INCLUDE_LIST}
namespace duckdb {
'''

footer = '''
} // namespace duckdb
'''

include_base = '#include "${FILENAME}"\n'

enum_header = '\nenum class ${ENUM_NAME} : ${ENUM_TYPE} {\n'

enum_footer = '};'

enum_value = '\t${ENUM_MEMBER} = ${ENUM_VALUE},\n'

enum_util_header = '''
template<>
const char* EnumUtil::ToChars<${ENUM_NAME}>(${ENUM_NAME} value);

template<>
${ENUM_NAME} EnumUtil::FromString<${ENUM_NAME}>(const char *value);
'''

enum_util_conversion_begin = '''
template<>
const char* EnumUtil::ToChars<${ENUM_NAME}>(${ENUM_NAME} value) {
	switch(value) {
'''

enum_util_switch = '\tcase ${ENUM_NAME}::${ENUM_MEMBER}:\n\t\treturn "${ENUM_MEMBER}";\n'

enum_util_conversion_end = '''	default:
		throw NotImplementedException(StringUtil::Format("Enum value of type ${ENUM_NAME}: '%d' not implemented", value));
	}
}
'''

from_string_begin = '''
template<>
${ENUM_NAME} EnumUtil::FromString<${ENUM_NAME}>(const char *value) {
'''

from_string_comparison = '''    if (StringUtil::Equals(value, "${ENUM_MEMBER}")) {
		return ${ENUM_NAME}::${ENUM_MEMBER};
	}
'''

from_string_end = '''   throw NotImplementedException(StringUtil::Format("Enum value of type ${ENUM_NAME}: '%s' not implemented", value));
}
'''


class EnumMember:
    def __init__(self, entry, index):
        self.comment = None
        self.index = index
        if type(entry) == str:
            self.name = entry
        else:
            self.name = entry['name']
            if 'comment' in entry:
                self.comment = entry['comment']
            if 'index' in entry:
                self.index = int(entry['index'])


class EnumClass:
    def __init__(self, entry):
        self.name = entry['name']
        self.type = 'uint8_t'
        self.values = []
        index = 0
        for value_entry in entry['values']:
            self.values.append(EnumMember(value_entry, index))
            index += 1


for entry in file_list:
    source_path = entry['source']
    target_header = entry['target_hpp']
    target_source = entry['target_cpp']
    include_path = entry['include_path']
    with open(source_path, 'r') as f:
        json_data = json.load(f)

    include_list = ['duckdb/common/constants.hpp', 'duckdb/common/enum_util.hpp']
    enums = []

    for entry in json_data:
        if 'includes' in entry:
            include_list += entry['includes']
        enums.append(EnumClass(entry))

    with open(target_header, 'w+') as f:
        include_text = '#pragma once\n\n'
        include_text += ''.join([include_base.replace('${FILENAME}', x) for x in include_list])
        f.write(header.replace('${INCLUDE_LIST}', include_text))

        for enum in enums:
            f.write(enum_header.replace('${ENUM_NAME}', enum.name).replace('${ENUM_TYPE}', enum.type))
            for value in enum.values:
                if value.comment is not None:
                    f.write('\t//! ' + value.comment + '\n')
                f.write(enum_value.replace('${ENUM_MEMBER}', value.name).replace('${ENUM_VALUE}', str(value.index)))

            f.write(enum_footer)
            f.write('\n')

        for enum in enums:
            f.write(enum_util_header.replace('${ENUM_NAME}', enum.name))

        f.write(footer)

    with open(target_source, 'w+') as f:
        source_include_list = [include_path, 'duckdb/common/string_util.hpp']
        f.write(
            header.replace(
                '${INCLUDE_LIST}', ''.join([include_base.replace('${FILENAME}', x) for x in source_include_list])
            )
        )

        for enum in enums:
            f.write(enum_util_conversion_begin.replace('${ENUM_NAME}', enum.name))
            for value in enum.values:
                f.write(enum_util_switch.replace('${ENUM_MEMBER}', value.name).replace('${ENUM_NAME}', enum.name))

            f.write(enum_util_conversion_end.replace('${ENUM_NAME}', enum.name))
            f.write(from_string_begin.replace('${ENUM_NAME}', enum.name))
            for value in enum.values:
                f.write(from_string_comparison.replace('${ENUM_MEMBER}', value.name).replace('${ENUM_NAME}', enum.name))

            f.write(from_string_end.replace('${ENUM_NAME}', enum.name))
        f.write(footer)
