public static AmazonStatement ReadAmazonCSV(string filepath)
        {
            AmazonStatement ret = new AmazonStatement(filepath);

            string[] lines = File.ReadAllText(filepath).Split('\n');

            AmazonTransaction prevEntry = null;

            foreach (string line in lines)
            {
                List <string> parts = FileHelpers.ParseCSVLine(line);

                if (parts.Count < 30)
                {
                    continue;
                }

                DateTime?date = TryToScanDate(parts[0], null, null);

                if (!date.HasValue)
                {
                    continue;
                }

                string amountStr = parts[29].Substring(1);
                double.TryParse(amountStr, out double amount);

                //Build an entry for this line by itself
                AmazonTransaction subEntry = new AmazonTransaction
                {
                    Date        = date.Value,
                    Amount      = -1 * amount,
                    Description = parts[2],
                    Category    = parts[3],
                    OrderNumber = parts[1]
                };

                //Special code here so we can combine multiple transactions with the same order number into one final transaction with sub-transactions

                AmazonTransaction wrapper = null;
                if (prevEntry != null)
                {
                    //If we had an entry in progress and this entry shares the same order number, add this entry as a sub-transaction
                    if (prevEntry.Items[0].OrderNumber.Equals(subEntry.OrderNumber))
                    {
                        wrapper              = prevEntry;
                        wrapper.Amount      += subEntry.Amount;
                        wrapper.Category    += ";" + subEntry.Category;
                        wrapper.Description += ";" + subEntry.Description;
                    }
                    else
                    {
                        //Commit the previous entry, the new entry starts a new order
                        ret.Transactions.Add(prevEntry);
                        prevEntry = null;
                    }
                }

                if (prevEntry == null)
                {
                    //Create a new wrapper to hold the sub-transactions
                    wrapper = new AmazonTransaction
                    {
                        Date        = date.Value,
                        Amount      = -1 * amount,
                        Description = parts[2],
                        Category    = parts[3],
                        OrderNumber = parts[1],
                    };
                }

                wrapper.Items.Add(subEntry);
                prevEntry = wrapper;
            }

            if (prevEntry != null)
            {
                ret.Transactions.Add(prevEntry);
            }

            return(ret);
        }
        public static List <Transaction> ReadChaseCreditPDF(Statement statement)
        {
            List <Transaction> ret = new List <Transaction>();

            string pdfText = FileHelpers.GetPdfText(statement.Filepath);

            string[] lines = pdfText.Split('\n');

            ReadState state         = ReadState.Header;
            DateTime? statementDate = null;

            foreach (string line in lines)
            {
                string   trimmed   = line.Trim();
                DateTime?entryDate = TryToScanDate(trimmed, "Opening/Closing Date", statementDate);

                Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState>
                {
                    { "TransactionMerchantNameorTransactionDescription$Amount", ReadState.Credit },
                    { "TransactionMerchantNameorTransactionDescription$AmountRewards", ReadState.Header }
                };

                //Special logic: spacing for the target line was funny sometimes (double or missing)
                //So we'll strip all spacing from the string
                string stripped = trimmed.Replace(" ", "");

                CheckForStateTransition(stripped, stateTransitions, false, ref state);

                switch (state)
                {
                case ReadState.Header:
                    if (entryDate.HasValue && !statementDate.HasValue)
                    {
                        statementDate = entryDate;
                    }
                    string searchHeader = "Previous Balance $";
                    if (trimmed.StartsWith(searchHeader))
                    {
                        double.TryParse(trimmed.Substring(searchHeader.Length), out double start);
                        statement.StartingBalance = -1 * start;
                    }
                    else
                    {
                        searchHeader = "Previous Balance -$";
                        if (trimmed.StartsWith(searchHeader))
                        {
                            double.TryParse(trimmed.Substring(searchHeader.Length), out double start);
                            statement.StartingBalance = start;
                        }
                        else
                        {
                            searchHeader = "New Balance $";
                            if (trimmed.StartsWith(searchHeader))
                            {
                                double.TryParse(trimmed.Substring(searchHeader.Length), out double end);
                                statement.EndingBalance = -1 * end;
                            }
                            else
                            {
                                searchHeader = "New Balance -$";
                                if (trimmed.StartsWith(searchHeader))
                                {
                                    double.TryParse(trimmed.Substring(searchHeader.Length), out double end);
                                    statement.EndingBalance = end;
                                }
                            }
                        }
                    }
                    break;

                case ReadState.Credit:
                case ReadState.Check:
                case ReadState.Debit:
                    if (entryDate.HasValue)
                    {
                        //Starting a new entry
                        List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                        if (entryParts.Count >= 3)
                        {
                            Transaction curEntry = new Transaction
                            {
                                Date     = entryDate.Value,
                                Accounts = new List <string> {
                                    statement.AccountName
                                },
                                Type = state.ToString()
                            };

                            entryParts.RemoveAt(0);

                            string amountString = entryParts.Last();
                            entryParts.RemoveAt(entryParts.Count - 1);

                            double.TryParse(amountString, out double amount);
                            curEntry.CreditAmount = -1 * amount;
                            if (curEntry.CreditAmount < 0)
                            {
                                curEntry.Type = "Debit";
                            }

                            curEntry.Description = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts));

                            ret.Add(curEntry);
                        }
                    }
                    break;
                }

                //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-");
            }

            ret.OrderBy(e => e.Date);

            return(ret);
        }
        public static List <Transaction> ReadBjsCreditPDF(Statement statement)
        {
            List <Transaction> ret = new List <Transaction>();

            string pdfText = FileHelpers.GetPdfText(statement.Filepath);

            string[] lines = pdfText.Split('\n');

            ReadState   state         = ReadState.Header;
            DateTime?   statementDate = null;
            Transaction curEntry      = null;

            foreach (string line in lines)
            {
                string   trimmed   = line.Trim();
                DateTime?entryDate = TryToScanDate(trimmed, "Statement closing date", statementDate);

                Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState>
                {
                    { "TRANS DATE TRANSACTION DESCRIPTION/LOCATION AMOUNT", ReadState.Credit }
                };

                CheckForStateTransition(trimmed, stateTransitions, false, ref state);

                switch (state)
                {
                case ReadState.Header:
                    if (entryDate.HasValue && !statementDate.HasValue)
                    {
                        statementDate = entryDate;
                    }
                    string searchHeader = "Previ ous balance $";
                    if (trimmed.StartsWith(searchHeader))
                    {
                        double.TryParse(trimmed.Substring(searchHeader.Length), out double start);
                        statement.StartingBalance = -1 * start;
                    }
                    else
                    {
                        searchHeader = "New balance $";
                        if (trimmed.StartsWith(searchHeader))
                        {
                            double.TryParse(trimmed.Substring(searchHeader.Length), out double end);
                            statement.EndingBalance = -1 * end;
                        }
                    }
                    break;

                case ReadState.Credit:
                case ReadState.Check:
                case ReadState.Debit:
                    if (entryDate.HasValue)
                    {
                        //Starting a new entry
                        if (curEntry != null)
                        {
                            ret.Add(curEntry);
                            curEntry = null;
                        }

                        List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                        if (entryParts.Count >= 3)
                        {
                            curEntry = new Transaction
                            {
                                Date     = entryDate.Value,
                                Accounts = new List <string> {
                                    statement.AccountName
                                },
                                Type = state.ToString()
                            };

                            entryParts.RemoveAt(0);

                            string amountString = entryParts.Last();
                            double.TryParse(amountString, out double amount);

                            if (amount != 0)
                            {
                                entryParts.RemoveAt(entryParts.Count - 1);
                            }

                            curEntry.CreditAmount = -1 * amount;
                            if (curEntry.CreditAmount < 0)
                            {
                                curEntry.Type = "Debit";
                            }

                            curEntry.Description = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts));

                            //ret.Add(curEntry);
                        }
                    }
                    else if (trimmed.StartsWith("Interest charge on purchases"))
                    {
                        List <string> entryParts   = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                        string        amountString = entryParts.Last().Substring(1);
                        double.TryParse(amountString, out double amount);
                        if (amount != 0)
                        {
                            amount *= -1;
                            entryParts.RemoveAt(entryParts.Count - 1);

                            curEntry = new Transaction
                            {
                                Date     = statementDate.Value,
                                Accounts = new List <string> {
                                    statement.AccountName
                                },
                                Type         = "Debit",
                                CreditAmount = amount,
                                Description  = string.Join(" ", entryParts)
                            };

                            ret.Add(curEntry);
                            curEntry = null;
                        }
                    }
                    else if (!trimmed.StartsWith("(CONTINUED)") && !trimmed.StartsWith("Total for"))
                    {
                        if (curEntry != null)
                        {
                            if (double.TryParse(trimmed, out double amount))
                            {
                                curEntry.CreditAmount = -1 * amount;
                                if (curEntry.CreditAmount < 0)
                                {
                                    curEntry.Type = "Debit";
                                }

                                if (curEntry != null)
                                {
                                    ret.Add(curEntry);
                                    curEntry = null;
                                }
                            }
                            else
                            {
                                curEntry.Description = string.Format("{0} {1}", curEntry.Description, trimmed);
                            }
                        }
                    }
                    else
                    {
                        if (curEntry != null)
                        {
                            ret.Add(curEntry);
                            curEntry = null;
                        }
                    }
                    break;
                }

                //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-");
            }

            ret.OrderBy(e => e.Date);

            return(ret);
        }
        public static List <Transaction> ReadUsaaSavingsPDF(Statement statement)
        {
            List <Transaction> ret = new List <Transaction>();

            string pdfText = FileHelpers.GetPdfText(statement.Filepath);

            string[] lines = pdfText.Split('\n');

            ReadState   state         = ReadState.Header;
            int         linesInEntry  = 0;
            Transaction curEntry      = null;
            DateTime?   statementDate = null;

            foreach (string line in lines)
            {
                string   trimmed   = line.Trim();
                DateTime?entryDate = TryToScanDate(trimmed, null, statementDate);

                Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState>
                {
                    { "DEPOSITS AND OTHER CREDITS", ReadState.Credit },
                    { "CHECKS", ReadState.Check },
                    { "OTHER DEBITS", ReadState.Debit },
                    { "ACCOUNT BALANCE SUMMARY", ReadState.Interim }
                };

                if (CheckForStateTransition(trimmed, stateTransitions, false, ref state))
                {
                    if (curEntry != null)
                    {
                        ret.Add(curEntry);
                        curEntry = null;
                    }
                }

                string searchHeader = "USAA SAVINGS";
                if (state == ReadState.Header && trimmed.StartsWith(searchHeader))
                {
                    state = ReadState.Balance;
                }

                switch (state)
                {
                case ReadState.Header:
                    if (entryDate.HasValue && !statementDate.HasValue)
                    {
                        statementDate = entryDate;
                    }
                    break;

                case ReadState.Balance:
                    List <string> balanceParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                    if (balanceParts.Count == 7)
                    {
                        if (double.TryParse(balanceParts[0], out double start))
                        {
                            statement.StartingBalance = start;
                        }

                        if (double.TryParse(balanceParts.Last(), out double end))
                        {
                            statement.EndingBalance = end;
                        }
                    }
                    break;

                case ReadState.Credit:
                case ReadState.Check:
                case ReadState.Debit:
                    if (entryDate.HasValue)
                    {
                        //Starting a new entry
                        if (curEntry != null)
                        {
                            ret.Add(curEntry);
                        }

                        curEntry = new Transaction
                        {
                            Date     = entryDate.Value,
                            Accounts = new List <string> {
                                statement.AccountName
                            },
                            Type = state.ToString()
                        };

                        List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                        entryParts.RemoveAt(0);

                        string amountString = "";
                        if (entryParts.Count > 0)
                        {
                            amountString = entryParts[0];
                            entryParts.RemoveAt(0);
                        }

                        string typeString = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts));

                        if (state == ReadState.Check)
                        {
                            //Swap amount and description for checks
                            string temp = amountString;
                            amountString = typeString;
                            typeString   = temp;
                        }

                        double.TryParse(amountString, out double amount);
                        int multiplier = state == ReadState.Credit ? 1 : -1;
                        curEntry.SavingsAmount = amount * multiplier;

                        curEntry.Description = typeString;

                        linesInEntry = 1;
                    }
                    else
                    {
                        //Either continuing an entry or left the section
                        linesInEntry++;
                        if (linesInEntry > 3)
                        {
                            if (curEntry != null)
                            {
                                //Commit the previous entry
                                ret.Add(curEntry);
                                curEntry = null;

                                state        = ReadState.Interim;
                                linesInEntry = 0;
                            }
                        }
                        else if (curEntry != null)
                        {
                            curEntry.Description = FileHelpers.RemoveTrailingNumbers(trimmed);
                        }
                    }
                    break;
                }

                //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-");
            }

            ret.OrderBy(e => e.Date);

            return(ret);
        }
        public static List <Transaction> ReadUsaaCreditPDF(Statement statement)
        {
            List <Transaction> ret = new List <Transaction>();

            string pdfText = FileHelpers.GetPdfText(statement.Filepath);

            string[] lines = pdfText.Split('\n');

            ReadState   state         = ReadState.Header;
            DateTime?   statementDate = null;
            Transaction curEntry      = null;

            foreach (string line in lines)
            {
                string   trimmed   = line.Trim();
                DateTime?entryDate = TryToScanDate(trimmed, "Statement Closing Date", statementDate);

                Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState>
                {
                    { "Payments and Credits", ReadState.Credit },
                    { "Transactions", ReadState.Debit },
                    { "Interest Charged", ReadState.Debit },
                    { "Summary of Account Activity", ReadState.Balance },
                    { "Fees", ReadState.Debit }
                };

                CheckForStateTransition(trimmed, stateTransitions, false, ref state);

                switch (state)
                {
                case ReadState.Header:
                    if (entryDate.HasValue && !statementDate.HasValue)
                    {
                        statementDate = entryDate;
                    }
                    break;

                case ReadState.Balance:
                    string searchHeader = "Previous Balance $";
                    if (trimmed.StartsWith(searchHeader))
                    {
                        double.TryParse(trimmed.Substring(searchHeader.Length), out double start);
                        statement.StartingBalance = -1 * start;
                    }
                    else
                    {
                        searchHeader = "New Balance $";
                        if (trimmed.StartsWith(searchHeader))
                        {
                            double.TryParse(trimmed.Substring(searchHeader.Length), out double end);
                            statement.EndingBalance = -1 * end;
                        }
                    }

                    break;

                case ReadState.Credit:
                case ReadState.Check:
                case ReadState.Debit:
                    if (curEntry == null)
                    {
                        if (entryDate.HasValue)
                        {
                            //Starting a new entry
                            List <string> entryParts = trimmed.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                            if (entryParts.Count >= 5)
                            {
                                curEntry = new Transaction
                                {
                                    Date     = entryDate.Value,
                                    Accounts = new List <string> {
                                        statement.AccountName
                                    },
                                    Type = state.ToString()
                                };

                                entryParts.RemoveAt(0);
                                entryParts.RemoveAt(0);

                                if (!entryParts[0].Equals("Interest"))
                                {
                                    entryParts.RemoveAt(0);
                                }

                                string amountString = entryParts.Last().Substring(1);
                                if (amountString.EndsWith("-"))
                                {
                                    amountString = amountString.Substring(0, amountString.Length - 1);
                                }

                                bool gotAmount = double.TryParse(amountString, out double amount);

                                if (gotAmount)
                                {
                                    entryParts.RemoveAt(entryParts.Count - 1);
                                }

                                curEntry.Description = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts));

                                if (gotAmount)
                                {
                                    int multiplier = state == ReadState.Credit ? 1 : -1;
                                    curEntry.CreditAmount = amount * multiplier;

                                    ret.Add(curEntry);
                                    curEntry = null;
                                }
                            }
                        }
                    }
                    else
                    {
                        //Look for a line containing the amount
                        if (trimmed.StartsWith("$") && double.TryParse(trimmed.Substring(1), out double amount))
                        {
                            int multiplier = state == ReadState.Credit ? 1 : -1;
                            curEntry.CreditAmount = amount * multiplier;

                            ret.Add(curEntry);
                            curEntry = null;
                        }
                        else
                        {
                            curEntry.Description += " " + FileHelpers.RemoveTrailingNumbers(trimmed);
                        }
                    }
                    break;
                }

                //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-");
            }

            ret.OrderBy(e => e.Date);

            return(ret);
        }
        public static List <Transaction> ReadUsaaCheckingPDF(Statement statement)
        {
            List <Transaction> ret = new List <Transaction>();

            string pdfText = FileHelpers.GetPdfText(statement.Filepath);

            string[] lines = pdfText.Split('\n');

            ReadState   state         = ReadState.Header;
            int         linesInEntry  = 0;
            Transaction curEntry      = null;
            DateTime?   statementDate = null;

            foreach (string line in lines)
            {
                string   trimmed   = line.Trim();
                DateTime?entryDate = TryToScanDate(trimmed, null, statementDate);

                Dictionary <string, ReadState> stateTransitions = new Dictionary <string, ReadState>
                {
                    { "DEPOSITS AND OTHER CREDITS", ReadState.Credit },
                    { "CHECKS", ReadState.Check },
                    { "OTHER DEBITS", ReadState.Debit },
                    { "USAA CLASSIC CHECKING", ReadState.Balance },
                    { "ACCOUNT BALANCE SUMMARY", ReadState.Interim }
                };

                if (CheckForStateTransition(trimmed, stateTransitions, true, ref state))
                {
                    if (curEntry != null)
                    {
                        ret.Add(curEntry);
                        curEntry = null;
                    }
                }

                switch (state)
                {
                case ReadState.Header:
                    if (entryDate.HasValue && !statementDate.HasValue)
                    {
                        statementDate = entryDate;
                    }
                    break;

                case ReadState.Balance:
                    List <string> balanceParts = trimmed.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                    if (balanceParts.Count == 7)
                    {
                        if (double.TryParse(balanceParts[0], out double start) &&
                            double.TryParse(balanceParts.Last(), out double end))
                        {
                            statement.StartingBalance = start;
                            statement.EndingBalance   = end;
                        }
                    }

                    break;

                case ReadState.Credit:
                case ReadState.Check:
                case ReadState.Debit:
                    string[] entries         = { trimmed };
                    bool     singleLineEntry = false;
                    if (state == ReadState.Check)
                    {
                        //Look for multiline check entries
                        string lineFooter = "USAA FEDERAL SAVINGS BANK";
                        if (trimmed.EndsWith(lineFooter))
                        {
                            trimmed         = trimmed.Substring(0, trimmed.Length - lineFooter.Length);
                            entries[0]      = trimmed;
                            singleLineEntry = true;
                        }

                        List <string> entryParts = trimmed.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                        if (entryParts.Count >= 6)
                        {
                            entries = new[]
                            {
                                string.Join(" ", entryParts.GetRange(0, 3)),
                                string.Join(" ", entryParts.GetRange(3, 3))
                            };
                        }
                    }

                    foreach (string entry in entries)
                    {
                        entryDate = TryToScanDate(entry, null, statementDate);
                        if (entryDate.HasValue)
                        {
                            //Starting a new entry
                            if (curEntry != null)
                            {
                                ret.Add(curEntry);
                            }

                            curEntry = new Transaction
                            {
                                Date     = entryDate.Value,
                                Accounts = new List <string> {
                                    statement.AccountName
                                },
                                Type = state.ToString()
                            };

                            List <string> entryParts = entry.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                            entryParts.RemoveAt(0);

                            string amountString = "";
                            if (entryParts.Count > 0)
                            {
                                //amountString = entryParts[0];
                                //entryParts.RemoveAt(0);

                                if (state == ReadState.Check)
                                {
                                    //Amount comes last for checks
                                    amountString = entryParts.Last();
                                    entryParts.RemoveAt(entryParts.Count - 1);

                                    ////Swap amount and description for checks
                                    //string temp = amountString;
                                    //amountString = typeString;
                                    //typeString = temp;
                                }
                                else
                                {
                                    amountString = entryParts[0];
                                    entryParts.RemoveAt(0);
                                }
                            }

                            string typeString = FileHelpers.RemoveTrailingNumbers(string.Join(" ", entryParts));

                            double.TryParse(amountString, out double amount);
                            int multiplier = state == ReadState.Credit ? 1 : -1;
                            curEntry.CheckingAmount = amount * multiplier;

                            curEntry.Description = (state == ReadState.Check ? "Check " : string.Empty) + typeString;

                            linesInEntry = 1;

                            if (singleLineEntry)
                            {
                                ret.Add(curEntry);
                                curEntry = null;
                            }
                        }
                        else if (!entry.Equals("FEDERAL"))
                        {
                            //Either continuing an entry or left the section
                            linesInEntry++;
                            //int allowableLines = state == ReadState.Credit ? 3 : 2;
                            if (linesInEntry > 3)
                            {
                                if (curEntry != null)
                                {
                                    //Commit the previous entry
                                    ret.Add(curEntry);
                                    curEntry = null;

                                    state        = ReadState.Interim;
                                    linesInEntry = 0;
                                }
                            }
                            else if (curEntry != null)
                            {
                                curEntry.Description += " " + FileHelpers.RemoveTrailingNumbers(trimmed);
                            }
                        }
                        else
                        {
                            state = ReadState.Interim;
                        }
                    }

                    if (singleLineEntry)
                    {
                        state = ReadState.Interim;
                    }

                    break;
                }

                //Console.WriteLine("{0}: {1} ({2})", state.ToString(), line, entryDate.HasValue ? entryDate.ToString() : "-");
            }

            ret.OrderBy(e => e.Date);

            return(ret);
        }