The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package App::PM::Announce;

use warnings;
use strict;

=head1 NAME

App::PM::Announce - Announce your PM meeting via Meetup and LinkedIn

=head1 VERSION

Version 0.025

=cut

our $VERSION = '0.025';

use Moose;
#with 'MooseX::LogDispatch';

use File::HomeDir;
use Path::Class;
use Config::JFDI;
use Config::General;
use String::Util qw/trim/;
use Data::UUID;
use Document::TriPart;
use DateTimeX::Easy;
use Log::Dispatch;
use Log::Dispatch::Screen;
use Log::Dispatch::File;

use App::PM::Announce::History;
use App::PM::Announce::Feed::meetup;
use App::PM::Announce::Feed::linkedin;
use App::PM::Announce::Feed::greymatter;
use App::PM::Announce::Feed::useperl;

sub BUILD {
    my $self = shift;
    $self->startup;
}

has debug => qw/is ro lazy_build 1/;
sub _build_debug {
    return $ENV{APP_PM_ANNOUNCE_DEBUG} ? 1 : 0;
}

has verbose => qw/is ro lazy_build 1/;
sub _build_verbose {
    return 0;
}

has dry_run => qw/is ro lazy_build 1/;
sub _build_dry_run {
    return 0;
}

has home_dir => qw/is ro lazy_build 1/;
sub _build_home_dir {
    my @home_dir;
    @home_dir = map { exists $ENV{$_} && defined $ENV{$_} ? $ENV{$_} : () } qw/APP_PM_ANNOUNCE_HOME/; # Don't want to write $ENV{...} twice
    @home_dir = ( File::HomeDir->my_data, '.app-pm-announce' ) unless @home_dir;
    return dir( @home_dir );
}

has config_file => qw/is ro lazy_build 1/;
sub _build_config_file {
    return shift->home_dir->file( 'config' );
}

has config_default => qw/is ro isa HashRef lazy_build 1/;
sub _build_config_default {
    return {};
}

#has _config => qw/is ro isa Config::JFDI lazy_build 1/;
#sub _build__config {
#    my $self = shift;
#    return Config::JFDI->new(file => $self->config_file);
#}

#sub config {
#    return shift->_config->get;
#}

has config => qw/is ro isa HashRef lazy_build 1/;
sub _build_config {
    my $self = shift;
    if ($self->config_file) {
        return { Config::General->new(
            -ConfigFile => $self->config_file,
        )->getall };
    }
    else {
        return $self->config_default,
    }
}

has log_file => qw/is ro lazy_build 1/;
sub _build_log_file {
    return shift->home_dir->file( 'log' );
}

has logger => qw/is ro isa Log::Dispatch lazy_build 1/;
sub _build_logger {
    my $self = shift;
    my $logger = Log::Dispatch->new( callbacks => sub {
        my $message = join ' ',
                "[@{[ DateTime->now->set_time_zone( 'local' ) ]}]",
                "[$_[3]]",
                "$_[1]\n",
        ;
#        $message = "# $message" if $_[3] eq 'debug';
        return $message;
    } );
    $logger->add( Log::Dispatch::Screen->new( name => 'screen', min_level => $self->debug ? 'debug' : 'info', stderr => 1 ) ) if $self->debug;
#    $logger->add( Log::Dispatch::File->new( name => 'file', mode => 'append', min_level => 'info', filename => $self->log_file.'' ) );
    return $logger;
}

has feed => qw/is ro isa HashRef lazy_build 1/;
sub _build_feed {
    my $self = shift;
    return { 
        meetup => $self->_build_meetup_feed,
        linkedin => $self->_build_linkedin_feed,
        greymatter => $self->_build_greymatter_feed,
        useperl => $self->_build_useperl_feed,
    };
}

sub _build_meetup_feed {
    my $self = shift;
    return undef unless my $given = $self->config->{feed}->{meetup};
    return App::PM::Announce::Feed::meetup->new(
        app => $self,
        username => $given->{username},
        password => $given->{password},
        uri => $given->{uri},
        venue => $given->{venue},
    );
}

sub _build_linkedin_feed {
    my $self = shift;
    return undef unless my $given = $self->config->{feed}->{linkedin};
    return App::PM::Announce::Feed::linkedin->new(
        app => $self,
        username => $given->{username},
        password => $given->{password},
        uri => $given->{uri},
    );
}

sub _build_greymatter_feed {
    my $self = shift;
    return undef unless my $given = $self->config->{feed}->{greymatter};
    return App::PM::Announce::Feed::greymatter->new(
        app => $self,
        username => $given->{username},
        password => $given->{password},
        uri => $given->{uri},
    );
}

sub _build_useperl_feed {
    my $self = shift;
    return undef unless my $given = $self->config->{feed}->{useperl};
    return App::PM::Announce::Feed::useperl->new(
        app => $self,
        username => $given->{username},
        password => $given->{password},
        promote => $given->{promote},
    );
}

has history => qw/is ro isa App::PM::Announce::History lazy_build 1/;
sub _build_history {
    my $self = shift;
    return App::PM::Announce::History->new( app => $self );
}

sub startup {
    my $self = shift;

    $self->logger->debug( "debug = " . $self->debug );
    $self->logger->debug( "verbose = " . $self->verbose );
    $self->logger->debug( "dry-run = " . $self->dry_run );

    my $home_dir = $self->home_dir;
    $self->logger->debug( "home_dir = $home_dir" );

    unless (-d $home_dir) {
        $self->logger->debug( "Making $home_dir because it does not exist" );
        $home_dir->mkpath;
    }

    # Gotta do this here
    $self->logger->add( Log::Dispatch::File->new( name => 'file', mode => 'append', min_level => 'info', filename => $self->log_file.'' ) );

    my $log_file = $self->log_file;
    $self->logger->debug( "log_file = $log_file" );

    my $config_file = $self->config_file;
    if (defined $config_file) {
        $self->logger->debug( "config_file = $config_file" );

        unless (-f $config_file) {
            $self->logger->debug( "Making $config_file stub because it does not exist" );
            $config_file->openw->print( <<_END_ );
# vim: set filetype=configgeneral:

# Replace 'An-Example-Group' with the real resource for your Meetup group
# Replace <venue> with the venue number you want to be the default

#<feed meetup>
#    username
#    password
#    uri http://www.meetup.com/An-Example-Group/calendar/?action=new
#    venue <venue>
#</feed>

# Replace <gid> with the gid of your group

#<feed linkedin>
#    username
#    password
#    uri http://www.linkedin.com/groupAnswers?start=&gid=<gid>
#</feed>

# Replace 'example.com' with a real host

#<feed greymatter>
#    username
#    password
#    uri http://example.com/cgi-bin/greymatter/gm.cgi
#</feed>

#<feed useperl>
#    username
#    password
#</feed>

_END_
        }
    }
}



sub announce {
    my $self = shift;
    my %event;
    if (ref $_[0]) {
        my $document = $self->parse( @_ );
        %event = %{ $document->header };
        $event{description} = $document->body;
    }
    else {
        %event = @_;
    }

    { # Validate, parse, and filter.

        $event{$_} = trim $event{$_} for qw/title venue/;

        die "Wasn't given a UUID for the event\n" unless $event{uuid};

        die "Wasn't given a title for the event\n" unless $event{title};

#        die "Wasn't given a venue for the event\n" unless $event{venue};

        die "Wasn't given a date & time for the event\n" unless $event{datetime};
        die "The date & time isn't a DateTime object\n" unless $event{datetime}->isa( 'DateTime' );
    }

    my (@report, $event, $result);
    my $uuid = $event{uuid};
    $event = $self->history->find_or_insert( $uuid )->{data};
    $self->history->update( $uuid => %event );

    eval {
        if ($event->{did_meetup}) {
            $self->logger->debug( "Already posted to meetup, skipping" );
            $self->logger->debug( "The Meetup link is " . $event->{meetup_link} ) if $event->{meetup_link};
            push @report, "Already announced on meetup";
        }
        elsif ($self->feed->{meetup}) {
            unless ($self->dry_run) {
                die "Didn't announce on meetup" unless $result = $self->feed->{meetup}->announce( %event );
                my $meetup_link = $event->{meetup_link} = $result->{meetup_link};
                $self->logger->debug( "Meetup link is " . $meetup_link );
                $self->logger->info( "\"$event{title}\" ($uuid) announced to meetup ($meetup_link) " );
                $self->history->update( $uuid => did_meetup => 1, meetup_link => "$meetup_link" );
                push @report, "Announced on meetup";
            }
            else {
                push @report, "Would announce on meetup";
            }
        }
        else {
            $self->logger->debug( "No feed configured for meetup" );
        }

        die "Don't have a Meetup link" unless $self->dry_run || $event->{meetup_link};

#        $event{description} = [
#            $event{description},
#            "\nRSVP at Meetup - <a href=\"$event->{meetup_link}\">$event->{meetup_link}</a>"
#        ];

        if ($event->{did_linkedin}) {
            $self->logger->debug( "Already posted to linkedin, skipping" );
            push @report, "Already announced on linkedin";
        }
        elsif ($self->feed->{linkedin}) {
            unless ($self->dry_run) {
                die "Didn't announce on linkedin" unless $result = $self->feed->{linkedin}->announce(
                    %event,
                    description => [
                        $event{description},
                        "RSVP at Meetup - $event->{meetup_link}",
                    ],
                );
                $self->logger->info( "\"$event{title}\" ($uuid) announced to linkedin" );
                $result = $self->history->update( $uuid => did_linkedin => 1 );
                push @report, "Announced on linkedin";
            }
            else {
                push @report, "Would announce on linkedin";
            }
        }
        else {
            $self->logger->debug( "No feed configured for linkedin" );
        }

        if ($event->{did_greymatter}) {
            $self->logger->debug( "Already posted to greymatter, skipping" );
            push @report, "Already announced on greymatter";
        }
        elsif ($self->feed->{greymatter}) {
            unless ($self->dry_run) {
                die "Didn't announce on greymatter" unless $result = $self->feed->{greymatter}->announce(
                    %event,
                    description => [
                        $event{description},
                        "\nRSVP at Meetup - <a href=\"$event->{meetup_link}\">$event->{meetup_link}</a>"
                    ],
                );
                $self->logger->info( "\"$event{title}\" ($uuid) announced to greymatter" );
                $result = $self->history->update( $uuid => did_greymatter => 1 );
                push @report, "Announced on greymatter";
            }
            else {
                push @report, "Would announce on greymatter";
            }
        }
        else {
            $self->logger->debug( "No feed configured for greymatter" );
        }

        if ($event->{did_useperl}) {
            $self->logger->debug( "Already posted to useperl, skipping" );
            push @report, "Already announced on useperl";
        }
        elsif ($self->feed->{useperl}) {
            unless ($self->dry_run) {
                die "Didn't announce on useperl" unless $result = $self->feed->{useperl}->announce(
                    %event,
                    description => [
                        $event{description},
                        "\nRSVP at Meetup - <a href=\"$event->{meetup_link}\">$event->{meetup_link}</a>"
                    ],
                );
                $self->logger->info( "\"$event{title}\" ($uuid) announced to useperl" );
                $result = $self->history->update( $uuid => did_useperl => 1 );
                push @report, "Announced on useperl";
            }
            else {
                push @report, "Would announce on useperl";
            }
        }
        else {
            $self->logger->debug( "No feed configured for useperl" );
        }
    };
    if ($@) {
        warn "Unable to announce \"$event{title}\" ($uuid)\n";
        die $@;
    }

    $event = $self->history->fetch( $uuid )->{data};
#    $self->logger->info("\"$event{title}\" is announced on", join ', ', map { $event->{"did_$_"} ? $_ : () } qw/meetup linkedin greymatter/);
#    $self->logger->info("Meetup link is $event->{meetup_link}") if $event->{meetup_link};

    return $event, \@report;
    
#    $result{done} = 1;
#    return \%result;
}

use Data::Dump qw/dd pp/;
sub parse {
    my $self = shift;

    die "Couldn't parse" unless my $document = Document::TriPart->read(shift);

    my $datetime = $document->header->{datetime};
    die "You didn't give a datetime" unless $datetime;
    die "Unable to parse ", $document->header->{datetime} unless $datetime = DateTimeX::Easy->parse( $datetime );
    $document->header->{datetime} = $datetime;

    return $document;
}

sub template {
    my $self = shift;
    my %given = @_;

    my $uuid = Data::UUID->new->create_str;
    my $datetime = DateTimeX::Easy->parse( '4th tuesday' );
    my $venue = $self->config->{venue} || '';
    $datetime = DateTimeX::Easy->parse( '3rd tuesday' ) unless $datetime;
    $datetime->set(hour => 20, minute => 0, second => 0);

    return <<_END_;
# App-PM-Announce
# You can leave 'venue' blank to use the default venue (per @{[ $self->config_file ]})
# The 'datetime' field is the date & time that the event will take place. Any reasonable string should do (parsed via DateTimeX::Easy)
---
title: The title of the event
venue: $venue
datetime: $datetime
image: $given{image}
uuid: $uuid
---
Put your multi-line description for the event here.
Everything below the '---' is considered the description.
_END_
}

=head1 SYNOPSIS

    # Initialize and edit the config (only need to do this once)
    pm-announce config edit
    
    # Generate a template for the event
    pm-announce template > event.txt

    # Edit event.txt with your editor of choice...

    # Announce the event
    pm-announce announce < event.txt

=head1 DESCRIPTION

App::PM::Announce is a tool for creating and advertising PM meetings (on Meetup, LinkedIn, and blog software)

            -v, -d,  --verbose  Debugging mode. Be verbose when reporting
            -h, -?,  --help     This help screen

        config              Check the config file ($HOME/.app-pm-announce/config)

        config edit             Edit the config file using $EDITOR

        history                 Show announcement history

        history <query>         Show announcement history for event <query>, where <query> should be enough of the uuid to be unambiguous

        template                Print out a template to be used for input to the 'announce' command

            --image <image>     Attach <image> (can be either a local file or remote URL) to the Meetup event

        announce                Read STDIN for the event information and make a post for each feed

            -n, --dry-run       Don't actually login and announce, just show what would be done

        test                    Post a bogus event to a test meetup account, test linkedin account, and test greymatter account

        help                    This help screen

=cut

=head1 AUTHOR

Robert Krimen, C<< <rkrimen at cpan.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-app-pm-announce at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=App-PM-Announce>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.




=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc App::PM::Announce


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-PM-Announce>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/App-PM-Announce>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/App-PM-Announce>

=item * Search CPAN

L<http://search.cpan.org/dist/App-PM-Announce/>

=back


=head1 ACKNOWLEDGEMENTS


=head1 COPYRIGHT & LICENSE

Copyright 2009 Robert Krimen, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.


=cut

1; # End of App::PM::Announce