#!/usr/bin/perl

use strict;
use warnings;

use FindBin;
use lib ("$FindBin::Bin/../lib", "$FindBin::Bin/../extlib");

use Carp ();
use IPC::Run3 'run3';
use IO::String;
use HTTP::Response;
use HTTP::Request;
use Data::Dumper;
use Encode;
use Getopt::Long;
use Web::Scraper;
use MT;

### Parses options.
my @blog_ids;
my @types;
my $username    = '';
my $password    = '';
my $script_path = '/mt/';
my $hostname    = 'localhost';
my $silent      = 0;
my $usage       = 0;
my $elapse      = 0;
GetOptions(
    'blog_id=s'  => \@blog_ids,
    'type=s'     => \@types,
    'user=s'     => \$username,
    'pass=s'     => \$password,
    'path=s'     => \$script_path,
    'host=s'     => \$hostname,
    'elapse'     => \$elapse,
    'silent'     => \$silent,
    'usage'      => \$usage,
);

if ($usage || !$username || !$password) {
    require Pod::Usage;
    Pod::Usage::pod2usage(1);
    exit;
}

if ($elapse) {
    require Time::HiRes;
}

### Creates MT object.
my $mt = MT->new() or die MT->errstr;

### Decide terminal's encoding
my $encoding;
if ( eval { require Term::Encoding } ) {
    $encoding = Term::Encoding::get_encoding();
}
else {
    $encoding = $^O eq 'MSWin32' ? 'cp932' : 'utf8';
}
my $enc = Encode::find_encoding($encoding)
    or die "unknown encoding: $encoding";

### Loads blogs
if (!@blog_ids) {
    push @blog_ids, '1';
}

my @blogs = MT::Blog->load( { id => \@blog_ids } )
    or die "Couldn't load blogs. [" . Dumper(@blog_ids) . "]\n";

# Creates scraper object
my $scraper = scraper {
    process "div.alert-publishing", status_publishing => 'TEXT';
    process "div.alert-error",      status_error      => 'TEXT';
    process "div.alert-success",    status_success    => 'TEXT';

    result 'status_publishing', 'status_error', 'status_success';
};

### Runs build script.
foreach my $blog (@blogs) {
    logging( "rebuilding (" . $blog->name . " ...\n" );
    run_rebuild($blog);
}

sub run_rebuild {
    my $blog = shift;

    my @at;
    if ( !@types ) {
        @at = split( ',', $blog->archive_type );
        push @at, 'index';
    }
    else {
        @at = @types;
    }

    foreach (@at) {
        my $at       = $_;
        my $archiver = $mt->publisher->archiver($at);
        next if ( !$archiver ) && ( $at ne 'index' );

        my $start = $elapse ? Time::HiRes::time() : 0;
        my $total = 0;
        if ($archiver) {
            if ( ( $archiver->entry_based || $archiver->date_based ) )
            {
                my $entry_class = $archiver->entry_class || 'entry';
                require MT::Entry;
                my $terms = {
                    class   => $entry_class,
                    status  => MT::Entry::RELEASE(),
                    blog_id => $blog->id,
                };
                $total = MT::Entry->count($terms);
            }
            elsif ( $archiver->category_based ) {
                require MT::Category;
                my $terms = {
                    blog_id => $blog->id,
                    class   => $archiver->category_class,
                };
                $total = MT::Category->count($terms);
            }
            elsif ( $archiver->author_based ) {
                require MT::Author;
                require MT::Entry;
                my $terms = {
                    blog_id => $blog->id,
                    status  => MT::Entry::RELEASE(),
                    class   => 'entry',
                };
                $total = MT::Author->count(
                    undef,
                    {
                        join => MT::Entry->join_on(
                            'author_id', $terms, { unique => 1 }
                        ),
                        unique => 1,
                    }
                );
            }
        }

        my $url =
            "http://$hostname$script_path"
          . $mt->config->AdminScript
          . "?__mode=rebuild"
          . "&blog_id="
          . $blog->id
          . "&next=0"
          . "&offset=0"
          . "&limit=20"
          . "&entry_id="
          . "&is_new="
          . "&old_status="
          . "&old_previous="
          . "&old_next="
          . "&total="
          . $total
          . "&type="
          . $at;
        do {
            $url .= "&username=" . $username;
            $url .= "&password=" . $password;
            my $req = new HTTP::Request( GET => $url );
            my $resp = simple_request($script_path, $req);
            if ( $resp->is_success ) {
                my $content = $resp->decoded_content;
                my $res = $scraper->scrape($content);
                if ( $res->{status_publishing} ) {
                    ( undef, $url ) =
                      $content =~ /window.location='(.*)\?(.*)'/;
                    $url = "http://$hostname$script_path" . $mt->config->AdminScript . "?" . $url;
                }
                elsif ( $res->{status_success} ) {
                    logging( "\t" . $at . " built success." . "\n" );
                    $url = undef;
                }
                elsif ( $res->{status_error} ) {
                    logging("\t"
                          . $at
                          . " built failed. "
                          . $res->{status_error}
                          . "\n" );
                    $url = undef;
                }
                else {
                    logging("\t"
                          . $at
                          . " built failed.\n"
                          . $content
                          . "\n" );
                    $url = undef;
                }
            }
            else {
                logging(
                    "\t" . $at . " request failed (" . $resp->code . ")\n" );
                $url = undef;
            }
        } while ($url);

        logging( "\t  total build time:" . ( Time::HiRes::time() - $start ) . "\n" )
            if $elapse;
    }
}

sub logging {
    my ($msg) = @_;
    print $enc->encode($msg) if !$silent;
}

sub simple_request {
    my $script_alias = shift;
    my $request      = shift;

    Carp::croak
        "simple_request requires an HTTP::Request argument"
        unless ref $request && $request->isa('HTTP::Request');

    my $request_method = $request->method();
    $request_method = "GET" unless $request_method;

    my ( $script_name, $path )
        = ( $request->uri->path()
            =~ m|^(?:\w+://[^/]*/?)?$script_alias([^/]*)(/?.*)| );
    $script_name
        or die "Couldn't determine script name from "
        . $request->uri()->path();
    $ENV{REQUEST_URI} = $script_alias;

    $request->headers()->scan(
        sub {
            my $header_name = $_[0];
            $header_name =~ tr/a-z-/A-Z_/;
            $ENV{ "HTTP_" . $header_name } = $_[1];
        }
    );

    $ENV{REQUEST_METHOD}  = $request_method;
    $ENV{SCRIPT_NAME}     = $script_name;
    $ENV{PATH_INFO}       = $path;
    $ENV{QUERY_STRING}    = $request->uri->query || '';
    $ENV{HTTP_USER_AGENT} = "Ezra's Local User Agent 0.1";

    my $content = $request->content();
    if ($content) {
        $ENV{CONTENT_LENGTH} = length $request->content();
    }
    run3 [$^X, "./$script_name"],
        \$content, \my $output, undef,
        { binmode_stdin => 1 }
        or die "Couldn't spawn ./$script_name";
    print STDERR "$script_name exit status: $?\n" if $?;

    my $RESPONSE = IO::String->new($output);

    my $response = new HTTP::Response();
    $response->request($request);
    my $status_line = '';
    while ( ( my $line = <$RESPONSE> ) !~ /^\s*$/ ) {
        if ( $line =~ /^Status:/i ) {
            $status_line = $line;
        }
        else {
            my ( $hdr_name, $hdr_val ) = ( $line =~ /(.*?):(.*)/ );
            $response->header( $hdr_name => $hdr_val );
        }
    }

    $status_line =~ s/Status: //i;
    my ( $response_code, $response_desc )
        = $status_line =~ /^(\d\d\d)(\s.*)?$/;
    $response->code($response_code);
    $response->message($response_desc);

    local $/ = undef;
    my $body = <$RESPONSE>;
    $response->content($body);

    close $RESPONSE;
    $response;
}

1;

__END__

=head1 NAME

rebuild_pages

=head1 SYNOPSIS

    rebuild-pages
      require:
        --user             login account of mt
        --pass             login password of mt

      optional:
        --blog_id <id>     target blog id
        --type <type>      target archive type
                             - Category-Monthly
                             - Category
                             - Individual
                             - Page
                             - Monthly
                             - index
                             - or others...
        --path             path to mt.cgi (default:/mt/)
        --host             hostname (default:localhost)
        --elapse           logging build elapsed time if you want (1|0)
        --silent           no output any logs (1|0)
        --usage            show this message

    This script requires following perl modules.
        Web::Scraper           You must install from CPAN if you not installed yet.
