#!/usr/bin/env python3
import os
import sys

MAX_PER_LINE = 100

def formatCallOrPrototype(content: str, indent: int, lineWidth: int):
    idx = content.find('(')
    shiftStr = ' ' * (idx + 1)
    idx2 = content.find(')', idx+1)
    extraLen = len(content[idx2:])

    ret = []
    currentLine = content[0:idx+1]
    for arg in content[idx + 1 : idx2].split(','):
        arg = arg.strip()
        #newLen = indent * 4 + len(currentLine) + len(arg)
        if indent * 4 + len(currentLine) + len(arg) + extraLen > lineWidth:
            ret.append('\t' * indent + currentLine[0:-1])
            currentLine = shiftStr
        currentLine += arg + ', '

    if len(currentLine) > len(shiftStr):
        ret.append('\t' * indent + currentLine[0:-2] + content[idx2:])

    return '\n'.join(ret)

def get_values(entry_dict, entry_type):
    values = []
    if '*' == entry_type:
        for key in list(entry_dict.keys()):
            if entry_type in key:
                values += entry_dict[key]
                entry_dict.pop(key, None)
    elif entry_type in dict(entry_dict):
        values = entry_dict[entry_type]
        entry_dict.pop(entry_type, None)
    if values:
        return sorted(values)
    return values

def get_keys(entry_dict, entry_type, values):
    l = list()
    if '*' == entry_type:
        dval = dict()
        for key in list(entry_dict.keys()):
            if entry_type in key:
                xval = entry_dict[key]
                for val in xval:
                    dval[val] = key

        for val in list(values):
            key = dval[val]
            l.append(key)
    return l

def write_entry(f, entry_dict, entry_type, entry_name):
    values = get_values(entry_dict, entry_type)
    if not values:
        return

    f.write('#define have_' + entry_name.lower() + '_list_indices\n')
    f.write('static const size_t ' + entry_name.lower() + '_list_indices[] = {\n')

    for val in values:
        f.write('\tFreeRDP_' + val + ',\n')

    f.write('};\n\n')

def formatArrayEntry(content: list[str], ident: int, maxPerLine: int):
    ret = []
    currentLine = '{ '
    for arg in content:
        newLen = ident * 4 + len(currentLine) + len(arg) + 2
        if newLen >= maxPerLine:
            ret.append('\t' * ident + currentLine[0:-1])
            currentLine = '  '
        currentLine += arg + ', '

    if len(currentLine) > ident * 4 + 2:
        ret.append('\t' * ident + currentLine[0:-2])

    return "\n".join(ret) + " }"

def write_str_case(f, entry_idx, val):
    entry_types = ['BOOL', 'UINT16', 'INT16', 'UINT32', 'INT32', 'UINT64', 'INT64', 'STRING', 'POINTER']

    f.write(formatArrayEntry([ f'FreeRDP_{val}', f'FREERDP_SETTINGS_TYPE_{entry_types[entry_idx]}', f'"FreeRDP_{val}"'], 1, MAX_PER_LINE) + ',\n')

def write_str(f, entry_dict):
    f.write('typedef enum\n{\n')
    f.write('\tFREERDP_SETTINGS_TYPE_BOOL,\n')
    f.write('\tFREERDP_SETTINGS_TYPE_UINT16,\n')
    f.write('\tFREERDP_SETTINGS_TYPE_INT16,\n')
    f.write('\tFREERDP_SETTINGS_TYPE_UINT32,\n')
    f.write('\tFREERDP_SETTINGS_TYPE_INT32,\n')
    f.write('\tFREERDP_SETTINGS_TYPE_UINT64,\n')
    f.write('\tFREERDP_SETTINGS_TYPE_INT64,\n')
    f.write('\tFREERDP_SETTINGS_TYPE_STRING,\n')
    f.write('\tFREERDP_SETTINGS_TYPE_POINTER\n')
    f.write('} FREERDP_SETTINGS_TYPE;\n')
    f.write('\n')
    f.write('struct settings_str_entry\n{\n')
    f.write('\tSSIZE_T id;\n')
    f.write('\tSSIZE_T type;\n')
    f.write('\tconst char* str;\n')
    f.write('};\n')
    f.write('static const struct settings_str_entry settings_map[] = {\n')
    #f.write('{\n')

    entry_types = ['BOOL', 'UINT16', 'INT16', 'UINT32', 'INT32', 'UINT64', 'INT64', 'char*', '*']
    for entry_type in entry_types:
        values = get_values(entry_dict, entry_type)
        if values:
            for val in values:
                write_str_case(f, entry_types.index(entry_type), val)
    f.write('};\n\n')
    f.write('\n')

def write_getter_case(f, val, cast, typestr):
    f.write('\t\tcase ')
    if typestr:
        f.write('(' + typestr + ')')
    f.write('FreeRDP_' + val + ':\n')
    f.write('\t\t\treturn ' + cast + 'settings->' + val + ';\n\n')

def write_getter_body(f, values, ret, keys, isPointer, compat_values, typestr, entry_type):
    f.write('{\n')
    f.write('\tWINPR_ASSERT(settings);\n\n')
    f.write('\tswitch (id)\n')
    f.write('\t{\n')
    if values:
        for i in range(len(values)):
            val = values[i]
            cast = ''
            if isPointer and keys:
                key = keys[i]
                if key != 'void*':
                    cast = '(void*)'
            write_getter_case(f, val, cast, None)
    if compat_values:
        f.write('#if !defined(WITHOUT_FREERDP_3x_DEPRECATED)\n')
        for i in range(len(compat_values)):
            val = compat_values[i]
            cast = '(' + entry_type + ')'
            f.write('\t\t// API Compatibility section, remove with FreeRDP 4.x\n')
            write_getter_case(f, val, cast, typestr)
        f.write('#endif\n')
    f.write('\t\tdefault:\n')
    f.write('\t\t\tWLog_ERR(TAG, "Invalid key index %d [%s|%s]", id, freerdp_settings_get_name_for_key(id),\n\t\t\t         freerdp_settings_get_type_name_for_key(id));\n')
    f.write('\t\t\tWINPR_ASSERT(FALSE);\n')
    f.write('\t\t\treturn ' + ret + ';\n')
    f.write('\t}\n')
    f.write('}\n\n')

def write_getter(f, entry_dict, entry_type, entry_name, postfix, compat_dict):
    isString = 'string' in entry_name
    isPointer = 'pointer' in entry_name
    copy = dict(entry_dict)
    values = get_values(entry_dict, entry_type)
    compat_values = get_values(compat_dict, entry_type)
    keys = get_keys(copy, entry_type, values)

    typestr = 'FreeRDP_Settings_Keys_' + entry_name.capitalize()
    typestr = typestr.replace('_Uint', '_UInt')

    protoLine = ''
    if isPointer:
        protoLine += 'void*'
    elif isString:
        protoLine += 'const ' + entry_type
    else:
        protoLine += entry_type

    if isPointer:
        protoLine += ' freerdp_settings_get_pointer_writable(rdpSettings* settings, ' + typestr + ' id)'
    else:
        protoLine += ' freerdp_settings_get_' + entry_name.lower() + '(WINPR_ATTR_UNUSED const rdpSettings* settings, WINPR_ATTR_UNUSED ' + typestr + ' id)'
    f.write(formatCallOrPrototype(protoLine, 0, MAX_PER_LINE) + '\n')

    if isString or isPointer:
        ret = 'nullptr';
    elif 'bool' in entry_name:
        ret = 'FALSE';
    else:
        ret = '0';

    write_getter_body(f, values, ret, keys, isPointer, compat_values, typestr, entry_type)

    if isString:
        f.write('char* freerdp_settings_get_' + entry_name.lower() + '_writable(rdpSettings* settings, ' + typestr + ' id)\n')
        write_getter_body(f, values, ret, keys, isPointer, compat_values, typestr, entry_type)

def write_setter_case(f, val, postfix, isPointer, cast):
    f.write('\t\tcase FreeRDP_' + val + ':\n')
    if isPointer:
        f.write('\t\t\tsettings->' + val + ' = ' + cast + 'cnv.v;\n')
        f.write('\t\t\tbreak;\n\n')
    elif not postfix:
        f.write('\t\t\tsettings->' + val + ' = ' + cast + 'cnv.c;\n')
        f.write('\t\t\tbreak;\n\n')
    elif len(postfix) <= 1:
        f.write(formatCallOrPrototype('return update_string' + postfix + '(&settings->' + val + ', cnv.c, len);\n', 3, MAX_PER_LINE) + '\n')
    else:
        f.write(formatCallOrPrototype('return update_string' + postfix + '(&settings->' + val + ', cnv.cc, len, cleanup);\n', 3, MAX_PER_LINE) + '\n')

def write_setter(f, entry_dict, entry_type, entry_name, postfix, compat_dict):
    isString = 'string' in entry_name
    isPointer = 'pointer' in entry_name
    copy = dict(entry_dict)
    values = get_values(entry_dict, entry_type)
    compat_values = get_values(compat_dict, entry_type)
    keys = get_keys(copy, entry_type, values)

    typestr = 'FreeRDP_Settings_Keys_' + entry_name.capitalize()
    typestr = typestr.replace('_Uint', '_UInt')

    protoLine = 'BOOL freerdp_settings_set_' + entry_name.lower() + postfix + '(WINPR_ATTR_UNUSED rdpSettings* settings, WINPR_ATTR_UNUSED ' + typestr + ' id, '
    if isString or isPointer:
        protoLine += 'const '
    if not isPointer:
        protoLine += entry_type + ' val'
    else:
        protoLine += 'void* val'

    if isString and len(postfix) <= 1:
        protoLine += ', size_t len)\n'
    elif isString:
        protoLine += ', size_t len, BOOL cleanup)'
    else:
        protoLine += ')'
    f.write(formatCallOrPrototype(protoLine, 0, MAX_PER_LINE) + '\n')

    f.write('{\n')
    f.write('\tunion\n')
    f.write('\t{\n')
    f.write('\t\tvoid* v;\n')
    f.write('\t\tconst void* cv;\n')
    if not isPointer:
        f.write('\t\t' + entry_type + ' c;\n')
        f.write('\t\tconst ' + entry_type + ' cc;\n')
    f.write('\t} cnv;\n')

    f.write('\tWINPR_ASSERT(settings);\n\n')
    if isPointer:
        f.write('\tcnv.cv = val;\n\n')
    elif isString:
        f.write('\tcnv.cc = val;\n\n')
    else:
        f.write('\tcnv.c = val;\n\n')
    f.write('\tswitch (id)\n')
    f.write('\t{\n')
    if values:
        count = 0
        for val in values:
            index = count
            count += 1
            cast = ''
            if isPointer:
                k = keys[index];
                if k != 'void*' and len(k) > 0:
                    cast = '(' + k + ')'
            write_setter_case(f, val, postfix, isPointer, cast)
    if compat_values:
        f.write('#if !defined(WITHOUT_FREERDP_3x_DEPRECATED)\n')
        for val in compat_values:
            cast = '(int32_t)'
            f.write('\t\t// API Compatibility section, remove with FreeRDP 4.x\n')
            write_setter_case(f, val, postfix, isPointer, cast)
        f.write('#endif\n')
    f.write('\t\tdefault:\n')
    f.write('\t\t\tWLog_ERR(TAG, "Invalid key index %d [%s|%s]", id, freerdp_settings_get_name_for_key(id),\n\t\t\t         freerdp_settings_get_type_name_for_key(id));\n')
    f.write('\t\t\treturn FALSE;\n')
    f.write('\t}\n')
    f.write('\treturn TRUE;\n')
    f.write('}\n')
    f.write('\n')
    if isString and len(postfix) <= 1:
        f.write('BOOL freerdp_settings_set_string_len(rdpSettings* settings, FreeRDP_Settings_Keys_String id,\n')
        f.write('                                     const char* val, size_t len)\n')
        f.write('{\n')
        f.write('\treturn freerdp_settings_set_string_copy_(settings, id, val, len, TRUE);\n')
        f.write('}\n')
        f.write('\n')

        f.write('BOOL freerdp_settings_set_string(rdpSettings* settings, FreeRDP_Settings_Keys_String id,\n')
        f.write('                                 const char* val)\n')
        f.write('{\n')
        f.write('\tsize_t len = 0;\n')
        f.write('\tif (val)\n')
        f.write('\t\tlen = strlen(val);\n')
        f.write('\treturn freerdp_settings_set_string_copy_(settings, id, val, len, TRUE);\n')
        f.write('}\n')
        f.write('\n')

name = os.path.dirname(os.path.realpath(__file__))
begin = "WARNING: this data structure is carefully padded for ABI stability!"
end = "WARNING: End of ABI stable zone!"

print('begin parsing settings header')
try:
    type_list = dict()

    with open(name + "/../include/freerdp/settings_types_private.h", "r") as f:
        lines = f.readlines()
        started = False
        for line in lines:
            if not started:
                if begin in line:
                    started = True
                continue

            if end in line:
                break

            sline = line.strip()
            if not sline:
                continue
            if sline.startswith('/'):
                continue
            if sline.startswith('*'):
                continue
            if 'padding' in sline:
                continue
            if 'version' in sline:
                continue

            if sline.startswith('SETTINGS_DEPRECATED(ALIGN64'):
                sline = sline[27:].strip()

            sline = sline[:sline.find(');')]
            pair = sline.split()
            if pair[0] in type_list:
                type_list[pair[0]].append(pair[1])
            else:
                type_list[pair[0]] = [pair[1]]

    with open(name + '/../libfreerdp/common/settings_getters.c', 'w+') as f:
        f.write('/* Generated by ' + ''  + ' */\n\n')
        f.write('#define FREERDP_SETTINGS_INTERNAL_USE\n\n')
        f.write('#include <freerdp/settings_types_private.h>\n\n')
        f.write('\n\n')
        f.write('#include "../core/settings.h"\n\n')
        f.write('#include <winpr/assert.h>\n')
        f.write('#include <freerdp/settings.h>\n')
        f.write('#include <freerdp/log.h>\n\n')
        f.write('#define TAG FREERDP_TAG("common.settings")\n\n')

        f.write('static void free_string(char** current, BOOL cleanup)\n')
        f.write('{\n')
        f.write('\tif (cleanup)\n')
        f.write('\t{\n')
        f.write('\t\tif (*current)\n')
        f.write('\t\t\tmemset(*current, 0, strlen(*current));\n')
        f.write('\t\tfree(*current);\n')
        f.write('\t\t(*current) = nullptr;\n')
        f.write('\t}\n')
        f.write('}\n\n')

        f.write('static BOOL alloc_empty_string(char** current, const char* next, size_t next_len)\n')
        f.write('{\n')
        f.write('\tif (!next && (next_len > 0))\n')
        f.write('\t{\n')
        f.write('\t\t*current = calloc(next_len, 1);\n')
        f.write('\t\treturn (*current != nullptr);\n')
        f.write('\t}\n')
        f.write('\treturn FALSE;\n')
        f.write('}\n\n')


        f.write('static BOOL update_string_copy_(char** current, const char* next, size_t next_len, BOOL cleanup)\n')
        f.write('{\n')
        f.write('\tfree_string(current, cleanup);\n')
        f.write('\n')
        f.write('\tif (alloc_empty_string(current, next, next_len))\n')
        f.write('\t\treturn TRUE;\n')
        f.write('\n')
        f.write('\t*current = (next ? strndup(next, next_len) : nullptr);\n')
        f.write('\treturn !next || (*current != nullptr);\n')
        f.write('}\n\n')

        f.write('static BOOL update_string_(char** current, char* next, size_t next_len)\n')
        f.write('{\n')
        f.write('\tfree_string(current, TRUE);\n')
        f.write('\n')
        f.write('\tif (alloc_empty_string(current, next, next_len))\n')
        f.write('\t\treturn TRUE;\n')
        f.write('\n')
        f.write('\t*current = next;\n')
        f.write('\treturn !next || (*current != nullptr);\n')
        f.write('}\n\n')

        getter_list = dict(type_list)
        setter_list = dict(type_list)
        setter_list2 = dict(type_list)
        # Compatibility with older 3.x releases where the value was wrongly an unsigned type
        compat_getter_list = dict()
        compat_getter_list['UINT32'] = list()
        compat_getter_list['UINT32'].append('MonitorLocalShiftX')
        compat_getter_list['UINT32'].append('MonitorLocalShiftY')

        compat_setter_list = dict(compat_getter_list)

        write_getter(f, getter_list, 'BOOL', 'bool', '', compat_getter_list)
        write_setter(f, setter_list, 'BOOL', 'bool', '', compat_setter_list)
        write_getter(f, getter_list, 'UINT16', 'uint16', '', compat_getter_list)
        write_setter(f, setter_list, 'UINT16', 'uint16', '', compat_setter_list)
        write_getter(f, getter_list, 'INT16', 'int16', '', compat_getter_list)
        write_setter(f, setter_list, 'INT16', 'int16', '', compat_setter_list)
        write_getter(f, getter_list, 'UINT32', 'uint32', '', compat_getter_list)
        write_setter(f, setter_list, 'UINT32', 'uint32', '', compat_setter_list)
        write_getter(f, getter_list, 'INT32', 'int32', '', compat_getter_list)
        write_setter(f, setter_list, 'INT32', 'int32', '', compat_setter_list)
        write_getter(f, getter_list, 'UINT64', 'uint64', '', compat_getter_list)
        write_setter(f, setter_list, 'UINT64', 'uint64', '', compat_setter_list)
        write_getter(f, getter_list, 'INT64', 'int64', '', compat_getter_list)
        write_setter(f, setter_list, 'INT64', 'int64', '', compat_setter_list)
        write_getter(f, getter_list, 'char*', 'string', '_', compat_getter_list)
        write_setter(f, setter_list, 'char*', 'string', '_', compat_setter_list)
        write_setter(f, setter_list2, 'char*', 'string', '_copy_', compat_setter_list)
        write_getter(f, getter_list, '*', 'pointer', '', compat_getter_list)
        write_setter(f, setter_list, '*', 'pointer', '', compat_setter_list)

        f.write('\n')

    with open(name + '/../libfreerdp/common/settings_str.h', 'w+') as f:
        f.write('/* Generated by ' + ''  + ' */\n\n')
        f.write('#ifndef FREERDP_CORE_SETTINGS_STR_H\n')
        f.write('#define FREERDP_CORE_SETTINGS_STR_H\n\n')
        f.write('#include "../core/settings.h"\n\n')
        f.write('#include <freerdp/settings.h>\n')
        f.write('#include <freerdp/log.h>\n\n')
        f.write('#define TAG FREERDP_TAG("common.settings")\n\n')

        getter_list = dict(type_list)
        write_str(f, getter_list)
        f.write('#endif\n')
        f.write('\n')


    with open(name + '/../libfreerdp/core/test/settings_property_lists.h', 'w+') as f:
        f.write('#ifndef TEST_SETTINGS_PROPERTY_LISTS\n')
        f.write('#define TEST_SETTINGS_PROPERTY_LISTS\n\n')

        write_entry(f, type_list, 'BOOL', 'bool')
        write_entry(f, type_list, 'UINT16', 'uint16')
        write_entry(f, type_list, 'INT16', 'int16')
        write_entry(f, type_list, 'UINT32', 'uint32')
        write_entry(f, type_list, 'INT32', 'int32')
        write_entry(f, type_list, 'UINT64', 'uint64')
        write_entry(f, type_list, 'INT64', 'int64')
        write_entry(f, type_list, 'char*', 'string')
        write_entry(f, type_list, '*', 'pointer')

        f.write('#endif /* TEST_SETTINGS_PROPERTY_LISTS */\n\n')

        print('remaining:\n' + str(type_list))
except IOError as e:
    print('failed to parse settings header ' + str(e))
    sys.exit(-1)
print('ended parsing settings header')
