#!/usr/bin/perl

# Converts my CV from LaTeX source to mdwn which is rendered to HTML by IkiWiki

use strict;
use warnings;
use Carp;
use LaTeX::TOM;
use Text::EscapeDelimiters;
use String::Tokenizer;
use DateTime;
use 5.010;

my $latex_string = join q{}, <>;
my $latex_parser = LaTeX::TOM->new()
        or confess 'Failed to create LaTeX parser';
my $latex_document = $latex_parser->parse($latex_string)
        or confess 'Failed to parse LaTeX document';

my @out;
my @text_context;
my $div_class;
my $div_open;
my $skip_text = -1;
my %list_level;
my $list_item_number_found = 0;
my $list_item_number_done = 0;
my $ignore_section = 0;
my $ignore_par = 0;
my $mangling_text;
process_tree($latex_document);
end_div();
print join "\n", @out;

sub process_tree {
        my $tree = shift;
        for my $node ($tree->getTopLevelNodes()) {
                process_node($node);
        }
}

sub process_node {
        my $node = shift;
        my $node_type = $node->getNodeType();
        my $node_handler = sprintf 'process_node_%s', lc $node_type;
        if(main->can($node_handler)) {
                main->$node_handler($node);
        }
}

sub process_node_text {
        shift;
        my $node = shift;
        return if skip_text();
        my $text = main->process_text($node->getNodeText());
        if($text =~ /\s*?(\t+)(?<!\\)(?:\\item)/) {
                my $list_level_number = (length $1) - 1;
                my $item_num = scalar grep { /^(?<!\\)(?:\\item)$/ } String::Tokenizer->new($text)->getTokens();
                for my $i (1 .. $item_num) {
                        $list_level{$list_item_number_found++} = $list_level_number;
                }
        }
        push @text_context, $text;
}

sub process_node_command {
        shift;
        my $node = shift;
        my $process_handler = sprintf 'process_command_%s', $node->getCommandName();
        if(main->can($process_handler)) {
                main->$process_handler($node);
        }
        process_tree($node->getChildTree());
}

sub process_node_environment {
        shift;
        my $node = shift;
        my $process_handler = sprintf 'process_environment_%s', $node->getEnvironmentClass();
        if(main->can($process_handler)) {
                push_text();
                main->$process_handler($node);
                push_text();
        }
        process_tree($node->getChildTree());
}

sub process_node_group {
        shift;
        my $node = shift;
        process_tree($node->getChildTree());
}

sub process_environment_cvsection {
        shift;
        my $node = shift;
        $ignore_section = 0;
        end_div();
        $div_class = 'section';
        start_div();
        $skip_text = 0;
        my $title = text_content($node->getFirstChild()->getNextSibling());
        if($title ne 'References') {
                push @text_context, sprintf '# %s', $title;
                $skip_text = -1;
                end_div();
        } else {
                $ignore_par = 1;
                $ignore_section = 1;
        }
}

sub process_command_header {
        shift;
        my $node = shift;
        end_div();
        $div_class = 'section';
        start_div();
        push @text_context, sprintf "# %s\n\n", text_content($node);
        $skip_text = 0;
        process_node($node->getNextSibling()->getNextSibling());
        $skip_text = 9;
        end_div();
}

sub process_command_hypersetup {
        shift;
        my $node = shift;
        my $pdfdata = text_content($node);
        my($title) = $pdfdata =~ /pdftitle=([^,]+|.+)/;
        push @text_context, sprintf "\n\n", $title if $title;
}


sub process_command_includegraphics {
        shift;
        my $node = shift;
        push @text_context, sprintf "[[!img <span class="error">Error: bad image filename</span>]]\n\n", text_content($node);
}

sub process_command_cvitem {
        return if $ignore_section;
        shift;
        my $node = shift;
        $skip_text = 0;
        $div_class = 'item_left';
        start_div();
        my $left_content = text_content($node->getFirstChild());
        push @text_context, sprintf '*%s*', $left_content if $left_content;
        end_div();
        $div_class = 'item_right';
        start_div();
        $skip_text = 0;
        process_node($node->getNextSibling()->getNextSibling());
        end_div();
        $skip_text = -1;
}

sub process_command_emph {
        return if skip_text();
        shift;
        my $node = shift;
        push @text_context, sprintf '*%s*', text_content($node);
        $skip_text = 1;
}

sub process_command_email {
        return if skip_text();
        shift;
        my $node = shift;
        push @text_context, sprintf '<a href="mailto:%s">%s</a>', text_content($node);
        $skip_text = 1;
}

sub process_command_url {
        return if skip_text();
        shift;
        my $node = shift;
        push @text_context, sprintf '<span class="createlink"><a href="/ikiwiki.cgi?do=create&amp;from=cv%2Fcv_to_mdwn.pl&amp;page=__37__s" rel="nofollow">?</a>&#37;s</span>', text_content($node);
        $skip_text = 1;
}

sub text_content {
        my $arg = shift;
        given(ref $arg) {
                when('LaTeX::TOM::Node') {
                        given($arg->getNodeType()) {
                                when('TEXT') {
                                        return main->process_text($arg->getNodeText());
                                }
                                when(/^(?:COMMAND|ENVIRONMENT|GROUP)$/) {
                                        return text_content($arg->getChildTree());
                                }
                                default {
                                        return q{};
                                }
                        }
                }
                when('LaTeX::TOM::Tree') {
                        my $text = q{};
                        for my $node ($arg->getTopLevelNodes()) {
                                $text .= text_content($node);
                        }
                        return $text;
                }
                when(q{}) {
                        return $arg;
                }
        }
}

sub process_text {
        shift;
        my $text = shift;
        return q{} if not defined $text;
        return q{} if $text =~ /^\s*$/;
        return $text;
}

sub mangle_text {
        return if $mangling_text;
        $mangling_text++;
        my $unformatted_text = join q{}, grep { !/^$/ } @_;
        my @out_text;
        my @chunks =
                split /\n{2,}/,
                        (join '\\par', Text::EscapeDelimiters->new()->split($unformatted_text, '\\\\'));
        for my $chunk (@chunks) {
                my $tokenizer = String::Tokenizer->new($chunk);
                my @lines;
                my @current_line;
                my @end_line;
                my $iterator = $tokenizer->iterator();
                while($iterator->hasNextToken()) {
                        my $token = $iterator->nextToken();
                        next if not $token;
                        given($token) {
                                when(/^(.*)(?<!\\)(?:\\today)$/) {
                                        (my $now = DateTime->now()->set_time_zone('UTC')->set_time_zone('local')->strftime('%B %e, %Y')) =~ s/ {2,}/ /g;
                                        $token = $1;
                                        $token .= q{ } if $token;
                                        $token .= $now;
                                }
                                when(/^(.*)(?<!\\)(?:\\hfill)$/) {
                                        $token = $1;
                                        $token .= q{ } if $token;
                                        $token .= sprintf '<span class="align_right">';
                                        push @end_line, '</span>';
                                }
                                when(/^(.*)(?<!\\)(?:\\item)$/) {
                                        push @current_line, @end_line if @end_line;
                                        push @lines, join q{ }, @current_line if @current_line;
                                        @current_line = ();
                                        @end_line = ();
                                        $token = sprintf '%s*', "\t" x ($list_level{$list_item_number_done++});
                                }
                                when(/^(.*)(?<!\\)(?:\\hrule)$/) {
                                        my $prev_div_class = $div_class;
                                        end_div();
                                        $div_class = 'hr';
                                        start_div();
                                        push @text_context, '---';
                                        end_div();
                                        $div_class = $prev_div_class if $prev_div_class;
                                        next;
                                }
                                when(/^(.*)(?<!\\)(?:\\par)$/) {
                                        $token = $1;
                                        push @current_line, $token if $token;
                                        next if $ignore_par;
                                        push @current_line, @end_line if @end_line;
                                        push @lines, join q{ }, @current_line if @current_line;
                                        @current_line = ();
                                        @end_line = ();
                                        next;
                                }
                                when(/^(.*)(?<!\\)(?:\\[a-z]+)$/) {
                                        next;
                                }
                        }
                        push @current_line, $token;
                }
                push @current_line, @end_line if @end_line;
                push(@lines, join q{ }, @current_line) if @current_line;
                push @out_text, join "\n\n", @lines if @lines;
        }
        return join "\n\n", @out_text;

}

sub push_text {
        push @out, mangle_text(@text_context) if @text_context;
        $mangling_text = 0;
        @text_context = ();
}

sub end_div {
        push_text();
        if($ignore_section) {
                pop @out;
        } else {
                push @out, '</div>', q{}, if $div_open;
        }
        $div_open = 0;
}

sub start_div {
        confess 'No div class' if not $div_class;
        push @out, sprintf "<div class=\"cv_%s\" markdown=\"1\">", $div_class;
        $div_open++;
}

sub skip_text {
        return 1 if $ignore_section;
        return 1 if $skip_text < 0;
        return if $skip_text == 0;
        return $skip_text-- > 0;
}