### Copyright (c) 2020, FlightAware LLC.
### All rights reserved.
### See the LICENSE file for licensing terms.

/* starch generated code. Do not edit. */

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include "${os.path.relpath(gen.generated_include_path, current_dir)}"

/* helper for re-sorting registries */
struct starch_regentry_prefix {
    int rank;
};

static int starch_regentry_rank_compare (const void *l, const void *r)
{
    const struct starch_regentry_prefix *left = l, *right = r;
    return left->rank - right->rank;
}

% for function in sorted(gen.functions.values()):
/* dispatcher / registry for ${function.name} */

${function.regentry_type} * ${function.select_symbol}() {
    for (${function.regentry_type} *entry = ${function.registry_symbol};
         entry->name;
         ++entry)
    {
        if (entry->flavor_supported && !(entry->flavor_supported()))
            continue;
        return entry;
    }
    return NULL;
}

static ${function.returntype} ${function.dispatcher_symbol} ( ${function.declaration_arglist} ) {
    ${function.regentry_type} *entry = ${function.select_symbol}();
    if (!entry)
        abort();

    ${function.callable_symbol} = entry->callable;
% if function.returntype == 'void':
    ${function.callable_symbol} ( ${function.named_arglist} );
% else:
    return ${function.callable_symbol} ( ${function.named_arglist} );
% endif
}

${function.pointer_type} ${function.callable_symbol} = ${function.dispatcher_symbol};

void ${function.set_wisdom_symbol} (const char * const * received_wisdom)
{
    /* re-rank the registry based on received wisdom */
    ${function.regentry_type} *entry;
    for (entry = ${function.registry_symbol}; entry->name; ++entry) {
        const char * const *search;
        for (search = received_wisdom; *search; ++search) {
            if (!strcmp(*search, entry->name)) {
                break;
            }
        }
        if (*search) {
            /* matches an entry in the wisdom list, order by position in the list */
            entry->rank = search - received_wisdom;
        } else {
            /* no match, rank after all possible matches, retaining existing order */
            entry->rank = (search - received_wisdom) + (entry - ${function.registry_symbol});
        }
    }

    /* re-sort based on the new ranking */
    qsort(${function.registry_symbol}, entry - ${function.registry_symbol}, sizeof(${function.regentry_type}), starch_regentry_rank_compare);

    /* reset the implementation pointer so the next call will re-select */
    ${function.callable_symbol} = ${function.dispatcher_symbol};
}

${function.regentry_type} ${function.registry_symbol}[] = {
% for mix in sorted(gen.mixes.values()):
  <%
    # gather all implementations for this mix, sort by wisdom
    def rank_key(value, wisdom=mix.function_wisdom(function)):
      impl, flavor = value
      try:
        return wisdom.index(impl.wisdom_name(flavor))
      except ValueError:
        return len(wisdom)

    mix_impls = []
    for flavor in mix.flavors:
        if function.aligned and function.aligned_pair:
            if flavor.alignment > 1:
                # add aligned impls
                for impl in function.impls:
                    if impl.feature is None or impl.feature in flavor.features:
                        mix_impls.append( (impl, flavor) )
            # add unaligned impls
            for impl in function.aligned_pair.impls:
                if impl.feature is None or impl.feature in flavor.features:
                    mix_impls.append( (impl, flavor) )
        else:
            # no alignment specialization
            for impl in function.impls:
                if impl.feature is None or impl.feature in flavor.features:
                    mix_impls.append( (impl, flavor) )

    mix_impls.sort(key=rank_key)
  %>
#ifdef ${mix.macro}
  % for rank, (impl, flavor) in enumerate(mix_impls):
    { ${rank}, "${impl.wisdom_name(flavor)}", "${flavor.name}", ${impl.impl_symbol(flavor)}, ${flavor.test_function_expr} },
  % endfor
#endif /* ${mix.macro} */
% endfor
    { 0, NULL, NULL, NULL, NULL }
};

% endfor

int ${gen.sym("read_wisdom")} (const char * path)
{
    FILE *fp = fopen(path, "r");
    if (!fp)
        return -1;

    /* reset all ranks to identify entries not listed in the wisdom file; we'll assign ranks at the end to produce a stable sort */
% for function in sorted(gen.functions.values()):
    int rank_${function.name} = 0;
    for (${function.regentry_type} *entry = ${function.registry_symbol}; entry->name; ++entry) {
        entry->rank = 0;
    }
% endfor

    char linebuf[512];
    while (fgets(linebuf, sizeof(linebuf), fp)) {
        /* split name and impl on whitespace, handle comments etc */
        char *name = linebuf;
        while (*name && isspace(*name))
            ++name;

        if (!*name || *name == '#')
            continue;

        char *end = name;
        while (*end && !isspace(*end))
            ++end;

        if (!*end)
            continue;
        *end = 0;

        char *impl = end + 1;
        while (*impl && isspace(*impl))
            ++impl;

        if (!*impl)
           continue;

        end = impl;
        while (*end && !isspace(*end))
            ++end;

        *end = 0;

        /* try to find a matching registry entry */
% for function in sorted(gen.functions.values()):
        if (!strcmp(name, "${function.name}")) {
            for (${function.regentry_type} *entry = ${function.registry_symbol}; entry->name; ++entry) {
                if (!strcmp(impl, entry->name)) {
                    entry->rank = ++rank_${function.name};
                    break;
                }
            }
            continue;
        }
% endfor
    }

    if (ferror(fp)) {
        fclose(fp);
        return -1;
    }

    fclose(fp);

    /* assign ranks to unmatched items to (stable) sort them last; re-sort everything */
% for function in sorted(gen.functions.values()):
    {
        ${function.regentry_type} *entry;
        for (entry = ${function.registry_symbol}; entry->name; ++entry) {
            if (!entry->rank)
                entry->rank = ++rank_${function.name};
        }
        qsort(${function.registry_symbol}, entry - ${function.registry_symbol}, sizeof(${function.regentry_type}), starch_regentry_rank_compare);

        /* reset the implementation pointer so the next call will re-select */
        ${function.callable_symbol} = ${function.dispatcher_symbol};
    }
% endfor

    return 0;
}
