#!/usr/bin/perl
use strict;
use warnings;
use Slay::Maker;
use File::Next;
use Path::Class;
use Template;
use File::Cache::Parsed;
use Cwd 'abs_path';
use Text::Restructured;
use Text::Restructured::Writer::LibXML;
use XML::LibXML;
use XML::LibXSLT;
use YAML::Syck;
use Getopt::Long;

my $SRCPATH='src';
my $DSTPATH='dst';
my $DSTBASEURL='/';

{
my $res=GetOptions('src|s=s'=>$SRCPATH,
                   'dst|d=s'=>$DSTPATH,
                   'url|u=s'=>$DSTBASEURL,
               );
exit 1 unless $res;
}

my $stash={};
my $template_provider=Template::Provider->new({
    INCLUDE_PATH=> 'common/',
    ABSOLUTE=>1,
    RELATIVE=>1,
});
my $template=Template->new({
    LOAD_TEMPLATES=>[$template_provider],
});
my $rest=Text::Restructured->new({},'WebCoso');
my $xml_parser=XML::LibXML->new();
my $xslt_proc=XML::LibXSLT->new();

my $fc=File::Cache::Parsed->new(follow=>1);
$fc->add_parser(qr{\.tt2?$} =>
                    sub {
                        $template->context->template($_[0]);
                    });
$fc->add_parser(qr{\.rest\.txt$} =>
                    sub {
                        my $dudom=$rest->Parse($_[1],$_[0]);
                        return Text::Restructured::Writer::LibXML
                            ->new->ProcessDOM($dudom);
                    });
$fc->add_parser(qr{\.xml$} =>
                    sub { $xml_parser->parse_string($_[1],$_[0]) });
$fc->add_parser(qr{\.xslt?$} =>
                    sub { $xslt_proc->parse_stylesheet
                              ($xml_parser->parse_string($_[1],$_[0])) });
$fc->add_writer(qr{\.xml$} =>
                    sub { $_[1]->toFile($_[0]) });
$fc->add_parser(qr{\.ya?ml$} =>
                       sub { Load($_[1]) });
$fc->add_writer(qr{\.ya?ml$} =>
                       sub { DumpFile($_[0],$_[1]) });


sub dstUriFor {
    my ($name)=@_;
    $name=~s{^\Q$SRCPATH\E/}{$DSTBASEURL};
    return $name;
}

sub isLang {
    my ($lang,$name)=@_;
    return 1 if $name=~m{/$}; # assume that MultiViews on the server will work
    return 1 if $name=~m{/document\.$lang(\.|$)};
    return;
}

$stash->{dstUriFor}=\&dstUriFor;
$stash->{isLang}=\&isLang;

sub expandTT {
    my ($maker,$target,$deps,$matches)=@_;

    my $tmpl=$fc->get($deps->[-1]);
    my $vars={ path=> $matches->[0],
               language => $matches->[1],
               %$stash,
           };
    if (@$deps>1) {
        $vars->{tagged}=$fc->get($deps->[0]);
    }
    push @{$template_provider->include_path},
        file($deps->[-1])->parent;
    $fc->put($target,
             $template->context->process($tmpl,
                                         $vars));
    pop @{$template_provider->include_path};
}

sub parseRST {
    my ($maker,$target,$deps,$matches)=@_;

    $fc->put($target,$fc->get($deps->[-1]));
}

sub du2html {
    my ($maker,$target,$deps,$matches)=@_;

    my $du=$fc->get($deps->[-1]);
    my $xslt=file($deps->[-1])->parent->file('du2html.xsl');
    $xslt=$fc->get($xslt);
    my $out=$xslt->transform($du,
                             XML::LibXSLT::xpath_to_string(
                                 path => $matches->[0],
                                 language => $matches->[1],
                             ));
    $fc->put($target,$xslt->output_string($out));
}

{my $xpath=XML::LibXML::XPathContext->new();
$xpath->registerNs('x', 'http://www.w3.org/1999/xhtml');
sub getTags {
    my ($maker,$target,$deps,$matches)=@_;

    my %tagged;
    for my $doc_name (@$deps) {
        my $doc=$fc->get($doc_name);
        my @tags=map {$_->textContent}
            $xpath->findnodes(
                q{/document/docinfo/field[field_name='tags']/field_body/*/list_item},
                $doc);
        chomp for @tags;
        push @{$tagged{$_}},$doc_name for @tags;
    }
    $fc->put($target,\%tagged);
}
}

sub getChanges {
    my ($maker,$target,$deps,$matches)=@_;

    print "changes: $target <- @$deps, @$matches\n";
    open my $fh,'>',$target;
}

sub ifExists {
    my ($src)=@_;
    return sub {
        my ($maker,$target,$matches)=@_;
        my $dep=Slay::MakerRule::var_expand_dep($src,$target,$matches);
        return if -e $target and ! -e $dep;
        return $dep;
    }
}

sub fromTo {
    my ($base,$opts)=@_;
    my $iter=File::Next::files(
        {
            file_filter=>$opts->{files},
            descend_filter=>$opts->{dirs},
        },
        $base);
    my (@ret,$file);
    if (defined $opts->{transform}) {
        push @ret,$opts->{transform}->($file) while $file=$iter->();
    }
    else {
        push @ret,$file while $file=$iter->();
    }
    return @ret;
}

{
my %order=(
    'document.rest.tt'=>0,
    'document.rest.txt'=>1,
    'document.du.xml'=>2,
);
sub earliest {
    my ($a,$b)=@_;
    return $a unless $b;
    return $order{$a} < $order{$b}
        ? $a
        : $b;
}
}

sub keepEarliest {
    my %dirs;
    for my $f (@_) {
        my $c=file($f);
        $dirs{$c->parent}=earliest($c->basename,$dirs{$c->parent});
    }
    my @ret;
    while (my ($d,$f)=each %dirs) {
        push @ret,file($d,$f)->stringify;
    }
    return @ret;
}

my %docfiles=(
    files=>sub{m{^document\.} and ($File::Next::dir !~ m{/\.svn/})},
    dirs=>sub{!m{^(tags|_webcoso)$}});

my $maker=Slay::Maker->new({
    rules => [
        ["$SRCPATH/(**)/document.(*).rest.txt",':',ifExists("$SRCPATH/\$1/document.\$2.rest.tt"),'=',\&expandTT],
        ["$SRCPATH/(**)/document.(*).du.xml",':',ifExists("$SRCPATH/\$1/document.\$2.rest.txt"),'=',\&parseRST],
        ["$SRCPATH/()document.(*).rest.txt",':',ifExists("$SRCPATH/document.\$2.rest.tt"),'=',\&expandTT],
        ["$SRCPATH/()document.(*).du.xml",':',ifExists("$SRCPATH/document.\$2.rest.txt"),'=',\&parseRST],
        ["$SRCPATH/_webcoso/tags.yml",':',fromTo("$SRCPATH/",{%docfiles,transform=>sub{my $s=shift;$s=~s{\.[^.]+\.[^.]+$}{.du.xml};$s}}),'=',\&getTags],
        #["$SRCPATH/_webcoso/changes.xml",':',keepEarliest(fromTo("$SRCPATH/",{%docfiles})),'=',\&getChanges],

        ["$SRCPATH/tags/(**)/document.(*).rest.txt",':',"$SRCPATH/_webcoso/tags.yml",ifExists("$SRCPATH/tags/\$1/document.\$2.rest.tt"),'=',\&expandTT],
        ["$SRCPATH/tags/(**)/document.(*).du.xml",':',"$SRCPATH/_webcoso/tags.yml",ifExists("$SRCPATH/tags/\$1/document.\$2.rest.txt"),'=',\&parseRST],
        ["$DSTPATH/tags/(**)/document.(*).html",':',"$SRCPATH/_webcoso/tags.yml","$SRCPATH/tags/\$1/document.\$2.du.xml",'=',\&du2html],
        ["$DESTPATH/(**)/document.(*).html",':',"$SRCPATH/_webcoso/tags.yml","$SRCPATH/\$1/document.\$2.du.xml",'=',\&du2html],
        ["$DESTPATH/()document.(*).html",':',"$SRCPATH/_webcoso/tags.yml","$SRCPATH/document.\$2.du.xml",'=',\&du2html],
    ],
    options => {
        auto_create_dirs => 1,
        #detect_no_diffs => 1,
        #detect_no_size_change => 1,
        #debug => 1,
    },
});

my @targets=(fromTo("$SRCPATH/",
                   {
                       %docfiles,
                       transform=>sub{
                           my $src=shift;
                           $src=~s{\.[^.]+\.[^.]+$}{.html};
                           $src=~s{^\Q$SRCPATH\E/}{$DSTPATH/};
                           return $src;
                       },
                   }),
    fromTo("$SRCPATH/tags/",
           {
               %tagfiles,
               transform=>sub{
                   my $src=shift;
                   $src=~s{\.[^.]+\.[^.]+$}{.html};
                   $src=~s{^\Q$SRCPATH\E/tags/}{$DSTPATH/tags/};
                   return $src;
               },
           }));

print "targets: @targets\n";
$maker->make(@targets);

