use 5.012;
use FindBin;
use Path::Class;

use constant UNSAFE_DIGIT      =>  1;
use constant UNSAFE_ALPHA      =>  2;
use constant UNSAFE_SUBDELIMS  =>  4;
use constant UNSAFE_GENDELIMS  =>  8;
use constant UNSAFE_RESERVED   => 16;
use constant UNSAFE_UNRESERVED => 32;
use constant UNSAFE_PCHAR      => 64;

my $scheme           = unsafe_generate(UNSAFE_ALPHA|UNSAFE_DIGIT, "+-.");
my $user_info        = unsafe_generate(UNSAFE_UNRESERVED | UNSAFE_SUBDELIMS, ":");
my $host             = unsafe_generate(UNSAFE_UNRESERVED | UNSAFE_SUBDELIMS);
my $path             = unsafe_generate(UNSAFE_PCHAR, "/");
my $path_segment     = unsafe_generate(UNSAFE_PCHAR);
my $query            = unsafe_generate(UNSAFE_PCHAR, "/?");
my $query_param      = unsafe_generate(UNSAFE_UNRESERVED);
my $query_param_plus = unsafe_generate(UNSAFE_UNRESERVED);
my $fragment         = unsafe_generate(UNSAFE_PCHAR, "/?");

$query_param_plus->[ord ' '] = ord '+';

my $restore = [0..255];
$restore->[ord '%'] = 0;
$restore->[ord '+'] = ord ' ';

my @hex = map {ord} split '', "0123456789ABCDEF";
my $forward  = [];
my $backward = [];

for (my $c = 0; $c < 255; ++$c) {
    $forward->[$c][0] = $hex[($c >> 4) & 15];
    $forward->[$c][1] = $hex[($c & 15) & 15];
    $backward->[$c][0] = from_hex($c) << 4;
    $backward->[$c][1] = from_hex($c);
}

my $str = "// THIS FILE WAS GENERATED BY $0, DO NOT EDIT\n\n";

$str .= print_alphabet("scheme", $scheme);
$str .= print_alphabet("user_info", $user_info);
$str .= print_alphabet("host", $host);
$str .= print_alphabet("path", $path);
$str .= print_alphabet("path_segment", $path_segment);
$str .= print_alphabet("query", $query);
$str .= print_alphabet("query_param", $query_param);
$str .= print_alphabet("query_param_plus", $query_param_plus);
$str .= print_alphabet("fragment", $fragment);
$str .= "\n";
$str .= "static const char _restore[256] = {".join(", ", map {pval($_)} @$restore)."};\n";
$str .= "\n";
$str .= "static const char _forward [256][2] = ".join_fwdback($forward).";\n";
$str .= "static const char _backward[256][2] = ".join_fwdback($backward).";\n";

my $file = Path::Class::Dir->new($FindBin::Bin)->parent->file("src/panda/uri/encode_gen.icc");
$file->spew($str);
say "$file written";

sub unsafe_generate {
    my $ret = [(0) x 256];
    _unsafe_generate($ret, @_);
    return $ret;
}

sub _unsafe_generate {
    my ($list, $flags, $chars) = @_;
    if ($flags & UNSAFE_DIGIT)      { _unsafe_generate($list, 0, "0123456789"); }
    if ($flags & UNSAFE_ALPHA)      { _unsafe_generate($list, 0, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); }
    if ($flags & UNSAFE_SUBDELIMS)  { _unsafe_generate($list, 0, "!$&'()*+,;="); }
    if ($flags & UNSAFE_GENDELIMS)  { _unsafe_generate($list, 0, ":/?#[]@"); }
    if ($flags & UNSAFE_RESERVED)   { _unsafe_generate($list, UNSAFE_SUBDELIMS | UNSAFE_GENDELIMS); }
    if ($flags & UNSAFE_UNRESERVED) { _unsafe_generate($list, UNSAFE_ALPHA | UNSAFE_DIGIT, "-._~"); }
    if ($flags & UNSAFE_PCHAR)      { _unsafe_generate($list, UNSAFE_UNRESERVED | UNSAFE_SUBDELIMS, ":@"); }
    
    my @chars = $chars ? (split '', $chars) : ();
    $list->[ord] = ord for @chars;
}

sub from_hex {
    my $c = shift;
    return $c - ord('0') if $c >= ord('0') && $c <= ord('9');
    
    $c = ord(lc chr $c);
    return 0 if $c < ord('a') or $c > ord('f');
    
    return $c - ord('a') + 10;
}

sub print_alphabet {
    my ($name, $data) = @_;
    my $spc = " " x (16 - length($name));
    return "const char URIComponent::${name}[256] $spc= {".join(", ", map {pval($_)} @$data)."};\n";
}

sub join_fwdback {
    my $data = shift;
    my $ret = "{";
    $ret .= join(", ", map {"{".pval($_->[0]).", ".pval($_->[1])."}"} @$data);
    $ret .= "}";
    return $ret;
}

sub pval {
    my $val = shift;
    return $val unless $val;
    return $val if $val <= 127;
    return "(char)$val";
}
