#!/usr/bin/env perl
#
# Generate C file that contains version strings

($header        # This is lsquic.h that contains version enum which we parse
    , $outfile  # This is destination C file
    ) = @ARGV;

open HEADER, $header
    or die "cannot open $header for reading: $!";
open OUT, ">$outfile"
    or die "cannot open $outfile for writing: $!";

while (<HEADER>) {
    if (/^enum lsquic_version$/ .. /^}/) {
        if (/^\s*(LSQVER_0*(\d+)),\s*$/ && $1 ne 'LSQVER_098') {
            if ($2 < 50) {
                push @enums, $1;
                push @versions, $2;
            }
            push @all_versions, $1;
            push @all_alpns, "h3-Q0$2";
        }
        if (/^\s*(LSQVER_ID(\d+))\b/) {
            push @draft_versions, $2;
            push @all_versions, $1;
            push @all_alpns, "h3-$2";
        }
        if (/^\s*(LSQVER_I(\d{3}))\b/) {
            push @all_versions, $1;
            if (not grep 'h3' eq $_, @all_alpns) {
                push @all_alpns, "h3";
            }
        }
    }
}

close HEADER;

$timestamp = localtime;

print OUT <<C_CODE;
/*
 * Auto-generated by $0 on $timestamp
 */

#include <assert.h>
#include <string.h>

#include "lsquic.h"

struct lsquic_engine;

static const char *const versions_to_string[ 1 << N_LSQVER ] = {
C_CODE

$max_mask = (1 << @versions) - 1;

for ($mask = 0; $mask <= $max_mask; ++$mask) {
    my @indexes;
    for ($i = 0; $i < @versions; ++$i) {
        if ($mask & (1 << $i)) {
            push @indexes, $i;
        }
    }
    print OUT "    [",
        join('|', map "(1<<$_)", @enums[@indexes]) || 0,
        "] = \"",
        join(',', @versions[@indexes]),
        "\",\n";
}

$enums = join '|', map "(1<<$_)", sort @enums;

print OUT <<"C_CODE";
};


const char *
lsquic_get_alt_svc_versions (unsigned versions)
{
    /* Limit to versions in versions_to_string: */
    versions &= ($enums);
    return versions_to_string[ versions ];
}

C_CODE


$all_version_count_and_null = scalar(@all_versions) + 1;

print OUT <<"C_CODE";
static const struct {
    unsigned    versions;
    const char *h3_alpns[$all_version_count_and_null];
} vers_2_h3_alnps[] = {
    { 0, { NULL }},
C_CODE

for ($i = 0; $i < (1 << @all_versions); ++$i)
{
    my (@vers, @alpns);
    for ($j = 0; $j < @all_versions; ++$j)
    {
        if ($i & (1 << $j))
        {
            push @vers, $all_versions[$j];
            push @alpns, $all_alpns[$j];
        }
    }
    if (@vers) {
        print OUT "    {", join("|", map "(1<<$_)", @vers), ", ",
            "{ ", join(", ", (map qq("$_"), @alpns), "NULL"), " }},\n";
    }
}

$all_versions = join "|", map "(1<<$_)", @all_versions;

print OUT <<"C_CODE";
};

const char *const *
lsquic_get_h3_alpns (unsigned versions)
{
    unsigned i;

    versions &= ($all_versions);

    for (i = 0; i < sizeof(vers_2_h3_alnps) / sizeof(vers_2_h3_alnps[0]); ++i)
        if (versions == vers_2_h3_alnps[i].versions)
            return vers_2_h3_alnps[i].h3_alpns;

    assert(0);
    return vers_2_h3_alnps[0].h3_alpns;
}
C_CODE


print OUT <<'C_CODE';

enum lsquic_version
lsquic_alpn2ver (const char *alpn, size_t len)
{
    static const struct el {
        size_t len;
        char alpn[10];
        enum lsquic_version version;
    } map[] = {
C_CODE

for ($i = 0; $i < @all_alpns; ++$i) {
    print OUT "        {sizeof(\"$all_alpns[$i]\")-1,\"$all_alpns[$i]\", $all_versions[$i]},\n";
}

print OUT <<'C_CODE';
    };
    const struct el *el;

    if (alpn)
        for (el = map; el < map + sizeof(map) / sizeof(map[0]); ++el)
            if (el->len == len && 0 == strncmp(el->alpn, alpn, len))
                return el->version;

    return -1;
}
C_CODE

close OUT;
