#
#  QIFParser
#
#  A quick and dirty hack for parsing QIF files.  Used by QIFImporter,
#  but intended to be testable in a standalone fashion.
#
#  Written by Niko Matsakis (c) 2006
#

class FileLineByLine(object):
    def __init__(self, fileobj):
        data = fileobj.read()
        if '\r' in data:
            lines = data.split('\r')
        else:
            lines = data.split('\n')
        self.iterator = iter(lines)
        self.line_number = 0
        self.current = None

    def next(self):
        self.line_number += 1
        self.current = self.iterator.next()
        return self.current

class DumpDelegate(object):

    def __init__(self, out):
        self.out = out

    def handle_account(self, account):
        self.out.write('Account:\n')
        for key, value in account.items():
            self.out.write('  %s: %s\n' % (key, repr(value)))

    def handle_transaction(self, transaction):
        self.out.write('  Transaction:\n')
        for key, value in transaction.items():
            self.out.write('    %s: %s\n' % (key, repr(value)))

class QIFParser(object):

    def __init__(self, delegate, encoding):
        self.delegate = delegate
        self.encoding = encoding
        self.warnings = []

    # ------------------------------------------------------------
    # QIF Parsing Code

    def parseQIF(self, fileobj):
        qiflines = FileLineByLine(fileobj)
        try:
            line = qiflines.next()
            while True:
                if line.startswith('!Account'):
                    line = qiflines.next()
                    while True:
                        current_account = self.parse_account(qiflines)
                        self.delegate.handle_account(current_account)
                        line = qiflines.next()
                        if line.startswith('!'):
                            break
                elif line.startswith('!Type:Bank'):
                    line = qiflines.next()
                    while True:
                        transaction_info = self.parse_transaction(qiflines)
                        self.delegate.handle_transaction(transaction_info)
                        line = qiflines.next()
                        if line.startswith('!'):
                            break
                else:
                    # Ignore unrecognized lines.
                    self.warnings.append(
                        "Line %d: ignored" % qiflines.line_number)
                    line = qiflines.next()
                    pass
        except StopIteration:
            # Reached EOF
            pass
        return

    account_table = {
        'N':('name','default'),
        'D':('description','default'),
        'L':('credit_limit','default'),
        '/':('statement_balance_date','default'),
        '$':('statement_balance_amount','default'),
        'B':('balance','default'),
        'X':('unknown', 'default'),
        'T':('type', 'default'),
        '^':(None,None)
        }

    transaction_table = {
        'D':('date','default'),
        'P':('payee','default'),
        'M':('memo','default'),
        'A':('address','default'),
        'L':('category','default'),
        'T':('total', 'default'),
        'S':('split','split'),
        '^':(None,None)
        }

    def parse_account(self, qiflines):
        return self.parse_item(
            qiflines,
            "account",
            self.account_table)

    def parse_transaction(self, qiflines):
        return self.parse_item(
            qiflines,
            "transaction",
            self.transaction_table)

    def parse_item(self, qiflines, desc, letter_dict):
        """
        driver routine for parse_account nad parse_transaction: uses a
        dictionary to determine how to dispatch each line based on its
        first character.
        """
        data = {}
        while True:
            try:
                line = qiflines.current
                if not line:
                    qiflines.next()
                    continue # ignore blank lines
                data_name, data_handler = letter_dict[line[0]]
                if not data_handler:
                    return data
                line = getattr(self, data_handler)(
                    qiflines, data_name, data)
            except KeyError:
                # Warning:
                self.warnings.append(
                    "Line %d: while parsing a %s, unexpected letter '%c'"
                    % (qiflines.line_number, desc, line[0]))
                qiflines.next()
                pass

    def default(self, qiflines, data_name, data):
        """
        default handler: most lines in QIF only have a key, value
        setup
        """
        if self.encoding:
            data[data_name] = qiflines.current[1:].decode(self.encoding)
        else:
            data[data_name] = qiflines.current[1:]
        qiflines.next()

    def split(self, qiflines, data_name, data):
        """
        handler for splits: splits are kind of a recursive mini
        structure
        """
        split_data = {'category':qiflines.current[1:]}
        qiflines.next()
        while True:
            line = qiflines.current
            if line[0] == 'E':
                self.default(qiflines, 'memo', split_data)
            elif line[0] == '$':
                self.default(qiflines, 'amount', split_data)
            else:
                break
        try:
            data[data_name].append(split_data)
        except KeyError:
            data[data_name] = [split_data]
    
if __name__ == "__main__":
    import sys
    delegate = DumpDelegate(sys.stdout)
    q = QIFParser(delegate, sys.argv[1])
    for arg in sys.argv[2:]:
        q.parseQIF(open(arg))
    for warn in q.warnings:
        print warn
