#!/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;
use WebCoso::Common;
use WebCoso::TT;

my @TMPLPATH=('common/');
my $CLEAN=0;

{
my $res=GetOptions('src|s=s'=>\$WebCoso::Common::SRCPATH,
                   'dst|d=s'=>\$WebCoso::Common::DSTPATH,
                   'url|u=s'=>\$WebCoso::Common::DSTBASEURL,
                   'include|I=s'=>\@TMPLPATH,
                   'clean'=>\$CLEAN,
               );
exit 1 unless $res;
$WebCoso::Common::SRCPATH=~s{/+$}{};
$WebCoso::Common::DSTPATH=~s{/+$}{};
$WebCoso::Common::DSTBASEURL=~s{/*$}{/};
}

my $rest=Text::Restructured->new(
    {
        D=>{
            'file-insertion-enabled'=>0, # we use TT
            generator=>0,
            date=>0,
            'time'=>0,
            'source-link'=>0,
            'section-subtitles'=>1,
        },
    },
    'WebCoso');

my $xml_parser=XML::LibXML->new();
my $xslt_proc=XML::LibXSLT->new();
my $xpath=XML::LibXML::XPathContext->new();
$xpath->registerNs('x', 'http://www.w3.org/1999/xhtml');

my $fc=File::Cache::Parsed->new(follow=>1);
$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]) });

my $template=WebCoso::TT->new(TMPLPATH=>\@TMPLPATH,fc=>$fc);

sub getTitleFor {
    my ($lang,$path,$name)=@_;

    my $doc_name=$name;
    $doc_name=~s{\.html$}{.du.xml};
    $doc_name=~s{/$}{/document.$lang.du.xml};
    if ($doc_name=~m{^\Q$WebCoso::Common::DSTBASEURL\E}) {
        $doc_name=~s{^\Q$WebCoso::Common::DSTBASEURL\E}{$WebCoso::Common::SRCPATH/};
    }
    else {
        $doc_name=file($doc_name)->absolute(file($path)->parent)->relative($WebCoso::Common::SRCPATH); # absolutize it
        $doc_name="$WebCoso::Common::SRCPATH/$doc_name";
    }
    warn "getTitleFor($lang,$path,$name)->$doc_name\n";

    my $doc=$fc->get($doc_name);
    unless ($doc) {
        warn "No document for <$doc_name>, returning <$name>\n";
        return $name;
    }
    my $title=$xpath->findnodes(
                q{/document/title},
                $doc);
    return $title;
}

my $NS='http://webcoso.thenautilus.net/';

$xslt_proc->register_function($NS,'title-for',\&getTitleFor);

{my $tags_source;
sub setXMLTagsSource {$tags_source=shift}
sub getTagsXML {
    my $doc=XML::LibXML::Document->new();
    return $doc unless defined $tags_source;

    warn "getTagsXML()\n";

    my $de=$doc->createElementNS($NS,'wc:tags');
    $doc->setDocumentElement($de);
    my ($tagname,$doclist);
    while (($tagname,$doclist)=each %$tags_source) {
        my $te=$doc->createElementNS($NS,'wc:tag');
        $te->setAttribute('name',$tagname);
        $de->appendChild($te);
        my %docs;
        push @{$docs{WebCoso::Common::dstUriFor($_)}},WebCoso::Common::langOf($_) for @$doclist;
        my ($docurl,$langs);
        while (($docurl,$langs)=each %docs) {
            my $dle=$doc->createElementNS($NS,'wc:doc');
            $dle->setAttribute('uri',$docurl);
            $te->appendChild($dle);
            for my $lang (@$langs) {
                my $le=$doc->createElementNS($NS,'wc:lang');
                $le->appendTextNode($lang);
                $dle->appendChild($le);
            }
        }
    }
    return $doc;
}
}
$xslt_proc->register_function($NS,'tagged',\&getTagsXML);

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);
    if (@$deps>1) {
        warn "xml tagging as $deps->[0]\n";
        setXMLTagsSource($fc->get($deps->[0]));
    }
    else {
        setXMLTagsSource(undef);
    }
    my $out=$xslt->transform($du,
                             XML::LibXSLT::xpath_to_string(
                                 path => $matches->[0],
                                 language => $matches->[1],
                                 filename => $deps->[-1],
                             ));
    $fc->put($target,$xslt->output_string($out));
}

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)=@_;

    warn "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=(
    'rest.tt'=>0,
    'rest.txt'=>1,
    '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);
        my $lang=WebCoso::Common::langOf($c->basename);
        my $type=WebCoso::Common::typeOf($c->basename);
        if (!defined $lang or !defined $type) {
            die "Weird document name <$f>";
        }
        $dirs{$c->parent}->{$lang}=earliest($type,$dirs{$c->parent}->{$lang});
    }
    my @ret;
    while (my ($d,$langs)=each %dirs) {
        while (my ($lang,$type)=each %$langs) {
            push @ret,file($d,"document.$lang.$type")->stringify;
        }
    }
    return @ret;
}
}

my %docfiles=(
    files=>sub{m{^document\.}},
    dirs=>sub{!m{^(tags|_webcoso|\.svn)$}});

if ($CLEAN) {
    my %to_keep;
    @to_keep{keepEarliest(fromTo($WebCoso::Common::SRCPATH,{%docfiles,dirs=>sub{!m{^(_webcoso|\.svn)$}}}))}=();
    @to_keep{fromTo($WebCoso::Common::SRCPATH,{files=>sub{!m{^document\.}},dirs=>sub{!m{^(_webcoso|\.svn)$}}})}=();

    my $iter=File::Next::files({descend_filter=>sub{!m{^\.svn$}}},$WebCoso::Common::SRCPATH,$WebCoso::Common::DSTPATH);
    while (defined (my $file=$iter->())) {
        next if exists $to_keep{$file};
        unlink $file;
    }
    dir($WebCoso::Common::DSTPATH)->rmtree;
    dir($WebCoso::Common::SRCPATH,'_webcoso')->rmtree;
    exit 0;
}

my %maker_opts=(
    options => {
        auto_create_dirs => 1,
        #debug => 1,
    },
);

my @passes=(
    {maker=>Slay::Maker->new({
        rules => [

            # tags : must come first, otherwise tha "tags" directory will
            # be taken as a normal document directory

            ["$WebCoso::Common::SRCPATH/tags/(**)/document.(*).rest.txt",
             ':',
             "$WebCoso::Common::SRCPATH/_webcoso/tags.yml",
             ifExists("$WebCoso::Common::SRCPATH/tags/\$1/document.\$2.rest.tt"),
             '=',
             $template->expandTT()],
            ["$WebCoso::Common::SRCPATH/tags/(**)/document.(*).du.xml",
             ':',
             "$WebCoso::Common::SRCPATH/_webcoso/tags.yml",
             ifExists("$WebCoso::Common::SRCPATH/tags/\$1/document.\$2.rest.txt"),
             '=',
             \&parseRST],

            # normal documents, in subdirs

            ["$WebCoso::Common::SRCPATH/(**)/document.(*).rest.txt",
             ':',
             ifExists("$WebCoso::Common::SRCPATH/\$1/document.\$2.rest.tt"),
             '=',
             $template->expandTT()],
            ["$WebCoso::Common::SRCPATH/(**)/document.(*).du.xml",
             ':',
             ifExists("$WebCoso::Common::SRCPATH/\$1/document.\$2.rest.txt"),
             '=',
             \&parseRST],

            # normal documents, in top dir

            ["$WebCoso::Common::SRCPATH/()document.(*).rest.txt",
             ':',
             ifExists("$WebCoso::Common::SRCPATH/document.\$2.rest.tt"),
             '=',
             $template->expandTT()],
            ["$WebCoso::Common::SRCPATH/()document.(*).du.xml",
             ':',
             ifExists("$WebCoso::Common::SRCPATH/document.\$2.rest.txt"),
             '=',
             \&parseRST],

            # tags from normal documents (tag documents can't be tagged!)

            ["$WebCoso::Common::SRCPATH/_webcoso/tags.yml",
             ':',
             fromTo($WebCoso::Common::SRCPATH,
                    {
                        %docfiles,
                        transform=>sub{WebCoso::Common::typedAs($_[0],'du.xml')}
                    }),
             '=',
             \&getTags],

            # changes (currently unimplemented)

            ["$WebCoso::Common::SRCPATH/_webcoso/changes.xml",
             ':',
             keepEarliest(fromTo($WebCoso::Common::SRCPATH,{%docfiles})),
             '=',
             \&getChanges],
        ],
        %maker_opts,
    }),
     targets=>[
         fromTo("$WebCoso::Common::SRCPATH/",
                {
                    %docfiles,
                    transform=>sub{WebCoso::Common::typedAs($_[0],'du.xml')},
                }),
         fromTo("$WebCoso::Common::SRCPATH/tags/",
                {
                    %docfiles,
                    transform=>sub{WebCoso::Common::typedAs($_[0],'du.xml')},
                })]},
    {maker=>Slay::Maker->new({
        rules => [

            # tags : must come first, otherwise tha "tags" directory will
            # be taken as a normal document directory

            ["$WebCoso::Common::DSTPATH/tags/(**)/document.(*).html",
             ':',
             "$WebCoso::Common::SRCPATH/_webcoso/tags.yml",
             "$WebCoso::Common::SRCPATH/tags/\$1/document.\$2.du.xml",
             '=',
             \&du2html],

            # normal documents, in subdirs

            ["$WebCoso::Common::DSTPATH/(**)/document.(*).html",
             ':',
             "$WebCoso::Common::SRCPATH/_webcoso/tags.yml",
             "$WebCoso::Common::SRCPATH/\$1/document.\$2.du.xml",
             '=',
             \&du2html],

            # normal documents, in top dir

            ["$WebCoso::Common::DSTPATH/()document.(*).html",
             ':',
             "$WebCoso::Common::SRCPATH/_webcoso/tags.yml",
             "$WebCoso::Common::SRCPATH/document.\$2.du.xml",
             '=',
             \&du2html],
        ],
        %maker_opts,
    }),
     targets=>[fromTo("$WebCoso::Common::SRCPATH/",
                      {
                          %docfiles,
                          transform=>sub{
                              (my $file=WebCoso::Common::typedAs($_[0],'html'))
                                  =~s{^\Q$WebCoso::Common::SRCPATH\E/}{$WebCoso::Common::DSTPATH/};
                              return $file;
                          },
                      }),
               fromTo("$WebCoso::Common::SRCPATH/tags/",
                      {
                          %docfiles,
                          transform=>sub{
                              (my $file=WebCoso::Common::typedAs($_[0],'html'))
                                  =~s{^\Q$WebCoso::Common::SRCPATH\E/tags/}{$WebCoso::Common::DSTPATH/tags/};
                              return $file;
                          },
                      })]},
);

$_->{maker}->make(@{$_->{targets}}) for @passes;

