#!/bin/perl -w
###
# pamex - Process AMEX downloads for Quicken import.
#
# Written by Christopher Rath <Christopher@Rath.ca> 
#
# The AMEX website's ability to download transactions to Quicken is
# very limited: it only allows downloading by billing period.  As the
# current billing period unfolds, the amount of data downloaded in any
# single download grows as new transactions are added.
#
# This script takes two .QIF files downloaded from the AMEX website,
# from two subsequent downloads, pulls the new transactions off the
# most recent download, and then adds a "Cleared" flag to those each
# of those transactions.
#
##############################
# Example--File One          #
##############################
# !Type:CCard
# D04/12/2004
# NAT041050002000010146890
# T-37.45
# PFLIGHT CENTRE - CORPCALGARY           AB
# M
# ^
##############################
# Example--File Two          #
##############################
# !Type:CCard
# D04/12/2004
# NAT041050002000010146890
# T-37.45
# PFLIGHT CENTRE - CORPCALGARY           AB
# M
# ^
# D04/12/2004
# NAT041050003000010018907
# T-46.05
# PF N RISTORANTE INC  GLENDALE          CA
# M33.68 Dollar amricain
# ^
##############################
# Example--Output from pamex #
##############################
# !Type:CCard
# D04/12/2004
# NAT041050003000010018907
# T-46.05
# PF N RISTORANTE INC  GLENDALE          CA
# M33.68 Dollar amricain
# C*
# ^
##############################
#
# This was originlly a bourne shell script.  That script
# appears immiedately below.
#
## 8><  8><  8><  8><  8><  8><
# #!/bin/sh
# set -u
# 
# if [ -f "${1}" -a -f "${2}" ]
# then
#     head -1 "${1}"
# # Use this pair of lines if a date-swap is needed:
# #    comm -13 "${1}" "${2}" | sed -e 's/\^/C*\
# #^/' | sed -e 's&^D\(..\)/\(..\)/\(....\)&D\2/\1/\3&'
# # Use this pair of lines if a date-swap is NOT needed:
#     comm -13 "${1}" "${2}" | sed -e 's/\^/C*\
# ^/'
# else
#     echo 'ERROR: need two input files!' >&2
#     echo 'Usage: pamex [old QIF file] [new QIF file]' >&2
#     exit 1
# fi
## 8><  8><  8><  8><  8><  8><

use Finance::QIF;

# CONST
my($outNm) = "-";

# String Vars
my($preNm) = "";
my($curNm) = "";

# Numeric Vars

# Boolean Vars
my($errFound) = 0;

###
# MAIN
###
if (1 != $#ARGV) {
  print STDERR "ERROR: exactly two filenames must be specified!\n";
} else {
  $preNm = "$ARGV[0]";
  $curNm = "$ARGV[1]";

  foreach $n ($preNm, $curNm) {
    if (! -f "$n") {
      print STDERR "ERROR: $n is not a text file!\n";
      $errFound = 1;
    }
  }

  if (!$errFound) {
    my($foundRec) = 0;
    my($preHeader) = "bad1";
    my($curHeader) = "bad2";
    my($preFl) = Finance::QIF->new( file => "$preNm" );
    my($curFl) = Finance::QIF->new( file => "$curNm" );
    my($outFl) = Finance::QIF->new( file => ">$outNm" );

    ###
    # Finance::QIF requires that we explicitly establish
    # a ->{header} for the output file.  Since the purpose
    # of this program is to perform a delta between two
    # versions of a file, we only allow a single ->{header}
    # type for the entire process.  To provide a small modicum
    # of error detection/protection, we compare the
    # ->{header} of the current and previous files, and if
    # they differ we abort.
    #
    # First establish the ->{header} strings.
    if ($curRec = $curFl->next()) {
      $curHeader = $curRec->{header};
    } else {
      $curHeader = "";
    }
    if ($preRec = $preFl->next()) {
      $preHeader = $preRec->{header};
    } else {
      $preHeader = "";
    }
    # If one of the files is empty we force the ->{header} types
    # to be the same.  If they are both empty we don't need to
    # worry about it.
    if (($preHeader ne "") || ($curHeader ne "")) {
      $preHeader = ($preHeader eq "") ? $curHeader : $preHeader;
      $curHeader = ($curHeader eq "") ? $preHeader : $curHeader;
    }
    # Now compare them and process accordingly.
    if ($curHeader ne $preHeader) {
      print STDERR "ERROR: the $preNm and $curNm files do not"
	. " contain the same type of Quicken data!\n";
    } else {
      # Reset the files.
      $curFl->reset();
      $preFl->reset();

      # Establish the output file ->{header} type.
      $outFl->header($curHeader);

      ###
      # Read a record from the current file, then scan the entire
      # previous file to see if the just-read record exists in
      # that previous file.  If it does exist, then get the next
      # current record; otherwise, send the record to the output
      # and then get the next current record.
      while (my $curRec = $curFl->next()) {
	$foundRec = 0;
	
	###
	# Here's where we look for a matching record in the
	# previous file.  Stop looking once we've found a
	# matching record.  We reset() the file just before
	# reading it, to ensure we always begin with the first
	# record.
	$preFl->reset();
	while (!$foundRec && (my $preRec = $preFl->next())) {
	  if (($curRec->{date} eq $preRec->{date})
	      && ($curRec->{amount} eq $preRec->{amount})
	      && ($curRec->{payee} eq $preRec->{payee})
	      && ($curRec->{number} eq $preRec->{number})) {
	    $foundRec = 1;
	  }
	}
	
	###
	# If no matching record was found then output the
	# current file record after setting the status.
	if (!$foundRec) {
	  $curRec->{status} = '*';
	  $outFl->write($curRec);
        }
      }
    }

    $preFl->close();
    $curFl->close();
    $outFl->close();
  }
}
