#!/usr/bin/env perl

use warnings;
use strict;

use FindBin;
use lib
    "$FindBin::Bin/../lib",
;

use File::ChangeNotify;
use Path::Class::File;
use App::Daemon qw( daemonize );
use Try::Tiny;
use Log::Log4perl qw( :easy );
use Getopt::Long;
use Mojolicious::Lite;

use Readonly;
Readonly my $OK          => 200;
Readonly my $ACCEPTED    => 202;
Readonly my $BAD_REQUEST => 400;

use Plerd;
use Plerd::Util;

my $webmention_enabled = 0;
my $webmention_port  = 0;
my $config_file;
my $ssl_cert;
my $ssl_key;
GetOptions(
    'config=s'              => \$config_file,
    'send-webmentions'      => \$webmention_enabled,
    'receive-webmentions=i' => \$webmention_port,
    'ssl-cert=s'            => \$ssl_cert,
    'ssl-key=s'             => \$ssl_key,
);

if ($ssl_cert || $ssl_key) {
    unless ( $ssl_cert && $ssl_key ) {
        die "Can't start $0: You must define both ssl-cert and ssl-key, "
            . "if you define either of them.\n";
    }
    unless (-r $ssl_cert) {
        die "Can't start $0: No readable ssl-cert file at $ssl_cert.\n";
    }
    unless (-r $ssl_key) {
        die "Can't start $0: No readable ssl-key file at $ssl_key.\n";
    }
}

my $webserver_pid;
$SIG{TERM} = \&handle_term_signal;

$ENV{MOJO_MODE} ||= 'production';

my $config_ref = Plerd::Util::read_config_file( $config_file );

for my $dir_type ( qw( run log ) ) {
    unless ( $config_ref->{ "${dir_type}_path" } ) {
        if ( $config_ref->{ path } ) {
            $config_ref->{ "${dir_type}_path" } =
                Path::Class::File->new(
                    $config_ref->{path},
                    $dir_type,
                )->stringify;
        }
        else {
            $config_ref->{ "${dir_type}_path" } =
                "$FindBin::Bin/../$dir_type";
        }
    }
    my $dir = $config_ref->{ "${dir_type}_path" };
    mkdir $dir unless -e $dir;
    unless (-w $dir) {
        die "Can't start $0: I don't have write permission to $dir_type "
            . "directory '$dir'. Either make that directory writeable, "
            . "or change Plerd's configuration to use a different "
            . "location as the $dir_type directory.\n";
    }
}
$App::Daemon::logfile = "$$config_ref{log_path}/plerdwatcher.log";
$App::Daemon::pidfile = "$$config_ref{run_path}/plerdwatcher.pid";

daemonize();

foreach (qw( base_uri image ) ) {
    unless ( ref $config_ref->{ $_ } ) {
        $config_ref->{ $_ } = URI->new ( $config_ref->{ $_ } );
    }
}

my $plerd;
my $watcher;
try {
    $plerd = Plerd->new( $config_ref );

    $watcher = File::ChangeNotify->instantiate_watcher (
        directories => [ $plerd->source_directory . '' ],
        filter      => qr/\.(md|markdown)$/,
    );
}
catch {
    ERROR "Couldn't start Plerd: $_";
};

post '/' => sub {
    my $c = shift;

    my $webmention;
    try {
        $webmention = Web::Mention->new_from_request ( $c );
    }
    catch {
        $c->render( status => $BAD_REQUEST, text => "Malformed webmention: $_" );
    };
    return unless $webmention;

    # If the list of blog source files has changed, force a lazy rebuild of
    # the plerd object's internal database of posts before continuing.
    if ( $watcher->new_events ) {
        $plerd->clear_posts;
        $plerd->clear_post_url_index_hash;
    }

    my $post = $plerd->post_with_url( $webmention->target );
    unless ( $post ) {
        $c->render( status => $BAD_REQUEST, text => "Unrecognized target URL." );
        return;
    }

    my $success_text = "Webmention accepted, and queued for verification and "
                     . "processing. Thank you!";

    my $return_link_url = $c->param( 'target' );
    my $return_link_text = 'Return to ' . $plerd->title . '.';
    $success_text .= qq{ <a href="$return_link_url">$return_link_text</a>};

    $c->render( status => $ACCEPTED, text => $success_text );

    $plerd->webmention_queue->add_webmention( $webmention );
};

get '/' => sub {
    my $c = shift;

    $c->render( status => $OK, text => 'OK (listening for webmentions)' );
};

if ( $webmention_port ) {
    $webserver_pid = fork;
    unless ( $webserver_pid ) {
        app->log->path( "$$config_ref{log_path}/plerdweb.log" );

        my $listen_uri;
        if ( $ssl_cert ) {
            $listen_uri =
                "https://*:$webmention_port?cert=$ssl_cert&key=$ssl_key";
        }
        else {
            $listen_uri =
                "http://*:$webmention_port";
        }

        app->start(
            'daemon',
            '-l' => $listen_uri,
        );
    }
}

while ( my @events = $watcher->wait_for_events ) {
    if ( @events ) {
        try {
            $plerd->publish_all;
            for my $event ( @events ) {
                if (
                    (
                        ( $event->type eq 'create' )
                        or ( $event->type eq 'modify' )
                    )
                    and $webmention_enabled
                ) {
                    my $file = Path::Class::File->new( $event->path );
                    my $post = Plerd::Post->new(
                        source_file => $file,
                        plerd => $plerd,
                    );
                    INFO "Sending webmentions for $file...";
                    my $report = $post->send_webmentions;
                    INFO "$report->{attempts} attempts, "
                         . "$report->{sent} sent, "
                         . "$report->{delivered} delivered.";
                }
            }
        }
        catch {
            ERROR "Failed to publish: $_\n";
        };
    }
}

sub handle_term_signal {
    kill ('KILL', $webserver_pid) if $webserver_pid;
    exit;
}

=head1 NAME

plerdwatcher - Daemon that automatically updates a Plerd-based blog when
needed

=head1 SYNOPSIS

In /path/to/plerd/conf/plerd.conf:

 base_uri: http://blog.example.com path: /home/me/Dropbox/plerd title:
 My Lovely Blog

And then, on the command line, while in the top-level plerd directory:

 bin/plerdwatcher start

=head1 DESCRIPTION

This script launches a simple daemon that monitors a Plerd blog's
directory of Markdown-based source documents for changes. When it sees
changes, it will direct Plerd to publish or republish files as needed.

It also supports options to send and receive webmentions, as described
in L<"OPTIONS">, below.

I<plerdwatcher> runs via L<App::Daemon>, and accepts all the
command-line arguments documented there.

It stores its logs in a directory called C<log> and other run-time files
in a C<run> directory, both of which it expects to find as siblings of
its own parent directory (usually C<bin>).

For instructions on installing and using Plerd, please see the README
file that should have accompanied this distribution. It is also
available online at L<https://github.com/jmacdotorg/plerd#plerd>.

=head1 OPTIONS

=head2 config

 bin/plerdall --config=/path/to/plerd.conf

Specify the location of a valid Plerd config file.

If not specified here, then this program will seek a config file in these
locations, and in this order:

=over

=item *

C<plerd.conf>, in the current working directory

=item *

C<conf/plerd.conf>, in the current working directory

=item *

C<.plerd>, in your home directory

=back

Consult the config file generated by C<plerdall --init> to learn more
about the config file format.

=head1 EXPERIMENTAL OPTIONS

All of these Webmention-related options are B<experimental>.

=head2 send-webmentions

 bin/plerdwatcher --send-webmentions start

Newly created or updated posts will also attempt to send webmentions to
any URLs they link to.

=head2 receive-webmentions=I<port>

 bin/plerdwatcher --receive-webmentions=4000 start

 bin/plerdwatcher --receive-webmentions=4000 --ssl-cert=/path/to/cert.pem --ssl-key=/path/to/key.pem start

Launches a webserver that listens on the specified TCP port. This will
allow plerdwatcher to receive webmentions.

If you want to use an SSL cert/key pair with this (so that plerdwatcher will
listen for webmentions via HTTPS, rather than HTTP) add C<--ssl-cert> and
C<--ssl-key> options as well.

=head1 SEE ALSO

=over

=item *

L<Plerd>

=back

=head1 AUTHOR

Jason McIntosh <jmac@jmac.org>
