package ivl.android.moneybalance.filter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeSet; import ivl.android.moneybalance.CurrencyHelper; import ivl.android.moneybalance.data.Calculation; import ivl.android.moneybalance.data.Currency; import ivl.android.moneybalance.data.Expense; import ivl.android.moneybalance.data.Person; public class CsvOutput { private final Calculation calculation; private final List<Person> persons; private final boolean multiCurrency; private final CurrencyHelper helper; private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); private final Set<Calendar> dates = new TreeSet<>(); private final Map<Calendar, List<Expense>> expensesByDate = new HashMap<>(); private StringBuffer buffer; private int row; public CsvOutput(Calculation calculation) { this.calculation = calculation; persons = calculation.getPersons(); multiCurrency = (calculation.getCurrencies().size() > 1); helper = calculation.getMainCurrency().getCurrencyHelper(Locale.ENGLISH); helper.setGroupingUsed(false); for (Expense expense : calculation.getExpenses()) { Calendar date = expense.getDate(); List<Expense> byDateList = expensesByDate.get(date); if (byDateList == null) { byDateList = new ArrayList<>(); expensesByDate.put(date, byDateList); dates.add(date); } byDateList.add(expense); } } public String toCsv() { buffer = new StringBuffer(); row = 0; appendTitleRow(); appendHeadings(); for (Calendar date : dates) { for (Expense expense : expensesByDate.get(date)) { appendExpense(date, expense); } } appendTotalExpenses(); appendTotalConsumptions(); appendResults(); return buffer.toString(); } private void appendTitleRow() { row++; buffer.append(quote(calculation.getTitle())); buffer.append("\n"); } private void appendHeadings() { row++; buffer.append(",,,,"); if (multiCurrency) buffer.append(",,,"); for (Person person : persons) { buffer.append(','); buffer.append(quote(person.getName())); } for (Person person : persons) { buffer.append(','); buffer.append(quote(person.getName())); } buffer.append('\n'); } private void appendExpense(Calendar date, Expense expense) { row++; buffer.append(dateFormat.format(date.getTime())); buffer.append(','); buffer.append(quote(expense.getPerson().getName())); buffer.append(','); buffer.append(quote(expense.getTitle())); buffer.append(','); Currency currency = expense.getCurrency(); if (multiCurrency) { Currency mainCurrency = calculation.getMainCurrency(); if (currency.equals(mainCurrency)) { buffer.append(",,,"); buffer.append(quote(currency.getCurrencyCode())); buffer.append(','); buffer.append(helper.format(expense.getAmount(), false)); } else { buffer.append(quote(currency.getCurrencyCode())); buffer.append(','); buffer.append(helper.format(expense.getAmount(), false)); buffer.append(','); buffer.append(currency.getExchangeRateMain() / currency.getExchangeRateThis()); buffer.append(','); buffer.append(quote(mainCurrency.getCurrencyCode())); buffer.append(','); buffer.append(String.format("\"=%s*%s\"", cell(row, localAmountColumn()), cell(row, exchangeRateColumn()))); } } else { buffer.append(quote(currency.getCurrencyCode())); buffer.append(','); buffer.append(helper.format(expense.getAmount(), false)); } Map<Long, Double> weights = expense.getSplitWeights(); for (int i = 0; i < persons.size(); i++) { String weight = ""; if (expense.isUnevenSplit()) { Double w = weights.get(persons.get(i).getId()); if (w != null) weight = helper.format(w, false); } else { weight = helper.format(1, false); } buffer.append(','); buffer.append(weight); } for (int i = 0; i < persons.size(); i++) { String amountCell = cell(row, exchangedAmountColumn()); String firstWeightCell = cell(row, firstWeightColumn()); String weightCell = cell(row, firstWeightColumn() + i); String lastWeightCell = cell(row, firstWeightColumn() + persons.size() - 1); String formula = String.format("=%s*%s/SUM(%s:%s)", amountCell, weightCell, firstWeightCell, lastWeightCell); buffer.append(','); buffer.append(formula); } buffer.append('\n'); } private void appendTotalExpenses() { row++; buffer.append(",,,,"); if (multiCurrency) buffer.append(",,,"); for (int i = 0; i < persons.size(); i++) buffer.append(','); for (int i = 0; i < persons.size(); i++) { int column = firstShareColumn() + i; String firstNameCell = cell(3, nameColumn()); String lastNameCell = cell(3 + calculation.getExpenses().size() - 1, nameColumn()); String nameCell = cell(2, column); String firstAmountCell = cell(3, exchangedAmountColumn()); String lastAmountCell = cell(3 + calculation.getExpenses().size() - 1 , exchangedAmountColumn()); String formula = String.format("\"=SUMIF(%s:%s, %s, %s:%s)\"", firstNameCell, lastNameCell, nameCell, firstAmountCell, lastAmountCell); buffer.append(','); buffer.append(formula); } buffer.append('\n'); } private void appendTotalConsumptions() { row++; buffer.append(",,,,"); if (multiCurrency) buffer.append(",,,"); for (int i = 0; i < persons.size(); i++) buffer.append(','); for (int i = 0; i < persons.size(); i++) { int column = firstShareColumn() + i; String firstCell = cell(3, column); String lastCell = cell(3 + calculation.getExpenses().size() - 1, column); String formula = String.format("=SUM(%s:%s)", firstCell, lastCell); buffer.append(','); buffer.append(formula); } buffer.append('\n'); } private void appendResults() { row++; buffer.append(",,,,"); if (multiCurrency) buffer.append(",,,"); for (int i = 0; i < persons.size(); i++) buffer.append(','); for (int i = 0; i < persons.size(); i++) { int column = firstShareColumn() + i; String expenseCell = cell(row - 2, column); String consumptionCell = cell(row - 1, column); String formula = String.format("\"=%s-%s\"", expenseCell, consumptionCell); buffer.append(','); buffer.append(formula); } buffer.append('\n'); } private String quote(String text) { return '"' + text.replace("\"", "\"\"") + '"'; } private String cell(int row, int column) { String columns = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; String result = ""; if (column > 26) result += columns.charAt((column - 1) / 26 - 1); result += columns.charAt((column - 1) % 26); result += Integer.toString(row); return result; } // Column numbers (n = Number of persons): // Single | Multi | Description // 1 | 1 | Date // 2 | 2 | Payer // 3 | 3 | Title // - | 4 | Local Currency // - | 5 | Amount (local currency) // - | 6 | Exchange Rate // 4 | 7 | Main Currency // 5 | 8 | Amount (exchanged) // 6 | 9 | First split weight // 5+n | 8+n | Last split weight // 6+n | 9+n | First share // 5+2n | 8+2n | Last share private int nameColumn() { return 2; } private int localAmountColumn() { return 5; } private int exchangeRateColumn() { return 6; } private int exchangedAmountColumn() { return multiCurrency ? 8 : 5; } private int firstWeightColumn() { return multiCurrency ? 9 : 6; } private int firstShareColumn() { return firstWeightColumn() + persons.size(); } }