#!/usr/bin/perl -w

use strict;

use XML::LibXML;
use Data::Dumper;

my %template = (
  index => Template::MasonLite->new_from_file('./_index.html'),
  toc   => Template::MasonLite->new_from_file('./_toc.html'),
  slide => Template::MasonLite->new_from_file('./_slide.html'),
  notes => Template::MasonLite->new_from_file('./_notes.txt'),
);

my $file = 'talk.xml';

my $parser = XML::LibXML->new;
my $doc    = $parser->parse_file($file);
my $root   = $doc->getDocumentElement;

gen_title_page($root);
gen_contents_page($root);
gen_slides($root);
gen_notes($root);

exit;


sub gen_title_page {
  my($doc) = @_;

  my $data = {
    title     => $root->findvalue('/presentation/title'),
    subtitle  => $root->findvalue('/presentation/subtitle'),
    author    => $root->findvalue('/presentation/author'),
    email     => $root->findvalue('/presentation/email'),
    next      => 'slide01.html',
  };

  save_file($template{index}, 'index.html', $data);
}


sub gen_contents_page {
  my($doc) = @_;

  my @page;

  my $count = 1;
  foreach my $slide ($doc->findnodes('/presentation/slide')) {
    push @page, {
      page   => sprintf("slide%02u.html", $count++),
      title  => $slide->findvalue('./title'),
    };
    pop @page if(@page > 1  and  $page[-1]->{title} eq $page[-2]->{title});
  }
  my $data = {
    title  => 'Contents',
    page   => \@page,
    prev   => 'index.html',
    next   => 'slide01.html',
  };
  save_file($template{toc}, 'toc.html', $data);
}


sub gen_slides {
  my($doc) = @_;

  my @page;

  my $count = 1;
  foreach my $slide ($doc->findnodes('/presentation/slide')) {
    my @content;
    foreach my $node ($slide->findnodes('./screenshot|./bullet|./code')) {
      my $type = $node->nodeName;
      if($type eq 'screenshot') {
        my $filename = $node->to_literal;
        my $img_src = "html/images/$filename";
        push @content, $filename;
      }
      elsif($type eq 'bullet') {
        push @content, [] if(!@content or !ref($content[-1]));
        push @{$content[-1]}, $node->to_literal;
      }
      else {
        my $code = outdent($node->to_literal);
        push @content, $code;
      }
    }
    my $data = {
      title   => $slide->findvalue('./title'),
      content => \@content,
      prev    => $count > 1 ? sprintf("slide%02u.html", $count-1) : 'toc.html',
    };
#    my($commentary) = $slide->findnodes('./commentary');
#    if($commentary) {
#      $data->{commentary} = $commentary->to_literal;
#    }
    my $image_src = $slide->findvalue('./image/@src');
    $data->{image_src} = $image_src if($image_src);
    if($slide->findnodes('./following::slide')) {
      $data->{next} = sprintf("slide%02u.html", $count+1);
    }
    else {
      $data->{next} = 'index.html';
    }
    save_file($template{slide}, sprintf("slide%02u.html", $count), $data);
    $count++;
  }
}


sub gen_notes {
  my($doc) = @_;

  my @page;

  my $count = 1;
  foreach my $slide ($doc->findnodes('/presentation/slide')) {
    my $notes = outdent($slide->findvalue('./notes') || '-');
    $notes =~ s/\n+$//s;
    push @page, {
      title => $slide->findvalue('./title'),
      notes => $notes,
    };
  }
  save_file($template{notes}, 'notes.txt', { slides => \@page });
}


sub outdent {
  local($_) = @_;

  s/\s+$//s;
  s/^\s*?\n//s;
  if(my($prefix) = (/^(\s+)/)) {
    s/^$prefix//mg;
  }
  return $_;
}


sub save_file {
  my($tmpl, $file, $data) = @_;

  open my $out, '>', "./html/$file" or die "open(./html/$file): $!";
  print $out $tmpl->apply(%$data);
}package Template::MasonLite;

use strict;
use warnings;
use Carp;

our $VERSION = '0.9';

my(
    $nl, $init_sect, $perl_sect, $perl_line, $comp_def, $comp_call, 
    $expression, $literal
);

BEGIN {
    $nl         = qr{(?:[ \r]*\n)};
    $init_sect  = qr{<%init>(.*?)</%init>$nl?}s;
    $perl_sect  = qr{<%perl>(.*?)</%perl>$nl?}s;
    $perl_line  = qr{(?:(?<=\n)|^)%(.*?)(\n|\Z)}s;
    $comp_def   = qr{<%def\s+([.\w+]+)>$nl(.*?)</%def>$nl?}s;
    $comp_call  = qr{<&\s*([\w._-]+)(?:\s*,)?(.*?)&>}s;
    $expression = qr{<%\s*(.*?)%>}s;
    $literal    = qr{(.*?(\n|(?=<%|<&|\Z)))};
}

sub new           { return bless $_[0]->_parse($_[1]),      $_[0]; }
sub new_from_file { return bless $_[0]->_parse_file($_[1]), $_[0]; }

sub apply         { my $self = shift; return $self->(@_) };

sub _parse_file {
    my($class, $template) = @_;

    open my $fh, '<', $template or croak "$!: $template";
    sysread $fh, $_, -s $template;
    return $class->_parse($_);
}

sub _parse {
    my($class, $template) = @_;

    die "No template!\n" unless defined($template);
    $_ = $template;

    my(@head, @body, %comp);
    while(!/\G\Z/sgc) {
        if   (/\G$init_sect/sgc ) { push @head, $1;        }
        elsif(/\G$perl_sect/sgc ) { push @body, $1;        }
        elsif(/\G$perl_line/sgc ) { push @body, $1;        }
        elsif(/\G$comp_def/sgc  ) { $comp{$1} = $2;        }
        elsif(/\G$comp_call/sgc ) { push @body,
                                      [ 0, "\$comp{'$1'}->apply($2)" ]; 
                                  }
        elsif(/\G$expression/sgc) { push @body, [ 0, $1 ]; }
        elsif(/\G$literal/sgc   ) { push @body, [ 1, $1 ]; }
        else {/(.*)/sgc && croak "could not parse: '$1'";  }
    };
    while(my($name, $source) = each %comp) {
        $comp{$name} = $class->new($source);
    }

    unshift @head, 'my @r; my %ARGS; %ARGS = @_ unless(@_ % 2);';
    push    @body, 'return join "", @r';

    my $code = join("\n", map {
        ref($_)
        ? ( $_->[0] ? _literal($_->[1]) : _expr($_->[1]) )
        : $_;
    } @head, @body);

    $_ = '';
    my $sub = eval "sub { $code }";
    croak $@ if $@;
    return $sub;
}

sub _expr    { "push \@r, $_[0];"; }
sub _literal { $_ = shift; s/'/\'/g; s/\\\n//s; _expr("'$_'"); }

# End of Template::MasonLite

sub h {
    $_ = shift;
    s/&/&amp;/g;
    s/</&lt;/g;
    s/>/&gt;/g;
    $_;
}
