#!/usr/bin/perl -w
use strict;

my $VERSION = "0.02";

my $count_type = "unsigned char";
my @model=("", qw(I II III IV V VI VII VIII IX));
my $models = @model;
my (%char_count);
for (@model) {
    $char_count{$_}++ for split //;
}
my @m_length = map length, @model;
# Notice we can skip 0 here. 0 is sort of special anyways
my $check_start = 0;
my @demand = sort {$m_length[$b] <=> $m_length[$a]} $check_start..$#model;

my $try_char = ord "a";
my @try_val = map stringify(join("", map chr($try_char++), 1..($_||1))), @m_length;
my $try_val = join(", ", @try_val);

my $generator = stringify("generated by $0 v$VERSION");
print <<"PRE";
#include <stdio.h>
#include <string.h>
/* #include <math.h> */

const char GENERATOR[]=$generator;
int check_start = $check_start;
unsigned char assign[256];
unsigned char val[$models][16] = { $try_val };
unsigned int val_len[$models];
unsigned int model_len[$models];
PRE
print "char *model[$models] = { ", join(", ", map stringify($_), @model), "};\n";

my %char_names;
for (keys %char_count) {
    # Do some transform here if some chars are invalid in C-identifiers
    $char_names{$_} = $_;
}
my @chars = sort {$char_names{$a} cmp $char_names{$b}} keys %char_names;
for (@chars) {
    print<<"PRE";
$count_type $char_names{$_}_set[256];
PRE
}

print <<"PRE";
int assigner(void);

int main(void) {
    int i;

    memset(assign, 0, sizeof(assign));
PRE
for (@chars) {
    print <<"INIT";
    memset($char_names{$_}_set, 0, sizeof($char_names{$_}_set));
INIT
}
print <<"PRE";
    for (i=0; i<$models; i++) model_len[i] = strlen(model[i]);

    /* Sanity check */
    assigner();
    printf("0=0\\n");
    fflush(stdout);

    /* Set up a val try loop here */
    if (assigner()) {
        printf("found with these params....\\n");
        fflush(stdout);
    }
    return 0;
}

PRE

my @ps = map { 
    my $val = $_;
    map "i$val$_", 0..length($model[$_])-1
} @demand;
my $ps = join(", ", @ps);
print <<"EOF";
int assigner(void) {
    int i, found=0;
    unsigned int $ps;

    for (i=0; i<$models; i++) {
        val_len[i] = strlen(val[i]);
        if (val_len[i] < model_len[i]) return 0;
    }

EOF
demand("    ", 0, 0);
print "    return found;\n}\n";

sub demand {
    my ($indent, $todo, $pos) = @_;
    if ($todo == @demand) {
        result($indent);
        return;
    }
    my $val = $demand[$todo];
    my $str = $model[$val];
    if ($pos == length($str)) {
        demand($indent, $todo+1, 0);
        return;
    }
    my $char = substr($str, $pos, 1);
    
    my $iid = "i$val$pos";
    my $cid = "c$val$pos";
    my $lid = "L$val$pos";
    my $off = $m_length[$val]-$pos-1;
    $off = $off ? "-$off" : "";
    my $from = $pos ? "i$val" . ($pos-1) . "+1" : 0;
    print $indent, "for ($iid=$from; $iid < val_len[$val]$off; $iid++) {\n";
    my $in = $indent . "  ";
    my $out = <<"STR";
unsigned int $cid = val[$val][$iid];
/* printf("${indent}Try %c for $char ($iid=%d)\\n", $cid, $iid); */
if (assign[$cid] && assign[$cid] != model[$val][$pos]) continue;
$char_names{$char}_set[$cid]++;
assign[$cid] = model[$val][$pos];
for (i=check_start; i<$models; i++) {
    const char *m = model[i];
    const char *v;
    for (v = val[i]; *v; v++) {
        unsigned char av = assign[*v];
        if (av == 0) continue;
        while (*m != av) {
            if (*m == 0) goto $lid;
            m++;
        }
        m++;
    }
}
STR
    $out =~ s/^/$in/mg;
    print $out;

    demand($in, $todo, $pos+1);

    $out = <<"STR";
 $lid:
  if (--$char_names{$char}_set[$cid]) break;
  else assign[$cid] = 0;
}
STR
    $out =~ s/^/$indent/mg;
    print $out;
}

sub result {
    my $indent = shift;

    my $out = <<"STR";
found=1;
for (i=0; i<$models;i++) {
    fputs(val[i], stdout);
    putchar(' ');
}
fputs("=>", stdout);
for (i=0; i<$models;i++) {
    unsigned char *v;
    putchar(' ');
    for (v=val[i]; *v; v++) putchar(assign[*v] ? assign[*v] : *v);
}
STR
    $out =~ s/^/$indent/mg;
    print $out;
    for (@chars) {
        
        my $out = <<"STR";
fputs(" $_=[", stdout);
for (i=0; i<256; i++) if (assign[i] == '$_') putchar(i);
putchar(']');
STR
        $out =~ s/^/$indent/mg;
        print $out;
    }
    print $indent, "putchar('\\n');\n";
}

sub stringify {
    my $str = shift;
    $str =~ s/([\"\\])/\\$1/g;
    return "\"$str\"";
}
