#!/usr/bin/perl
#  recur.pl - Manage and update recurring transactions in a .cbb file.
#
#  Written by Curtis Olson.  Started January 16, 1996.
#
#  Copyright (C) 1996  Curtis L. Olson  - curt@sledge.mn.org
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of 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.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# $Id: recur.pl,v 2.8 1997/02/28 21:21:58 curt Exp $
# (Log is kept at end of this file)


use strict;    # don't take no guff

require "timelocal.pl";


package CBB;

my($key, $date, $check, $desc, $debit, $credit, $cat, $com, $cleared, $total);
my($recur, $result, $update);
my($future_days, $secs_per_day, $cur_date, $account);


# specify the installed location of the necessary pieces.
$CBB::cbb_incl_dir = "/usr/local/share/cbb";
unshift(@INC, $CBB::cbb_incl_dir);

require "categories.pl";
require "common.pl";
require "engine.pl";


($#ARGV >= 0) || die "Usage: $0 account";


# how many days to plan ahead
#$future_days = 366;		# approximately 1 year;
$future_days = 92;		# approximately 3 months;
$secs_per_day = 86400;		# seconds per day;

$cur_date = &raw_date;

$account = shift(@ARGV);
if ( $account !~ /\.cbb$/ ) {
    die "Account name must end in '.cbb'\n";
}

(&load_trans($account) eq "ok") || die "Cannot open account:  $account";

#-----------------------------------------------------------------------
# Traverse all transactions and perform the following steps on entries with
# $cleared = "?":
# 
#   1.  If date has passed, change $cleared to "!"
#   2.  If date is present or future, delete entry.  These entries will be
#       reinserted later.
#-----------------------------------------------------------------------

print "Updating/deleting current recurring transactions:  ";

$result = &first_trans();
while ( $result ne "none" ) {
    ($key, $date, $check, $desc, $debit, $credit, $cat, $com, $cleared,
     $total) = split(/\t/, $result);

    if ( $cleared eq "?" ) {
	if ( $date < $cur_date ) {
	    # set $cleared to "!"
	    # print "updating - $result\n";
	    print ".";
	    $update = "$key\t$date\t$check\t$desc\t$debit\t$credit\t".
		"$cat\t$com\t!\t$total";
	    &update_trans($update);
	} else {
	    # delete
	    # print "deleting - $result\n";
	    print ".";
	    &delete_trans($key);
	}
    }
    
    $result = &next_trans();
}

print "\n";


#-----------------------------------------------------------------------
# Now add in all future recurring transactions
#-----------------------------------------------------------------------

print "Adding in future recurring transactions:  ";

# print "$account - " . &file_root($account) . "\n";
$recur = &file_root($account) . ".rcr";	# 
# print "$recur\n";		# 
open(RECUR, "<$recur") || die "Cannot open:  $recur";

while ( <RECUR> ) {
    # print length($_) . " - $_";
    if ( m/^#/ || ! m/\t/ ) {
	# ignore this line
    } else {
	# Ok, we found one!
	&add_all_recurs($_);
    }
}

close(RECUR);

# Finally, save the result

(&save_trans("$account") eq "ok") || die "Cannot save account:  $account";


#-----------------------------------------------------------------------
# Supporting Routines
#-----------------------------------------------------------------------

# Add all recuring transactions specified

sub add_all_recurs {
    my($line) = @_;
    my($days, $months, $years, $desc, $debit, $credit, $com, $cat, $begindate, 
       $cutoff);
    my($date, $dates, $key, $trans, @DATES);

    chop($line);

    ($days, $months, $years, $desc, $debit, $credit, $com, $cat,
     $begindate, $cutoff) = split(/\t/, $line);

    if ( ($begindate eq "") || ($begindate < $cur_date) ) {
	$begindate = $cur_date;
    }

    if ( $cutoff eq "" ) {
	$cutoff = &calc_cutoff();
    }

    # print "cutoff = $cutoff\n";

    if ( ($days < 32) || ($days =~ m/,/) || ($days eq "*") ) {
	# type 1 recurring transaction
	$dates = &gen_dates_1($days, $months, $years, $begindate, $cutoff);
	# print "$dates\n";
    } else {
	# type 2 recurring transaction
	$dates = &gen_dates_2($days, $months, $years, $begindate, $cutoff);
	# print "$dates\n";
    }

    @DATES = split(/,/, $dates);

    foreach $date (@DATES) {
	print ".";
	$trans = "$date\t\t$desc\t$debit\t$credit\t$cat\t$com\t?\t";
        if ($cat =~ m/^\[/) {
            $key = &create_xfer($trans);
        } else {
            $key = &create_trans($trans);
        }
    }
}

print "\n";


# Calculate cutoff date

sub calc_cutoff {
    my($csec,$cmin,$chour,$cmday,$cmon,$cyear,$cwday,$cyday,$cisdst) = 
        localtime(time);
    my($cutoff_secs, $today_secs);

    # print "calling timelocal with cmon = $cmon\n";
    $today_secs = &main'timelocal(0, 0, 0, $cmday, $cmon, $cyear);
    $cutoff_secs = $today_secs + ($future_days * $secs_per_day);

    ($csec,$cmin,$chour,$cmday,$cmon,$cyear,$cwday,$cyday,$cisdst) = 
        localtime($cutoff_secs);

    return &century() . $cyear . &pad($cmon + 1) . &pad($cmday);  
}


# Generate a list of type 1 dates

sub gen_dates_1 {
    my($days, $months, $years, $begindate, $cutoff) = @_;
    my($csec,$cmin,$chour,$cmday,$cmon,$cyear,$cwday,$cyday,$cisdst) = 
        localtime(time);
    my($ldates) = "";
    my($day, $month, $year, $tdays, $tmonth, $tyear);
    my($this_date, $month_end, $next_month);
    my(@DAYS, @MONTHS, @YEARS);

    # print "$days - $months - $years\n";

    if ( $months eq "*" ) {
	$months = "1,2,3,4,5,6,7,8,9,10,11,12";
    }
    @MONTHS = split(/,/, $months);

    if ( $years eq "*" ) {
	$years = "$cyear," . ($cyear+1);
    }
    @YEARS = split(/,/, $years);

    foreach $year (@YEARS) {
	foreach $month (@MONTHS) {
	    # print &century() . $year . &pad($month) . "\n";
	    if ( $month == 12 ) {
		$tyear = $year + 1; $tmonth = 1;
	    } else {
		$tyear = $year; $tmonth = $month + 1;
	    }
	    # note in perl the months start at 0 ... :(
	    # print "calling timelocal with tmonth = $tmonth \n";
	    $next_month = &main'timelocal(0, 0, 0, 1, ($tmonth - 1), $tyear);
	    # subtract the number of seconds in a day to get the last
	    # day of the previous month
	    $month_end = $next_month - $secs_per_day;
	    ($csec,$cmin,$chour,$cmday,$cmon,$cyear,$cwday,$cyday,$cisdst) = 
		localtime($month_end);

	    if ( $days eq "*" ) {
		$tdays = "1";
		$day = 2;
		while ( $day <= $cmday ) {
		    $tdays .= "," . $day;
		    $day++;
		}
	    } else {
		$tdays = $days;
		$tdays =~ s/last/$cmday/;
	    }

	    # print "$tdays\n";

	    @DAYS = split(/,/, $tdays);
	    foreach $day (@DAYS) {
		$this_date = &century() . $year . &pad($month) . &pad($day);
		if ( ($this_date >= $begindate) && ($this_date <= $cutoff) ) {
		    # print "$this_date\n";
		    if ( $ldates eq "" ) {
			$ldates = $this_date;
		    } else {
			$ldates .= "," . $this_date;
		    }
		}
	    }
	}
    }

    return $ldates;
}


# Generate a list of type 2 dates

sub gen_dates_2 {
    my($start, $incr, $junk, $begindate, $cutoff) = @_;
    my($ldates) = "";
    my($csec,$cmin,$chour,$cmday,$cmon,$cyear,$cwday,$cyday,$cisdst);
    my($scentury, $syear, $smonth, $sday) = 
	$start =~ /(\d\d)(\d\d)(\d\d)(\d\d)/;
    my($secs, $sincr, $start_secs, $this_date);

    # print "$syear - $smonth - $sday\n";

    # print "calling timelocal with smonth = $smonth\n";
    $start_secs = &main'timelocal(0, 0, 0, $sday, ($smonth - 1), $syear);
    $sincr = $incr * $secs_per_day;

    $secs = $start_secs;
    ($csec,$cmin,$chour,$cmday,$cmon,$cyear,$cwday,$cyday,$cisdst) = 
	localtime($secs);
    $this_date = &century() . $cyear . &pad($cmon + 1) . &pad($cmday);
    while ( $this_date <= $cutoff ) {
	if ( $this_date >= $begindate ) {
	    if ( $ldates eq "" ) {
		$ldates = $this_date;
	    } else {
		$ldates .= "," . $this_date;
	    }
	    # print "$this_date\n";
	}
	$secs += $sincr;
	($csec,$cmin,$chour,$cmday,$cmon,$cyear,$cwday,$cyday,$cisdst) = 
	    localtime($secs);
	$this_date = &century() . $cyear . &pad($cmon + 1) . &pad($cmday);
    }

    return $ldates;
}


#----------------------------------------------------------------------------
# $Log: recur.pl,v $
# Revision 2.8  1997/02/28 21:21:58  curt
# Fixed some problems introduced by using "use strict"
#
# Revision 2.7  1997/02/19 18:09:09  curt
# Fixed some residual oversites from switching to "use strict".
#
# Revision 2.6  1997/01/18 17:26:39  curt
# Added "use strict" pragma to enforce good scoping habits.
#
# Revision 2.5  1996/10/03 04:49:08  curt
# Fixed an inconsistency in &raw_date() in common.pl (with how it was
# called.)
#
# Version now is 0.67-beta-x
#
# Revision 2.4  1996/10/03 03:53:42  curt
# CBB now determines the current century automatically ... no need for it
# to be hard coded.  Removed all hardcoded instances of the century (especially
# in reports.pl and recur.pl)
#
# Added an optional --debug flag to the invocation of CBB.
#
# Revision 2.3  1996/09/17 19:41:10  curt
# Add support for recurring transfer transactions.
#
# Revision 2.2  1996/07/13 02:58:24  curt
# Misc. changes.
#
# Revision 2.1  1996/02/27  05:36:04  curt
# Just stumbling around a bit with cvs ... :-(
#
# Revision 2.0  1996/02/27  04:43:14  curt
# Initial 2.0 revision.  (See "Log" files for old history.)
