The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
#

use strict; use warnings;

use Test::More tests => 341;

BEGIN {
    use_ok('Data::Dumper');
    use_ok('UNIVERSAL::require');
    use_ok('List::MoreUtils', qw/uniq/);

    use_ok('Business::EDI');
    use_ok('Business::EDI::Spec');
    use_ok('Business::EDI::Test', qw/JSONObject2Perl ordrsp_data/);
}

sub sg_sort {
    return Business::EDI::Spec->sg_sort(@_);
}
sub spec_version_sort {
    return Business::EDI::Spec->spec_version_sort(@_);
}

use vars qw/%code_hash $slurp $perl/;

my $debug = $Business::EDI::debug = $Business::EDI::Spec::debug = @ARGV ? shift : 0;

my $edi = Business::EDI->new('d08a') or die "Business::EDI->new('d08a') failed";

ok($perl = ordrsp_data, "DATA handle read and decode" );

$perl or die "DATA handle not decoded successfully";
# note("ref(\$obj): " . ref($perl));
# note("    \$obj : " .     $perl );

$Data::Dumper::Indent = 1;

ok($perl->{body},      "EDI interchange body");
ok($perl->{body}->[0], "EDI interchange body is a populated arrayref!");
is(scalar(@{$perl->{body}}), 3, "EDI interchange body has 3 messages");

is($edi->spec->syntax, 40100, "\$edi->spec->syntax");

my $spec;
   $spec = $edi->spec->get_spec('segment');
ok($spec,"\$edi->spec->get_spec('segment')");

my @failed;

foreach (qw/
ADR  AGR  AJT  ALC  ALI  APP  APR  ARD  ARR  ASI  ATT  AUT  BAS  BGM  BII
BUS  CAV  CCD  CCI  CDI  CDS  CDV  CED  CIN  CLA  CLI  CMP  CNI  CNT  COD
COM  COT  CPI  CPS  CPT  CST  CTA  CUX  DAM  DFN  DGS  DII  DIM  DLI  DLM
DMS  DOC  DRD  DSG  DSI  DTM  EDT  EFI  ELM  ELU  ELV  EMP  EQA  EQD  EQN
ERC  ERP  EVE  FCA  FII  FNS  FNT  FOR  FSQ  FTX  GDS  GEI  GID  GIN  GIR
GOR  GPO  GRU  HAN  HYN  ICD  IDE  IFD  IHC  IMD  IND  INP  INV  IRQ  LAN
LIN  LOC  MEA  MEM  MKS  MOA  MSG  MTD  NAD  NAT  PAC  PAI  PAS  PCC  PCD
PCI  PDI  PER  PGI  PIA  PNA  POC  PRC  PRI  PRV  PSD  PTY  PYT  QRS  QTY
QUA  QVR  RCS  REL  RFF  RJL  RNG  ROD  RSL  RTE  SAL  SCC  SCD  SEG  SEL
SEQ  SFI  SGP  SGU  SPR  SPS  STA  STC  STG  STS  TAX  TCC  TDT  TEM  TMD
TMP  TOD  TPL  TRU  TSR  VLI
/) {
    ok($spec->{$_}, "\$edi->spec->get_spec('segment')->{$_}") or push @failed, $_;
}

my $i=0;
if ($debug) {
    print "ALL segment keys:\n";
    foreach (sort keys %$spec) {
        print $_, (++$i % 15 == 0 ? "\n" : "  ");
    }
    print "\n";
}

if (@failed) {
    $i=0;
    diag("FAILED segment codes:");
    foreach (@failed) {
        print $_, (++$i % 15 == 0 ? "\n" : "  ");
    }
    diag();
}

my $msgcode = 'ORDRSP';
   $spec = $edi->spec->get_spec('message');
ok($spec,"\$edi->spec->get_spec('message')");
ok($spec->{$msgcode}, "\$edi->spec->get_spec('message')->{$msgcode}");
#print "Dump of ORDRSP spec: ", Dumper($spec->{ORDRSP});

my $sg_spec = $edi->spec->get_spec('segment_group');
ok($sg_spec,"\$edi->spec->get_spec('segment_group')");
ok($sg_spec->{$msgcode}, "\$edi->spec->get_spec('segment_group')->{$msgcode}");

is_deeply($sg_spec->{$msgcode}->{SG26}, $spec->{"$msgcode/SG26"}, "SG_SPECS->{$msgcode}->{SG26} === MSG_SPECS->{'$msgcode/SG26'}");

sub sg_expander {
    my ($countsref, $mapref, $msgcode, $parts, $depth) = @_;
    $depth ||= 1;
    $debug and note("\$edi->spec->get_spec('message')->xpath('$msgcode')");
    foreach my $part (@$parts) {
        my $code = $part->{code};
        $countsref->{$depth}->{$code}++;
        $mapref->{$depth}->{$code} = "$msgcode/$code";
        $debug and note("\$edi->spec->get_spec('message')->xpath('$msgcode/$code') " . $countsref->{$depth}->{$code});
    }
    my $i = scalar @$parts;
    my $spec_key = $msgcode;
    $spec_key =~ s#/.+##; # and print "SPEC_KEY stripped: $spec_key\n";
    foreach (grep {/^SG\d+$/} map {$_->{code}} @$parts) {
        $debug and note("Depth=" . ($depth+1) . " check $msgcode/$_ ($spec_key)");
        $i += sg_expander($countsref, $mapref, "$msgcode/$_", $spec->{"$spec_key/$_"}->{parts}, $depth+1);
        # recursion for depth!
    }
    return $i;
}

my %scratch  = ();
my %deep_map = ();
my %counts   = ();

my @keys = sort {&sg_sort($a,$b)} map {$_->{code}} @{$spec->{$msgcode}->{parts}};
foreach my $key (@keys) {
    $key =~ /SG\d+$/ or next;
    $sg_spec->{$msgcode}->{$key} and
        is_deeply($sg_spec->{$msgcode}->{$key},     $spec->{"$msgcode/$key"},
                "\$sg_spec->{$msgcode}->{$key} vs. \$spec->{'$msgcode/$key'}");
    my @parts = @{$spec->{"$msgcode/$key"}->{parts}};
    sg_expander(\%counts, \%scratch, "$msgcode/$key", \@parts);
}

my @single_keys = grep {$counts{1}->{$_} == 1} keys %{$counts{1}};
my @mutli_keys  = grep {$counts{1}->{$_}  > 1} keys %{$counts{1}};
is(scalar @single_keys, 56, "Depth 1 Deeploadable keys: 56");
is(scalar @mutli_keys, 10, "Depth 1 NOT Deeploadable keys: 10");

if ($debug) {
    my @depths = sort keys %counts;
    DEPTH: foreach my $d (@depths) {
        my @single = sort {&sg_sort($a,$b)} grep {$counts{$d}->{$_} == 1} keys %{$counts{$d}};
        my @mutli  = sort {&sg_sort($a,$b)} grep {$counts{$d}->{$_}  > 1} keys %{$counts{$d}};

        KEY: foreach my $key (@single) {
            foreach my $other (grep {$_ < $d} @depths) {    # check shallower depths (we go in order, so we haven't seen deeper ones yet)
                if ($counts{$other}->{$key}) {
                    printf "Blocked %-4s %2d hits (already at depth=$other)\n", $key, $counts{$other}->{$key};
                    next KEY;
                }
            }
            $deep_map{$key} and warn "Internal ERROR: $key is already deepmapped??";
            $deep_map{$key} = $scratch{$d}->{$key};
            printf "        %-4s => %-25s (depth=$d)\n", $key, $deep_map{$key};
        }
        foreach my $key (@mutli) {
            printf "MUTLI   %-4s %2d hits   %s\n", $key, $counts{$d}->{$key},
                join(", ",
                    map {sprintf "%2d\@depth=$_", $counts{$_}->{$key}}
                    grep {$_ != $d and $counts{$_}->{$key}}
                    @depths
                );
        }
    }
}

$i=0;
foreach my $part (@{$perl->{body}}) {
    $i++;
    next unless ok((ref $part and scalar keys %$part), "EDI interchange message $i has structure.");
    foreach my $key (keys %$part) {
        next unless ok($key eq 'ORDRSP', "EDI interchange message $i type == ORDRSP");
        my $ordrsp;
        ok($ordrsp = $edi->message($key, $part->{$key}), "EDI $key object via \$edi->message($key, ...)");
    }
}

my $specs = {};
my $sgs = {};
my @versions = qw/
 1901   1902   1911   1921   2932
 d93a   s93a   d94a   d94b   d95a   d95b   d96a   d96b
 d97a   d97b   d98a   d98b   d99a   d99b
 d00a   d00b   d01a   d01b   d01c
 d02a   d02b   d03a   d03b   d04a
 d04b   d05a   d05b   d06a   d06b   d07a   d07b   d08a
/;

my @sorted_vers = sort {spec_version_sort($a,$b)} qw/
 d04b   d05a   d05b   d06a   d06b   d07a   d07b   d08a
 1901   1902   2932   1921   1911
 d00a   d00b   d01c   d01a   d01b
 d95a   d95b   d96a   d96b   s93a   d93a   d94a   d94b
 d97a   d97b   d98a   d98b   d99a   d99b
 d02b   d02a   d03a   d03b   d04a
/;
# We check whether the arrays match later

foreach (@versions) {
    ok($specs->{$_} = Business::EDI->new(version => $_)->spec, "Business::EDI->new(version => '$_')->spec()") or next;
    ok($specs->{$_}->spec_page('message'),                     "Business::EDI->new(version => '$_')->spec->spec_page('message')") or next;;
}


my @msgs = qw/ ORDERS ORDRSP INVOIC OSTRPT /;

note "Analyzing " . scalar(@versions) . " spec versions";

foreach my $v (@versions) {
    my @allkeys = sort keys %{$specs->{$v}->spec_page('message')};
    my @msgtypes = grep {!/\//} @allkeys;
    note "\n$v has " . sprintf("%3d", scalar(@msgtypes)) . " message types (eg: " . join(' ', @msgtypes[0..3]) . " ...)";
    foreach my $msg (@msgs) {
        my @sgkeys = sort {sg_sort($a, $b)} grep {/^$msg\/SG/} @allkeys;
        note "$v $msg has " . sprintf("%3d", scalar(@sgkeys)) . " Segment Groups";
        foreach my $fullkey (@sgkeys) {
            $_ = $fullkey;
            s/^.*\///;
            $sgs->{$v}->{$msg}->{$_} = $specs->{$v}->spec_page('message')->{$fullkey};
            my @parts = @{ $sgs->{$v}->{$msg}->{$_}->{parts} };
            note sprintf("$v %11s has %3d parts: ", $fullkey, scalar(@parts)) . join " ", map {$_->{code}} @parts;
        }
    }
}



my @unsorted = qw# SG47 SG18 ORDRSP/SG26 INVOIC/SG26 INVOIC/SG1 ORDRSP/SG4 SG1 #;
note "sorted: " . join " ", sort{sg_sort($a,$b)} @unsorted;
is_deeply(\@sorted_vers, \@versions, "spec_version_sort");


my $terms = {
    line_detail => {
        '1911' => "SG25",
        'd94b' => "SG25",
        'd95a' => "SG26",
        'd05b' => "SG26",
        'd06a' => "SG27",
        'd11a' => "SG27",   # hypothetical
    },
    party => {
        '1911' => "SG2",
        'd94b' => "SG2",
        'd95a' => "SG3",
        'd95b' => "SG3",
    },
    currency => {
        '1911' => "SG7",
        'd94b' => "SG7",
        'd95a' => "SG8",
        'd11a' => "SG8",    # hypothetical
    },
    payment_terms => {
        '1911' => "SG8",
        'd94b' => "SG8",
        'd95a' => "SG9",
        'd11a' => "SG9",    # hypothetical
    },
    transport => {
        '1911' => "SG9",
        'd94b' => "SG9",
        'd95a' => "SG10",
        'd11a' => "SG10",    # hypothetical
    },
    delivery_terms => {
        '1911' => "SG11",
        'd94b' => "SG11",
        'd95a' => "SG12",
        'd11a' => "SG12",    # hypothetical
    },
    delivery_schedule => {
        '1911' => "SG15",
        'd94b' => "SG15",
        'd95a' => "SG16",
        'd97b' => "SG16",
        'd11a' => "SG16",    # hypothetical
    },
    packaging => {
        '1911' => "SG12",
        'd94b' => "SG12",
        'd95a' => "SG13",
        'd11a' => "SG13",    # hypothetical
    },
    mark_label => {
        '1911' => "SG13",
        'd94b' => "SG13",
        'd95a' => "SG14",
        'd11a' => "SG14",    # hypothetical
    },
    handling => {
        '1911' => "SG14",
        'd94b' => "SG14",
        'd95a' => "SG15",
        'd11a' => "SG15",    # hypothetical
    },
    APR => {
        '1911' => "SG17",
        'd94b' => "SG17",
        'd95a' => "SG18",
        'd11a' => "SG18",    # hypothetical
    },
    allowance => {
        '1911' => "SG18",
        'd94b' => "SG18",
        'd95a' => "SG19",
        'd11a' => "SG19",    # hypothetical
    },
    requirement => {
        '1911' => "SG24",
        'd94b' => "SG24",
        'd95a' => "SG25",
        'd11a' => "SG25",    # hypothetical
    },
    line_reference => {
        '1911' => "SG25/SG27",
        '1921' => "SG25/SG28",
        'd94b' => "SG25/SG28",
        'd95a' => "SG26/SG30",
        'd95b' => "SG26/SG30",
        'd96a' => "SG26/SG31",
        'd97a' => "SG26/SG31",
        'd05b' => "SG26/SG31",
        'd06a' => "SG27/SG32",
        'd11a' => "SG27/SG32",  # hypothetical
    },

};

my @uniq;
foreach my $term (sort keys %$terms) {
    push @uniq, keys %{$terms->{$term}};
}
@uniq = uniq @uniq, '1100', '0000', '0900', 'zzzz', 's93a', 'd93a';

note "spec_version_sort: " . join(' ', sort {spec_version_sort($a,$b)} @uniq);

foreach my $term (sort keys %$terms) {
    foreach (sort {spec_version_sort($a,$b)} keys %{$terms->{$term}}) {
        $debug and note "is(Business::EDI::Spec->metamap('ORDRSP', $term, $_), " . $terms->{$term}->{$_} . ", 'ORDRSP/$term in $_')";
        my $result = Business::EDI::Spec->metamap('ORDRSP', $term, $_);
        is($result, $terms->{$term}->{$_}, "ORDRSP/$term in $_ => " . $terms->{$term}->{$_});
    }
}

#print Dumper($specs->{d96a}), "\n";
note("done");

__END__