#!/usr/bin/perl

use Time::ParseDate;
use strict;
use Data::Dumper;
use DateTime;
use DateTime::Format::ICal;
use FileHandle;
use Mail::Sendmail;

my %pd_options = ( FUZZY => 1,
		   PREFER_FUTURE => 1 );

######################################################################
# user settings

# the path to a directory devoted to date_extract data
my $db_path = "/home/steve/.emailevents";
# the path where date_extract will publish the ical files.
# this must be writable by the procmail user and accessible
# via HTTP at the URL below
my $ical_path = "/home/httpd/xavier/html/cals";

### onboard help config
# the remote URL for the ical files
my $puburl    = "http://staticfree.info/cals/";

# the email address to listen on. 
my $my_addr   = "scheduler\@staticfree.info";


umask 0022; # for procmail which sets the umask to be more restrictive

######################################################################
# constants
my $mon = qr/\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\b/i;

######################################################################

my $event;
my $email = {};

if( $ARGV[0] ){
    $email->{from} = $ARGV[0];

}else{

    my $text = join "", <STDIN>;

    $email = parse_email( $text );
}

my $username = email_addr2user_name( $email->{from} );

my $db_file   = "$db_path/$username";
my $ical_file = "$ical_path/$username.ics";
my $pub_file  = "$puburl$username.ics";

unless( -e $db_file ){
    send_welcome( $email->{from} );
}

$event = extract_from_mail( $email ) if $email->{body};

my $events = read_events( $db_file ) if -e $db_file;

$events = [] unless $events;

if( $event && $event->{date_parsed} ){
    print "Event is in ".$event->{location}.
	" at ".localtime( $event->{date_parsed} )."\n";

    push @$events, $event;

}elsif( ! $email->{body} ){

}else{
    warn "no date found.\n";
}

gen_ical( $ical_file, $events );

write_events( $db_file, $events );



######################################################################
########################### subs #####################################
######################################################################

sub extract_from_mail{
    my( $email ) = @_;

    # attempt to understand the forwarded part of a message
    my( $pers_body, $forwarded ) = extract_forwarded( $email->{body} );
    my $event = extract_event( $pers_body, $email->{date} );

    $event->{subject} = $email->{subject};

    $event->{description} = "";
    $event->{description} .= $pers_body if $pers_body;

#     # remove signatures;
#     $event->{description} =~ s/\n--\s*\n(?:[^\n]+\n){2,10}//s;

    my $fwemail;
    if( $forwarded ){
	$fwemail = parse_email( $forwarded );
	$event->{description} .= $fwemail->{body};
	
    }

    unless( $event->{date_parsed} ){
	$event = extract_event( $fwemail->{body}, $fwemail->{date} );
    }

    return $event;
}

sub extract_event{
    my( $text, $now ) = @_;

    my %event;

    # explicitly marking
    #    Time: blah
    if( $text =~ /^\s*time\s*\:(.+?)$/smi ){

	$event{date} .= " $1 ";
    }
    

    if( $text =~ /^\s*(?:when|date)\s*\:(.+?)$/smi ){

	$event{date} .= " $1 ";
    }

    if( $text =~ /^\s*(?:location|where)\s*\:(.+?)$/msi ){

	$event{location} .= " $1 ";
    }

    if( $text =~ /^\s*(?:what|subject|topic|agenda)\s*\:(.+?)$/msi ){

	$event{summary} .= " $1 ";
    }

    $event{date} =~ s/^\s*(.+?)\s*$/$1/;
    $event{location} =~ s/^\s*(.+?)\s*$/$1/;
    $event{summary} =~ s/^\s*(.+?)\s*$/$1/;

    # local parsing options
    my %loc_opt;
    $loc_opt{NOW} = parsedate($now) if $now;

    my $error;
    ($event{date_parsed}, $error) = parsedate( pre_parsedate( $event{date} ), %loc_opt, %pd_options );

    warn "Could not parse the date, \"$event{date}\": $error\n" 
	if ($event{date} && !$event{date_parsed});
    return \%event;

}

# parses certain things out of the email body
sub parse_email{
    my( $text ) = @_;

    my $email = {};

    my $from;
    my $body;
    my $date;
    my $subject;

    # (?<=\n.{75,75})
    $text =~ s/=\n//gs;
    $text =~ s/=(\d\d)/char("0x$1")/xsg;

    my ( $header, $body ) = $text =~ /^(.+?)\n\n(.+)$/s;

    ($from) = $header =~ /\nfrom:\s*(.+?)\n/si;

    # attempt to get a date from the message
    ($date) = $header =~ /\ndate:\s*(.+?)\n/si;
    ($date) = $header =~ /\nsent:\s*(.+?)\n/si unless $date;

    ($subject) = $header =~ /\nsubject:\s*(.+?)\n/si;

    $email = { body => $body, from => $from,
	       date => $date, subject => $subject };

    return $email;

}

sub extract_forwarded{
    my( $body ) = @_;

    my( $pers_body, $forwarded ) = $body =~ /^(.*?)(?:\n-{3,15}\s*(?:forward(?:ed)?|original)[^\n]+?message[^\n]+?-{3,15}\n(.+)(?:-{3,15}\s*end\s+forward(?:ed)?(?:\s+message)?\s*-{3,15})?)?$/si;

    if( $pers_body && ! $forwarded ){
	#warn "no forwarded message found.\n";
    }

    if( !$pers_body && !$forwarded ){
	warn "no information could be extracted from the body. Strange.\n";
    }

    return ( $pers_body, $forwarded );
}

sub email_addr2user_name{
    my( $eaddr ) = @_;

    my $uname = $eaddr;
    # strip off the personal name if there is one.
    $uname =~ s/^.+?\<(.+)\>$/$1/;

    $uname =~ s/\@/./g;

    $uname =~ s/\.{2,}/./g;

    $uname = lc $uname;

    return $uname;
}

# filters out some known noise
sub pre_parsedate{
    my( $date ) = @_;

    # parsedate doesn't recognize:
    # "Monday, March 3"
    if( $date =~ /\b\w+day\b.+?$mon/i ){
	$date =~ s/^\b\w+day\b(?:[,.]?\s*)?(.+)$/$1/i;
    }
    return $date;
}

sub gen_ical{
    my( $ical_file, $events ) = @_;

    my $fh = new FileHandle;

    $fh->open( "> $ical_file" ) or die "cannot open $ical_file for writing: $!\n";

    print $fh <<EOS;
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Steve Pomeroy//email2ics//EN
EOS

foreach my $event (@$events){

    gen_ical_event( $event, $fh );
}

    print $fh <<EOS;
END:VCALENDAR
EOS

$fh->close;

}

sub read_events{

    my( $file ) = @_;

    my $fh = new FileHandle;
    my $events;

    $Data::Dumper::Indent = 1;

    $fh->open("<$file") or die "cannot open file for reading: $!\n";

    eval join("", <$fh>);

    close $fh;

    return $events;
}

sub write_events{
    my( $file, $events ) = @_;

    $Data::Dumper::Indent = 1;
    my $fh = new FileHandle;

    $fh->open(">$file") or die "cannot open file for writing: $!\n";

    $fh->print(Data::Dumper->Dump( [$events], ['events'] ));

    close $fh;
}

sub gen_ical_event{
    my( $event, $fh ) = @_;

    my $dt = DateTime->from_epoch( epoch => $event->{date_parsed} );
    my $dt2 = DateTime->from_epoch( epoch => $event->{date_parsed} );
    $dt2->add( DateTime::Duration->new( hours => 1 ) );

    my $icals =  DateTime::Format::ICal->format_datetime( $dt );
    my $icale =  DateTime::Format::ICal->format_datetime( $dt2 );

    my $subject = $event->{subject};

    # escape for ical
    $subject =~ s/\,/\\,/g;
#     $subject =~ s/[^\w\s]//g;
    print $fh <<EOS;
BEGIN:VEVENT
DTSTART:$icals
DTEND:$icale
SUMMARY:$subject
EOS

print $fh "LOCATION:".escape_text($event->{location})."\n" if $event->{location};
print $fh "DESCRIPTION:".escape_text($event->{description})."\n" if $event->{description};

print $fh <<EOS;
END:VEVENT
EOS

}

sub respond_to_user{
    my( $user, $from, $subject, $message ) = @_;

    my $username = email_addr2user_name($user);

    my %mail = ( To => $user,
		 From => $from, 
		 Subject => "[Staticfree Scheduler] $subject",
		 Message => $message );

    sendmail(%mail) or die $Mail::Sendmail::error;

}

sub escape_text{
    my( $text ) = @_;

    $text =~ s/,/\\,/g;
    $text =~ s/\n/\\n/gs;

    return $text;
}
sub send_welcome{
    my( $user ) = @_;

    respond_to_user( $user, $my_addr, "Welcome to Staticfree scheduler",
"Welcome to the email scheduler!

Your event is at:
$pub_file

What is this?

This system will let you create an iCalendar feed simply by sending 
an email to:
   $my_addr

When you get an email about some event you wish to have on your calendar,
simply forward the email to the scheduler and it will attempt to find event
time / location information in the message. Then, if it finds information, 
it will post it to your personal iCalendar feed located at:
  $pub_file 

This address is currently based off your email address, so you must
post from the same address.

You can use tools like Apple's iCal application, Evolution, 
Mozilla Sunbird and MS Exchange to subscribe to the calendar feed.

*Note*: right now, you must explicitly note the date/location in your
message. You can do this by adding:

   where: GCCIS Auditorium
   when: next thursday, 7:00pm

before the forwarded message. Everything but the date information is 
optional. The system understands absolute dates ('july 23', '7/14/2005')
and relative dates ('tomorrow', 'next friday'). If the date information
is stored in the forwarded part of the message, the system attempts
to base relative dates off the date the forwarded part was sent instead
of the date you send it.

Enjoy!

-Steve Pomeroy
http://staticfree.info/
");

}
