#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
use FindBin '$Bin';
use lib "$Bin/../lib";

use IO::All;
use Data::Dumper;
use YAML::PP;
use YAML::PP::Dumper;
use File::Basename qw/ basename dirname /;
use HTML::Entities qw/ encode_entities /;
use YAML::PP::Highlight;
use JSON::XS ();
use Encode;

chomp(my $version = qx{git describe --dirty});
my $yaml_test_suite = 'test-suite/yaml-test-suite-data/';
my @dirs = grep { m{/[0-9A-Z]{4}$} } map { "$_" } io->dir($yaml_test_suite)->all;
my @subdirs;
for my $dir (@dirs) {
    if (-f "$dir/in.yaml") {
        push @subdirs, $dir;
    }
    else {
        push @subdirs, grep { m{/[0-9]+$} } map { "$_" } io->dir($dir)->all;
    }
}
@subdirs = sort @subdirs;
my @valid = grep { not -f "$_/error" } @subdirs;
my @invalid = grep { -f "$_/error" } @subdirs;

my %tags;
for my $tagdir (io->dir("$yaml_test_suite/tags")->all) {
    for my $id (io->dir($tagdir)->all) {
        my $tag = basename $tagdir;
        push @{ $tags{ basename $id } }, basename $tag;
    }
}

my $html = <<"EOM";
<html>
<head>
<title>YAML Test Suite Highlighted</title>
<link rel="stylesheet" type="text/css" href="css/yaml.css">
<style>
body {
    font-family: Arial;
}
table.highlight {
    border: 1px solid #bbb;
    border-collapse: collapse;
    box-shadow: 2px 2px 4px 1px grey;
}
table.highlight tr th, table.highlight tr td {
    border: 1px solid #bbb;
    background-color: white;
    padding: 2px 3px 2px 3px;

}
td.error {
    background-color: #ff7777;
}
td.diff {
    background-color: #ffff77;
}
td.ok {
    background-color: #7777ff;
}
span.anchor { color: green; }
span.indent { background-color: #e8e8e8; }
span.dash { font-weight: bold; color: magenta; }
span.colon { font-weight: bold; color: magenta; }
span.question { font-weight: bold; color: magenta; }
span.yaml_directive { color: cyan; }
span.tag_directive { color: cyan; }
span.tag { color: blue; }
span.comment { color: grey; }
span.eol { color: grey; }
span.alias { color: green; }
span.singlequote { font-weight: bold; color: green; }
span.doublequote { font-weight: bold; color: green; }
span.singlequoted { color: green; }
span.doublequoted { color: green; }
span.literal { font-weight: bold; color: magenta; }
span.folded { font-weight: bold; color: magenta; }
span.doc_start { font-weight: bold; }
span.doc_end { font-weight: bold; }
span.block_scalar_content { color: #aa7700; border-left: 2px solid #999; margin-left: -2px; }
span.tab { background-color: lightblue; }
span.error { background-color: #ff8888; }
span.trailing_space { border: 1px solid red; margin: -1px; background-color: #eee; }
span.flowseq_start { font-weight: bold; color: magenta; }
span.flowseq_end { font-weight: bold; color: magenta; }
span.flowmap_start { font-weight: bold; color: magenta; }
span.flowmap_end { font-weight: bold; color: magenta; }
span.flow_comma { font-weight: bold; color: magenta; }

pre {
    background-color: white;
    padding: 2px;
}
pre.error {
    border: 1px solid red;
}
pre.diff {
    border: 1px solid #ff9900;
}
</style>
</head>
<body>
<a href="test-suite.html">YAML Test Suite Test Cases</a>
| <a href="schema-examples.html">Schema examples</a>
| <a href="schemas.html">Schema comparison</a><hr>
Generated with YAML::PP $version<br>
<a href="#valid">Valid (@{[ scalar @valid ]})</a><br>
<a href="#invalid">Invalid (@{[ scalar @invalid ]})</a><br>
EOM

my $ypp = YAML::PP->new(
    boolean => 'JSON::PP',
    schema => [qw/ Core /],
);
my $table;
for my $dir (sort @valid) {
    my $test = highlight_test($dir);
    $table .= $test;
}
$html .= <<"EOM";
<h1><a name="valid">Valid</a></h1>
<table class="highlight">
<tr>
<td></td>
<td>YAML::PP::Highlight</td>
<td>YAML::PP::Loader | Data::Dump</td>
<td>YAML::PP::Loader | JSON::XS</td>
<td>YAML::PP::Loader | YAML::PP::Dumper</td>
<td>YAML::PP::Parser | YAML::PP::Emitter</td>
</tr>
$table
</table>
EOM
$table = '';

for my $dir (sort @invalid) {
    my $test = highlight_test($dir);
    $table .= $test;
}
$html .= <<"EOM";
<h1><a name="invalid">Invalid</a></h1>
<table class="highlight">
<tr>
<td></td>
<td>YAML::PP::Highlight</td>
<td>YAML::PP::Loader | Data::Dump</td>
<td>YAML::PP::Loader | JSON::XS</td>
<td>YAML::PP::Loader | YAML::PP::Dumper</td>
<td>YAML::PP::Parser | YAML::PP::Emitter</td>
</tr>
$table
</table>
EOM

sub highlight_test {
    my ($dir) = @_;
    my $html;
    my $file = "$dir/in.yaml";
    my $id = basename $dir;
    my $main_id = $id;
    if ($id !~ tr/0-9//c) {
        $main_id = basename(dirname $dir);
        $id =  "$main_id/$id";
    }
    my $title = io->file("$dir/===")->slurp;
    my $yaml;

    warn "$id\n";
    $yaml = do { open my $fh, '<', $file or die $!; local $/; <$fh> };
    $yaml = decode_utf8 $yaml;

    my $class = "ok";
    my @docs;
    eval {
        @docs = $ypp->load_string($yaml);
    };
    my $error = $@ || '';
    my $tokens = $ypp->loader->parser->tokens;
    my $diff = 0;
    if ($error) {
        $error =~ s{\Q$Bin/../lib/}{};
        $class = "error";
        my $remaining_tokens = $ypp->loader->parser->_remaining_tokens;
        push @$tokens, map { +{ %$_, name => 'ERROR' } } @$remaining_tokens;
        my $out = join '', map {
            my $value = $_->{value};
            my $sub = $_->{subtokens};
            if ($sub) {
                $value = join '', map {
                    defined $_->{orig} ? $_->{orig} : $_->{value}
                } @$sub;
            }
            $value;
        } @$tokens;
        if ($out ne $yaml) {
            local $Data::Dumper::Useqq = 1;
            warn __PACKAGE__.': '.__LINE__.$".Data::Dumper->Dump([\$out], ['out']);
            warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$yaml], ['yaml']);
            warn "$id error diff";
            $diff = 1;
        }
    }
    else {
        my $out = join '', map {
            my $value = $_->{value};
            my $sub = $_->{subtokens};
            if ($sub) {
                $value = join '', map {
                    defined $_->{orig} ? $_->{orig} : $_->{value}
                } @$sub;
            }
            $value;
        } @$tokens;
        if ($out ne $yaml) {
            $class = "diff";
            local $Data::Dumper::Useqq = 1;
            warn __PACKAGE__.': '.__LINE__.$".Data::Dumper->Dump([\$out], ['out']);
            warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$yaml], ['yaml']);
            warn "$id diff";
            $diff = 1;
        }
    }

    my $coder = JSON::XS->new->ascii->pretty->allow_nonref->canonical;
    my $json_dump = join "\n", map {
        "Doc " . ($_+1) . ': ' . $coder->encode( $docs[ $_ ] );
    } 0 .. $#docs;

    my $yppd = YAML::PP->new( boolean => 'JSON::PP' );
    my $yaml_dump = $yppd->dump_string(@docs);

    my @reload_docs = $ypp->load_string($yaml_dump);
    my $reload_tokens = $ypp->loader->parser->tokens;

    my $dd = eval { require Data::Dump; 1 };
    my $data_dump = join "\n", map {
        if ($dd) {
            '$doc' . ($_ + 1) . ' = ' . Data::Dump::dump( $docs[ $_ ] );
        }
        else {
            local $Data::Dumper::Useqq = 1;
            local $Data::Dumper::Sortkeys = 1;
            Data::Dumper->Dump([$docs[ $_ ]], ['doc' . ($_ + 1)]);
        }
    } 0 .. $#docs;


    my $emit_yaml = '';
    {
        my @events;
        my $parser = YAML::PP::Parser->new(
            receiver => sub {
                my ($self, @args) = @_;
                push @events, [@args];
            },
        );
        eval {
            $parser->parse_string($yaml);
        };
        if ($@) {
#            warn "Error parsing: $@";
        }
        else {
            my $writer = YAML::PP::Writer->new;
            my $emitter = YAML::PP::Emitter->new();
            $emitter->set_writer($writer);
            eval {
                for my $event (@events) {
                    my ($type, $info) = @$event;
                    if ($type eq 'sequence_start_event' or $type eq 'mapping_end_event') {
                        delete $info->{style};
                    }
                    $emitter->$type($info);
                }
            };
            if ($@) {
                warn __PACKAGE__.':'.__LINE__.": Error emitting $id: $@\n";
            }
            $emit_yaml = $emitter->writer->output;
        }
    }
    my @emit_docs = eval { $ypp->load_string($emit_yaml) };
    my $emit_tokens = $ypp->loader->parser->tokens;




    $title = decode_utf8($title);
    $title = encode_entities($title);
    $error =~ s{\Q$Bin/../lib/}{}g;
    $error = encode_entities($error);
    $yaml = encode_entities($yaml);
    $data_dump = encode_entities($data_dump);
    $json_dump = encode_entities($json_dump);
    $yaml_dump = encode_entities($yaml_dump);
    my $taglist = join ', ', @{ $tags{ $id } || [] };
    $html .= <<"EOM";
<tr>
<td colspan="6" valign="top" style="background-color: #dddddd"><b>$id - $title</b></td></tr>
<tr>
<td style="max-width: 15em;" valign="top" >Tags:<br>$taglist<br>
<a href="https://github.com/yaml/yaml-test-suite/blob/master/src/$main_id.yaml">View source</a><br>
</td>
EOM
    my $high = YAML::PP::Highlight->htmlcolored($tokens);
    my $reload_high = YAML::PP::Highlight->htmlcolored($reload_tokens);
    my $emit_high = YAML::PP::Highlight->htmlcolored($emit_tokens);
    my $orig = $diff ? qq{<br><pre>$yaml</pre>} : '';
    $html .= <<"EOM";
<td style="max-width: 20em; overflow-x: auto;" valign="top"><pre class="$class">$high</pre>
<pre>$error</pre>
$orig
</td>
<td valign="top" style="max-width: 20em; overflow-x: auto;">
<pre>$data_dump</pre>
</td>
<td valign="top" style="max-width: 20em; overflow-x: auto;">
<pre>$json_dump</pre>
</td>
<td valign="top" style="max-width: 20em; overflow-x: auto;">
<pre>$reload_high</pre>
</td>
<td valign="top" style="max-width: 20em; overflow-x: auto;">
<pre>$emit_high</pre>
</td>
</tr>
EOM
    return $html;

}

$html .= <<"EOM";
</body></html>
EOM

binmode STDOUT, ":encoding(utf-8)";
say $html;
