#!/usr/bin/perl

# Birthday Program
# Adam J. Griff, Ph.D.
# GriffMonster, LLC
# email: computer at GriffMonster dot com

# Description: Reminders of yearly events - birthdays, anniversaries, etc.

################ Copyright ################

# This program is Copyright 1991-2007 by Adam J. Griff, Ph.D. (GriffMonster, LLC)
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Perl Artistic License or the
# GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# If you do not have a copy of the GNU General Public License write to
# the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
# MA 02139, USA.

################ Module Preamble ################
# 2006-12-11 V2.6.3
# Updated directions for Windows XP
# 7/19/2004 V2.6.2
# Added info on creating MSWindows bat script
# 4/27/2003 V2.6.1
# Changed email address and name
# 6/18/98 V2.6
# Added the -i switch to handle international output <day>-<month>
# Fixed the parser to accept white-space around month and day on input
# 3/19/97 V2.5
# Fixed a bug with year calculation, when event is in the
# following year.
# Added info on writing a cron job script to email info daily
# 6/15/96 V2.4
# as recommened by the geekboy includes are now allowed
# also added date sorting on output to make the include work pretty
# 1/29/96 V2.3  (translated from Pascal V2.2 1/1/91)
# This program read a birthday file and notifies of upcoming
# birthdays and events

sub help {
  local($directions)=
  "       Birthday Program \n".
  "       V2.6.3  2006-12-11\n".
  "             by\n".
  "       Adam J. Griff, Ph.D.\n".
  "       GriffMonster, LLC\n".
  " (email: computer at GriffMonster dot com)\n".
  "\n\n\n\n".
  "                       PAGE 1\n".
  "This program tells you when it is a person's birthday or\n".
  "any other special event.\n\n".
  "The data file .birthday is by default expected in your\n".
  "home directory.\n".
  "The file must be in the format:\n".
  "   Date of birth (year,month,day)\n".
  "     Optional spaces can be placed before and after the month and day.\n".
  "   Name (first, at least one space, last, then enter)\n\n".
  "   Example: for Apr 01 1963 type\n".
  "            19630401Adam Griff\n\n".
  "   or      for  1980 Dec 5th type\n".
  "            19801205   Adam    Griff\n\n".
  "If this is not a birthday enter the '/' symbol after the date\n".
  "followed by text and terminated by '/'.\n".
  "New include feature: to add include file place the file name after \n".
  "the date\n enclosed in quotes.\n".
  "Lines without dates starting with the /'s are comment lines.\n\n".
  "Suppress the year count by entering the year as 19XX.\n".
  "          Example: 19XX1031 /It is Halloween!/\n\n".
  "          Example: 19XX0000 \"\/home\/foo\/bar\/birthday.extra\"".
  "\n\n\n\n\n".
  "                       PAGE 2\n\n".
  "Features to be used on the command line:\n".
  "  '-a' gives all dates and ages for current year's event.\n".
  "  '-d' gives all dates and current ages.\n".
  "  '-n' stops the date and time from printing.\n".
  "  '-h' for this help information.\n".
  "  '#' is the number of days in advance you \n".
  "      will be warned of a birthday or an event.\n".
  "  '-f <filename_1> <filename_2> ... <filename_n>' change the default\n".
  "       input filename to a list of file names. Must be last flag\n".
  "  '-i' causes the dates to print in international format\n".
  "The output is sorted in date order when -a or -d is used and in\n".
  "the order of closest to today first otherwise.\n".
  "The program is designed to warn of an event/birthday a week \n".
  "in advance and the day itself.\n\n".
  "$0 with no commands gives this help document.\n".
  "$0 must be followed by something to run.\n".
  " Ex: $0 -x  (run without changing defaults)\n".
  "     $0 7  (My recommendation. This will print \n".
  "time and date \n".
  " and warn you everyday for a week.)\n\n".
  "ENJOY and send me any comments or suggestions you may have.\n".
  "\n\n\n\n\n".
  "                       PAGE 3\n\n".
  "Below is a shell script that you can save and put into a cron job.\n".
  "This script will send you email if any events occurs in the next week.\n".
  "\n".
  "#!/bin/csh -f \n".
  "set f = \"/tmp/.birthday.\$USER\" \n".
  "rm -f \$f \n".
  "\$HOME/bin/birthday -n 7 > \$f \n".
  "if !(-z \$f) mail \$USER < \$f \n".
  "rm -f \$f \n".
  "\nThe following is a MSWindows .bat script you can put in your startup directory\n".
  "Assumes location of birthday program, data, and perl\n\n".
  "@echo off\n".
  "C:\n".
  "chdir C:/\Data/\Birthday\n".
  "c:\/cygwin/\bin/\perl birthday 7 -f C:/\Data/\Birthday/\birthday.doc\n".
  "pause\n";
  print $directions;
}


# return 1 if satisfied and 0 if not
sub testentry {
  local($day_count,$fday_count,$year,$fyear,$yearlength,
	$daysinadvance,
	$show_all,$current_count)=@_;
  local($retcode,$count);

  if ($show_all) {
    if ($current_count && ($fday_count > $day_count)) {
      $count=$year-$fyear-1;
    } else {
      $count=$year-$fyear;
    }
    $retcode=1;
  } else {
    if ($day_count == $fday_count ||

	($day_count+7)== $fday_count ||

	(($day_count <= $fday_count) &&
	 (($day_count+$daysinadvance) >= $fday_count)) ||
	
	(($day_count >= $fday_count) &&
	 ($day_count+$daysinadvance > $yearlength) &&
	 (($day_count+$daysinadvance - $yearlength) >= $fday_count))) {

      if ($fday_count < $day_count ) {
	$count=$year-$fyear+1;
      } else {
	$count=$year-$fyear;
      }
      $retcode=1;
    } else {
      $retcode=0;
    }
  }
  return ($retcode,$count);
}


# sorry about the mess, originally check_dates was only called once
# return $day_count
sub check_dates {
  local($infile,$mday,$mon,$year,$daysinadvance,
	       $show_all,$current_count)=@_;
  local($line_num)=0;
  local(*DATA);

  if (open(DATA,"<$infile")) {
    local(@mon_offset,$day_count,$fday_count,$leapyear,$yearlength);

    # leapyear is years divisable by 4 but not 100 unless by 400
    if (($year % 400) == 0 || (($year % 4) == 0 && ($year % 100) != 0)) {
      $leapyear=1;
    } else {
      $leapyear=0;
    }

    if ($leapyear) {
      $yearlength=366;
      @mon_offset=(0,31,60,91,121,152,182,213,244,274,305,335);
    } else {
      $yearlength=365;
      @mon_offset=(0,31,59,90,120,151,181,212,243,273,304,334);
    }

    if ($daysinadvance > $yearlength) { $daysinadvance = $yearlength; }
      

    $day_count=@mon_offset[$mon-1]+$mday;

    while ($item=<DATA>) {
      $line_num++;
      chop $item;
      if ($item =~ /^\//) {
	# comment line so skip it

      } elsif ($item =~ /^(.{4})\s*?(\d{2})\s*?(\d{2})(.*?)(\r?)$/) {
	# includes the fix for reading dos files
	local($fyear,$fmon,$fmday,$message)=($1,$2,$3,$4);
	local($show_year)=0;
	local($retcode,$count);

	# check if it is an include file
	if ($message =~ /\"(.*)\"/) {
	  $retcode = 0;
	  local($temp);
	  if (! defined $filenames{"$1"}) {
	    $filenames{"$1"} = 1;
	    &check_dates($1,$mday,$mon,$year,$daysinadvance,
			 $show_all,$current_count);
	  }
	} else {
	  # check if the year is XX'ed out
	  if ($fyear =~ /\d{4}/) { $show_year = 1; } else { $fyear = $year;}
	  
	  $fday_count=@mon_offset[$fmon-1]+$fmday;
	  ($retcode,$count) = &testentry($day_count,$fday_count,
					    $year,$fyear,$yearlength,
					    $daysinadvance,
					    $show_all,$current_count);

	  if ($retcode) {
	    $unique_count++;
	    # fix the day number for proper sorting.
	    if (! $show_all && $fday_count < $day_count) {
	      $fday_count += 366;
	    }
	    $OUT_DATA{"$fday_count\0$unique_count"}= 
		join("\0",$count,$show_year,$fmon,$fmday,$message);
	  }
	}
      } else {
	print "Error in $infile input file format line $line_num :\n$item\n";
      }
    }

  } else {
    print "Could not open the \"$infile\" input file.\n".
	"Help Usage: $0 -h\n";
  }
  return $day_count;
}

# sort in date order, remove the unique id
sub number_order {
  local($a_num,$b_num,$count);
  ($a_num,$count) = split("\0",$a);
  ($b_num,$count) = split("\0",$b);
  $a_num <=> $b_num;}


# Main Body
local(@infile);
local($daysinadvance,$show_datentime,$show_all,$international_date)=(0,1,0,0);
local($current_count)=0;  # number as of today not at the time of event
local($file_count)=0;
$infile[0]="$ENV{'HOME'}/.birthday";

while ($ARGV = shift) {
  if    ("$ARGV" eq "-a")           { $show_all=1;}
  elsif ("$ARGV" eq "-d")           { $show_all=1; $current_count=1;}
  elsif ("$ARGV" eq "-f")           { 
    while ($ARGV = shift) {
      $infile[$file_count] = $ARGV;
      $file_count++;
    }
  }
  elsif ("$ARGV" eq "-n")           { $show_datentime=0;}
  elsif ("$ARGV" eq "-i")           { $international_date=1;}
  elsif ("$ARGV" eq "-h")           { &help(); exit;}
  elsif ("$ARGV" =~ /^\d+$/ )       { $daysinadvance=$ARGV;}
}

local($date_string);
$date_string=localtime(time);
if ($show_datentime) {
  print "$date_string\n";
}

local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$mon++;       # make the month be from 1-12
$date_string =~ /([\d]{4})$/;
$year=$1;     # get the year as a 4 digit number instead of 2(bad)

# some formats for check_dates output

format TOP =
Name or Message                                                Count  Date
.

    format OTHER =
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<< @<-@<
$message,                        $agestr,$fmon,$fmday
~ ^<<<<<<<<
$message
.

    format OTHER_I =
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<< @<-@<
$message,                        $agestr,$fmday,$fmon
~ ^<<<<<<<<
$message
.

    format BIRTHDAY =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Birthday @<<<<<<< @<-@<
$message,                        $agestr,$fmon,$fmday
.	    

    format BIRTHDAY_I =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Birthday @<<<<<<< @<-@<
$message,                        $agestr,$fmday,$fmon
.	    

$^ = 'TOP';    #set top-of-form format.
$= = 100000;   # set page length to very big to stop multiple headers

$unique_count=0;


foreach $i (0..$#infile) {
  if (! defined $filenames{"$infile[$i]"}) {
    $filenames{"$infile[$i]"} = 1;
    &check_dates($infile[$i],$mday,$mon,$year,$daysinadvance,
		 $show_all,$current_count);
  }
}

foreach $item (sort number_order keys %OUT_DATA) {
  local($agestr);
  local($count,$show_year,$fmon,$fmday,$message) = 
      split("\0",$OUT_DATA{$item});
  # check if it is a birthday
  if ( $message =~ /^.*?\/(.*?)\/$/) {
    $message = $1;
    if ($show_year) { $agestr="$count years ";}
    if ($international_date) {
      $~ = 'OTHER_I';
    } else {
      $~ = 'OTHER';
    }
    write;
  } else {
    if ($show_year) { $agestr="Age $count ";} else {$agestr="";}
    if ($international_date) {
      $~ = 'BIRTHDAY_I';
    } else {
      $~ = 'BIRTHDAY';
    }
    write;
  }
}
