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

use strict;
use warnings;

use Net::Twitter;
use File::MkTemp;
use HTTP::Date 'parse_date';
	
our $VERSION="0.1.3";
our @EXPORT = qw(new);

use constant PASS => 1;
use constant FAIL => 0;

use constant ERR_NO_ERROR => "";
use constant ERR_NO_ERROR_NUM => 0;
use constant ERR_PUBLISH => 3;
use constant ERR_PUBLISH_MSG =>"Can't publish entry : ";
use constant ERR_TW_TIMELINE => 4;
use constant ERR_TW_TIMELINE_MSG =>"Can't retrieve Twitter timeline";
use constant ERR_TEMP_FILE => 5;
use constant ERR_TEMP_FILE_MSG =>"Can't save entry to temporary file";

use constant NEWLINE => "\n";


=pod

=head1 NAME

Twitter::Daily - Publishes a blog entry using the given day Twitter tweets

=head1 SYNOPSIS

	my $twDaily = Twitter::Daily->new( 'TWuser' => $twUser,
									   'twitter' => $twitter,
									   'blog' => $blog,
									   'entry' => $entry,
									   'verbose' => $verbose
									   ) || die("Not all options were passed");
								
	$twDaily->postNews($date,$title) || do {
		my $error =  $twDaily->errMsg();
		$twDaily->close(); 
		die( $error ); 
	};
	
	$twDaily->close();
 
=head1 DESCRIPTION 

This package contains the very Twitter::Daily core and coordinates Twitter
timeline retrieval and blog publishing

=head2 new

Constructor. Accepts the next parameters :

=over 1

=item * TWuser : twitter username

=item * twitter : Net::Twitter created object

=item * blog : blog publishing object based on Twitter::Daily::Blog::Base (e.g Blosxom::Publish)

=item * verbose : more activity related messages than usual :-D

=back

=cut


sub new {
    my $class = shift;
    my %option = @_;
    ## ToDo make 'twitter' use not a Net::Twitter object
    ## but one whose the user can be obtained from it 
	my $mandatory = [ 'twitter', 'TWuser', 'blog', 'entry'];
	my $optional = ['verbose', 'silent'];
	
    my $this;

    
    $this->{'silent'} = 0    if (! defined $this->{'silent'} );
	$this->{'verbose'} = 0   if (! defined $this->{'verbose'} );
    
    
    for my $opt ( @$mandatory ) {
    	_verboseMessage($this, "new: processing option " . $opt );
    	return undef  if ( ! defined $option{$opt} );
    	$this->{$opt} = $option{$opt};
    }
    
    for my $option ( @$optional ) {
    	if ( defined $option{$option} ) {
    		$this->{$option} = $option{$option}
    	} 
    }

    $this->{'errMsg'} = "";
    $this->{'errNumber'} = 0;

    return bless $this, $class;
};

=head2 postNews

Obtains Twitter entries for the day and creates a new entry in the blog.
Returns 1 on success and 0 on fail. The error can be retrieved using errMsg and errNumber

=cut

sub postNews {
    my $this = shift;
    ### ToDo add parameter verification
    my $date = shift;
    my $title = shift;
    
    my ($entryFile,$entries) = $this->_buildEntry($date,$title);
    return FAIL     if (! $entryFile );

	if ( ! $entries ) {
		$this->_normalMessage("No entries to publish");
		$this->_verboseMessage("Deleting file $entryFile");
	    unlink( $entryFile ); 
	    $this->_setError(ERR_NO_ERROR, ERR_NO_ERROR_NUM);
		return PASS;
	}
	
    $this->_verboseMessage("Publishing entry");
    
    $this->{'blog'}->publish( $entryFile ) 
    	|| do {
    			$this->_verboseMessage("Deleting file $entryFile");
    			unlink( $entryFile );
    			return  $this->_setError( ERR_PUBLISH_MSG . $this->{'blog'}->errMsg,
       			  				    	  ERR_PUBLISH );
    	};
	
    $this->_verboseMessage("Deleting file $entryFile");
	unlink( $entryFile );
	$this->_setError(ERR_NO_ERROR, ERR_NO_ERROR_NUM);
	return PASS;    
}

=head2 close

Ends relationship woth Twitter and Blog

=cut

sub close {
    my $this = shift;


    $this->_verboseMessage("Bye, Blog !");
    $this->{'blog'}->quit();
	$this->_setError(ERR_NO_ERROR, ERR_NO_ERROR_NUM);
	return PASS;  
}

## ToDo change error management using Error module

=head2 errMsg

Returns the last error message in English language.
Will return an empty string if the last operation ended successfuly

=cut

sub errMsg {
	return $_[0]->{'errMsg'};
}


## ToDo change error management using Error module

=head2 errNumber

Returns the last error number.
Will return zero if the last operation ended successfuly

=cut

sub errNumber {
	return $_[0]->{'errNumber'};
}

sub _acceptEntry($$) {
    my $this = shift;
	my $date = shift;
	my $line = shift;
	
	my ($year, $month, $day, $hour, $min, $sec, $tz) =
            parse_date($date);
        $month = $this->_getMonthName($month);

	## ToDo use Twitter::Date
	## Quick Twitter date parsing
	## Sample date : Tue May 26 20:25:13 +0000 2009
	my ($Twday, $Tmonth, $Tday, $Ttime, $Ttz, $Tyear) =
            split (/ /, $line->{'created_at'});

       return  ( ($year == $Tyear) && ( $month eq $Tmonth ) && ($Tday == $day) );
}

sub _buildEntry($$$) {
	my $this = shift;
    my $date = shift;
    my $title = shift;
    my $entries = 0;

    $this->_verboseMessage("Obtaining Twitter timeline ($date)" );
	my $timeline = $this->{'twitter'}->user_timeline(
						{  id => $this->{'TWuser'}, since => $date } );

	return  $this->_setError( ERR_TW_TIMELINE_MSG . ' (' . $@ . ')' , ERR_TW_TIMELINE )	
		if (! defined $timeline);

	$this->{'entry'}->setTitle( $this->_getTitle($title, $date) );

    foreach my $line ( @$timeline ) {
    	## Since Apr 12th 2009 (aprox) Net::Twitter retrieves more
    	## entries than expected, then we need to perform a local
    	## filtering based on the entry date :-(
    	if (! $this->_acceptEntry($date, $line)) {
    		$this->_verboseMessage("Entry rejected (" . $line->{'created_at'}.") : "
    		                       . "'" . $line->{'text'} . "'" );
    		next;
    	}

    	$this->_normalMessage("Generating entry '" . $line->{'text'} . "' (" . $line->{'created_at'}.")" );
		$this->{'entry'}->addLine( $line->{'text'}, $line->{'created_at'}  );
    	$entries++;
    }

	my $fname = _saveToTempFile($this, $this->{'entry'} );
	
 	return  $this->_setError( ERR_TEMP_FILE , ERR_TEMP_FILE_MSG )	
		if (! $fname);

	return ($fname,$entries);
};

sub _setError {
	my $this = shift;

	$this->{'errMsg'} = $_[0];
	$this->{'errNumber'} = $_[1];
	return FAIL;
}


sub _normalMessage($$) {
	my $this = shift;
	my $msg = shift;
	
	print "$msg\n"  if ( ! $this->{'silent'} );
}

sub _verboseMessage($$) {
	my $this = shift;
	my $msg = shift;
	
	print "$msg\n"  if ( $this->{'verbose'} != 0 );
}

sub _getTitle {
	my $this  = shift;
	my $title = shift;
	my $date  = shift;

	if ( !$title ) {
		my ($year, $month, $day, $hour, $min, $sec, $tz) = parse_date($date);
		my $m = $this->_getMonthName($month);
		$title = "Twitter timeline for $m $day, $year";
	}
	
	$this->_verboseMessage( "News title : '" . $title . "'");
	return $title;
}

sub _getMonthName($$) {
	my $this  = shift;
	my $month = shift;
	my @m = ('Jan', 'Feb', 'Mar', 'Apr', 
                 'May', 'Jun', 'Jul', 'Aug', 
                 'Sep', 'Oct', 'Nov', 'Dec');
	$month--;
	
	return $m[$month];
}

sub _saveToTempFile {
	my $this = shift;
	my $entryBuilder = shift;

	$this->_verboseMessage("Generating temp file");
	my $fname = mktemp( 'twitterXXXXXX', '.');
    $this->_verboseMessage("Temp filename : $fname");

	$this->_verboseMessage("Opening it");
	my $fh;
	open ($fh, '>', $fname) || return FAIL;
			
	$this->_verboseMessage("Writing entry");
	print $fh $entryBuilder->getEntry();
	
	$this->_verboseMessage("Closing entry temp file");
	CORE::close($fh);
	
    return $fname;
}


=pod

=head1 AUTHOR

Victor A. Rodriguez (Bit-Man)


=cut

1;