#!/usr/local/bin/perl -w
###########################################
# ical-daemon - Parse .ics files and send
#               alerts on upcoming events.
# Mike Schilli, 2010 (m@perlmeister.com)
###########################################
use strict;
use local::lib;
use iCal::Parser;
use Log::Log4perl qw(:easy);
use App::Daemon qw(daemonize);
use Sysadm::Install qw(mkd slurp tap);
use FindBin qw($Bin);

our $UPDATE_REQUESTED = 0;
our $ALERT_BEFORE = 
 DateTime::Duration->new( minutes => 15 );
our $CURRENT_DAY      = DateTime->today();
our @TODAYS_EVENTS    = ();

my($home)  = glob "~";
my $admdir = "$home/.ical-daemon";
my $icsdir = "$admdir/ics";

mkd $admdir unless -d $admdir;
mkd $icsdir unless -d $icsdir;

$App::Daemon::logfile = "$admdir/log";
$App::Daemon::pidfile = "$admdir/pid";

if( exists $ARGV[0] and 
  $ARGV[0] eq '-q' ) {
  my $pid = App::Daemon::pid_file_read();
  kill 10, $pid; # Send USR1
  exit 0;
}

Log::Log4perl->easy_init({ 
  level => $DEBUG, 
  file  => $App::Daemon::logfile 
});

$SIG{ USR1 } = sub {
    DEBUG "Received USR1";
    $UPDATE_REQUESTED = 1;
};

$UPDATE_REQUESTED = 1; # bootstrap

daemonize();

while(1) {
  my $now = DateTime->now( 
    time_zone => 'local' );

  my $today =  
    $now->clone->truncate( to => 'day' );

  if( $UPDATE_REQUESTED or
      $CURRENT_DAY ne $today ) {

    $UPDATE_REQUESTED = 0;
    $CURRENT_DAY      = $today;

    DEBUG "Updating ...";
    @TODAYS_EVENTS    = update( $now );
    DEBUG "Update done.";
  }

  if( scalar @TODAYS_EVENTS ) {
    my $entry         = $TODAYS_EVENTS[0];
                         
    DEBUG "Next event at: $entry->[0]";

    if( $now >
        $entry->[0] - $ALERT_BEFORE ) {
      INFO "Notification: ",
           "$entry->[1] $entry->[0]";
      tap "$Bin/ical-notify", $entry->[1],
          $entry->[0];
      shift @TODAYS_EVENTS;
      next;
    }
  }

  DEBUG "Sleeping";
  sleep 60;
}

###########################################
sub update {
###########################################
  my($now) = @_;

  my $start = $now->clone->truncate( 
      to => 'day' );
  my $tomorrow = $now->clone->add( 
      days => 1 );

  my $parser=iCal::Parser->new( 
      start => $start, 
      end   => $tomorrow );

  my $hash;

  for my $file (<$icsdir/*.ics>) {
    DEBUG "Parsing $file";
    $hash = $parser->parse( $file );
  }

  my $year  = $now->year;
  my $month = $now->month;
  my $day   = $now->day;

  if(! exists $hash->{ events }->{ 
          $year }->{ $month }->{ $day } ) {
      return ();
  }

  my $events = $hash->{ events }->{ 
          $year }->{ $month }->{ $day };

  for my $key ( keys %$events ) {
      if( event_is_holiday( 
              $events->{ $key } ) ) {
          WARN "No alerts today (holiday)";
          return ();
      }
  }

  my @events = ();

  for my $key ( keys %$events ) {
    next if $now > 
      $events->{ $key }->{ DTSTART };
        # already over?

    push @events, [
     $events->{ $key }->{ DTSTART },
     $events->{ $key }->{ DESCRIPTION },
    ];
  }

  @events = sort { $a->[0] <=> $b->[0] } 
              @events;

  return @events;
}

###########################################
sub event_is_holiday {
###########################################
  my($event) = @_;

  return undef unless 
    exists $event->{ ATTENDEE };

  if( $event->{ ATTENDEE }->[ 0 ]->{ CN } 
      eq "US Holidays" ) {
    return 1;
  }
  return 0;
}
