#!/usr/bin/perl -w
#
# far.pl -- Pass or fail an Featured Article class review
#     This Bot runs every day, looking for featured class articles that have been processed by a delegate
#    If it finds one, it follows the steps involved in keeping or delisting it.
# Usage: far.pl
#    13 February 15 Created

use English;
use strict;
use utf8;
use warnings;

use Carp;
use Data::Dumper;
use File::Basename qw(dirname);
use File::Spec;
use MediaWiki::Bot;
use POSIX qw(strftime);
use XML::Simple;

binmode (STDERR, ':utf8');
binmode (STDOUT, ':utf8');

# Pages used
my $candidates_page = 'Wikipedia:featured article review';
my $showcase_fa = 'Wikipedia:WikiProject Military history/Showcase/FA';

my $editor = MediaWiki::Bot->new ({
        assert        => 'bot',
        host        => 'en.wikipedia.org',
        protocol     => 'https',
}) or die "new MediaWiki::Bot failed";

my $dirname = dirname (__FILE__, '.pl');
push @INC, $dirname;
require Cred;
my $cred = new Cred ();
my $log = $cred->log ();

require showcase;

sub allow_bots ($$;$) {
    my($text, $user, $opt) = @ARG;
    return 0 if $text =~ /{{[nN]obots}}/;
    return 1 if $text =~ /{{[bB]ots}}/;
    if ($text =~ /{{[bB]ots\s*\|\s*allow\s*=\s*(.*?)\s*}}/s){
        return 1 if $1 eq 'all';
        return 0 if $1 eq 'none';
        my @bots = split(/\s*,\s*/, $1);
        return (grep $ARG eq $user, @bots)?1:0;
    }
    if ($text =~ /{{[bB]ots\s*\|\s*deny\s*=\s*(.*?)\s*}}/s){
        return 0 if $1 eq 'all';
        return 1 if $1 eq 'none';
        my @bots = split(/\s*,\s*/, $1);
        return (grep $ARG eq $user, @bots)?0:1;
    }
    if (defined($opt) && $text =~ /{{[bB]ots\s*\|\s*optout\s*=\s*(.*?)\s*}}/s){
        return 0 if $1 eq 'all';
        my @opt = split(/\s*,\s*/, $1);
        return (grep $ARG eq $opt, @opt)?0:1;
    }
    return 1;
}

sub error_exit ($) {
    my @message = @ARG;
    if ($editor->{error}->{code}) {
        push @message, ' (', $editor->{error}->{code} , ') : ' , $editor->{error}->{details};
    }
    $cred->error (@message);
}

sub has_been_closed ($) {
    my ($nomination) = @ARG;
    $cred->showtime ("checking if $nomination has been closed...\n");
    my $text = $editor->get_text ($nomination) or
        error_exit ("Unable to find '$nomination')");
    if ($text =~ /{{FARClosed\|(.+?)}}.+(\d+:\d+, \d+ (\w+) (\d+) \(UTC\))/) {
        $cred->showtime ("\t$nomination $1\n");
        return ($1, $2, $3, $4);
    }
    return ();
}   

sub whodunnit ($$$) {
    my ($article, $nomination, $action) = @ARG;
    my $old;
    my @history = $editor->get_history ($nomination) or
        error_exit ("Unable to get history of '$nomination'");
    foreach my $revision (@history) {
#        print Dumper $revision, "\n";
        my $text = $editor->get_text ($nomination, $revision->{revid}) or
            error_exit ("Unable to find '$nomination:$revision->{revid}')");
        if ($text !~ /{{FARClosed/) {
            $cred->showtime ("\t$article was $action by $old->{user} at $old->{timestamp_date} $old->{timestamp_time}\n");
            my $diff = "https://en.wikipedia.org/w/index.php?title=$nomination\&diff=$old->{revid}\&oldid=$revision->{revid}";
            $diff =~ s/ /_/g;
#            print $diff, "\n";
            return ($old->{user}, $old->{timestamp_date}, $old->{timestamp_time}, $diff);
        } else {
            $old = $revision;
        }
    }
}

sub keep_update_nomination_page ($$$$$) {
    my ($page, $nomination, $user, $date, $diff) = @ARG;
    $cred->showtime ("\tUpdating the nomination page\n");
    my $text = $editor->get_text ($nomination) or
        error_exit ("Unable to find '$nomination')");
    $cred->error ("no bots allowed on '$nomination'") unless allow_bots ($text, $cred->user);

    # Remove transcluded article links and featured article tools
    $text =~ s/<noinclude>.+<\/noinclude>//s;

    # Tag the top and bottom of the page
    my $result = "'''kept''' by [[User:$user|$user]] via ~~~ $date [$diff]";
    my $top = "{{subst:FAR top|result=$result}}";
    my $bottom = "{{subst:FAR bottom}}\n";
    $text = join "\n", $top, $text, $bottom;

    $editor->edit ({
        page => $nomination,
        text => $text,
        summary => "'$page' kept",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$nomination'");
}

sub delist_update_nomination_page ($$$$$) {
    my ($page, $nomination, $user, $date, $diff) = @ARG;
    $cred->showtime ("\tUpdating the nomination page\n");
    my $text = $editor->get_text ($nomination) or
        error_exit ("Unable to find '$nomination')");
    $cred->error ("no bots allowed on '$nomination'") unless allow_bots ($text, $cred->user);

    # Remove transcluded article links and featured article tools
    $text =~ s/<noinclude>.+<\/noinclude>//s;

    # Tag the top and bottom of the page
    my $result = "'''delisted''' by [[User:$user|$user]] via ~~~ $date [$diff]";
    my $top = "{{subst:FAR top|result=$result}}";
    my $bottom = "{{subst:FAR bottom}}\n";
    $text = join "\n", $top, $text, $bottom;

    $editor->edit ({
        page => $nomination,
        text => $text,
        summary => "Archiving '$page'",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$nomination'");
}

sub delist_update_article_page ($) {
    my ($page) = @ARG;
    $cred->showtime ("\tUpdating the article page\n");
    my $text = $editor->get_text ($page) or
        error_exit ("Unable to find '$page')");
    $cred->error ("no bots allowed on '$page'") unless allow_bots ($text, $cred->user);

    $text =~ s/{{featured article}}//igs;

    $editor->edit ({
        page => $page,
        text => $text,
        summary => "Delisting '$page' after unsuccessful Featured Article Review",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$page'");
}

sub parse_template ($@) {
    my ($text, @args) = @ARG;
    my %p;
    while ($text =~ s/\|(\w+)\s*=\s*([^}|]+)//is) {
        $p{$1}=$2;   
    }
   
    my @p = split '\|', $text;
    param:foreach my $p (@p) {
        next param unless $p;
        foreach my $arg (@args) {
            if (!defined $p{$arg}) {
                $p{$arg} = $p;
                next param;
            }
        }
    }
#    foreach my $p (keys %p) {
#        print "$p => $p{$p}\n";
#    }
   
    return %p;
}

sub newaction ($$$$$$) {
    my ($action, $date, $link, $result, $revid, $id) = @ARG;
    my $newaction = join "\n",
        "|action${id}=$action",
        "|action${id}date=$date",
        "|action${id}link=$link",
        "|action${id}result=$result",
        "|action${id}oldid=$revid";
    return $newaction;
}

sub update_article_history ($$$$$$) {
    my ($text, $action, $date, $link, $result, $revid) = @ARG;
    $text =~ s/{{Article\s*History/{{ArticleHistory/is;
    my ($articleHistory) = $text =~ /{{ArticleHistory(.+?)}}/gis;
    if ($articleHistory) {
#        print "articlehistory='$articleHistory'\n";
        for (my $id = 1;; ++$id) {
            if ($articleHistory =~ /action$id/) {
#                print "\t\tfound action$id\n";
            } else {
#                print "\t\tno $id - going with that\n";
                my $newaction = newaction ($action, $date, $link, $result,  $revid, $id);
                $text =~ s/{{Article\s*History(.+?)}}/{{ArticleHistory$1\n$newaction\n}}/is;
                last;
            }
        }
    } else {
        my $newaction = newaction ($action, $date, $link, $result, $revid, 1);
        $text =~ s/^/{{ArticleHistory\n$newaction\n}}\n/is;
    }
    return $text;
}

sub get_revid ($$$) {
    my ($page, $date, $time) = @ARG;
    my @history = $editor->get_history ($page) or
        error_exit ("Unable to get history of '$page'");
    foreach my $history (@history) {
        if ($history->{timestamp_date} le $date ||
                ($history->{timestamp_date} eq $date && $history->{timestamp_time} le $time)) {   
            return $history->{revid};
        }
    }
    error_exit ("Unable to get revid of '$page')");
}

sub keep_update_talk_page ($$$$$) {
    my ($page, $talk, $nomination_page, $date, $time) = @ARG;
   
    $cred->showtime ("\tUpdating the talk page\n");
    my $text = $editor->get_text ($talk) or
        error_exit ("Unable to find '$talk')");
    $cred->error ("no bots allowed on '$talk'") unless allow_bots ($text, $cred->user);

    # Remove the candidacy
    $text =~ s/{{featured article review\|.+?}}//;

    # Update the article history
    my $revid = get_revid ($page, $date, $time);
    $text = update_article_history ($text, 'FAR', $date, $nomination_page, 'kept', $revid);

    $editor->edit ({
        page => $talk,
        text => $text,
        summary => "Updating '$page' after successful Featured Article Review",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$talk'");
}

sub delist_update_talk_page ($$$$$) {
    my ($page, $talk, $nomination_page, $date, $time) = @ARG;
   
    $cred->showtime ("\tUpdating the talk page\n");
    my $text = $editor->get_text ($talk) or
        error_exit ("Unable to find '$talk')");
    $cred->error ("no bots allowed on '$talk'") unless allow_bots ($text, $cred->user);

    # Remove the candidacy
    $text =~ s/{{featured article review\|.+?}}//;

    # Update the article history
    my $revid = get_revid ($page, $date, $time);
    $text = update_article_history ($text, 'FAR', $date, $nomination_page, 'demoted', $revid);
   
    # Update the current status
    $text =~ s/currentstatus=FA/currentstatus=FFA/is;
    $text =~ s/class=FA/class=/igs;

    $editor->edit ({
        page => $talk,
        text => $text,
        summary => "Updating '$page' after unsuccessful Featured Article Review",
        bot => 1,
        minor => 0,
    }) or
        error_exit ("unable to edit '$talk'");
}

sub remove_from_showcase ($) {
    my ($article) = @ARG;
       
    my $showcase_text = $editor->get_text ($showcase_fa) or
        error_exit ("Unable to find '$showcase_fa'");
    $cred->error ("no bots allowed on '$showcase_fa'") unless allow_bots ($showcase_text, $cred->user);

    my $showcase = new showcase ($showcase_text);
    my $found = $showcase->del ($article);

    if ($found) {   
        $editor->edit ({
            page => $showcase_fa,
            text => $showcase->text,
            summary => "'$article' has been delisted",
#            bot => 1,
            minor => 0,
        }) or
            error_exit ("unable to edit '$showcase_fa'");
    }
    return $found;
}

# Find the nomination page
sub nomination ($) {
    my ($talk) = @ARG;
    my $text = $editor->get_text ($talk) or
        error_exit ("Unable to find '$talk')");
    $text =~ /{{featured article review\|(.+?\/archive\d+)}}/;
    my $nomination = "Wikipedia:Featured article review/$1";
    $nomination =~ s/&#([0-9a-f]+);/chr($1)/ige;
    $cred->showtime ("\t$nomination\n");
    return $nomination;
}

$editor->login ({
    username => $cred->user,
    password => $cred->password
}) or die $editor->{error}->{code} . ': ' . $editor->{error}->{details};

$cred->showtime ("========== Commenced ==========\n");

# First, we need to find the nomination pages
my @candidates = $editor->get_pages_in_category ('Wikipedia featured article review candidates');
foreach my $talk (@candidates) {

    my $article = $talk;
    $article =~ s/Talk:// or
        next;
    $cred->showtime ($article, "\n");

    my $nomination = nomination ($talk);
    if (my ($status, $display_date, $month, $year) = has_been_closed ($nomination)) {
        $cred->showtime ("\t$nomination closed ($status) on $display_date\n");
        if ($status =~ /kept|keep/i) {
            my ($user, $date, $time, $diff) = whodunnit ($article, $nomination, 'kept');
            keep_update_talk_page ($article, $talk, $nomination, $date, $time);
            keep_update_nomination_page ($article, $nomination, $user, $display_date, $diff);
        } elsif ($status =~ /delisted/i) {
            my ($user, $date, $time, $diff) = whodunnit ($article, $nomination, 'delisted');
            delist_update_talk_page ($article, $talk, $nomination, $date, $time);
            delist_update_nomination_page ($article, $nomination, $user, $display_date, $diff);
            delist_update_article_page ($article);
            remove_from_showcase ($article);
        } else {
            $cred->showtime ("\tunknown status\n");
        }       
    } else {
        $cred->showtime ("\t$nomination is still current\n");
    }
}
exit 0;