package name.abuchen.portfolio.datatransfer.csv;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVStrategy;

import name.abuchen.portfolio.Messages;
import name.abuchen.portfolio.model.Account;
import name.abuchen.portfolio.model.AccountTransaction;
import name.abuchen.portfolio.model.Portfolio;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.Security;
import name.abuchen.portfolio.model.SecurityPrice;
import name.abuchen.portfolio.model.Transaction;
import name.abuchen.portfolio.model.Transaction.Unit;
import name.abuchen.portfolio.money.Money;
import name.abuchen.portfolio.money.Values;

/* not thread safe */
public class CSVExporter
{
    /* package */static final CSVStrategy STRATEGY = new CSVStrategy(';', '"', CSVStrategy.COMMENTS_DISABLED,
                    CSVStrategy.ESCAPE_DISABLED, false, false, false, false);

    public void exportAccountTransactions(File file, Account account) throws IOException
    {
        try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))
        {
            CSVPrinter printer = new CSVPrinter(writer);
            printer.setStrategy(STRATEGY);

            printer.println(new String[] { Messages.CSVColumn_Date, //
                            Messages.CSVColumn_Type, //
                            Messages.CSVColumn_Value, //
                            Messages.CSVColumn_TransactionCurrency, //
                            Messages.CSVColumn_Shares, //
                            Messages.CSVColumn_ISIN, //
                            Messages.CSVColumn_WKN, //
                            Messages.CSVColumn_TickerSymbol, //
                            Messages.CSVColumn_SecurityName, //
                            Messages.CSVColumn_Note });

            for (AccountTransaction t : account.getTransactions())
            {
                printer.print(t.getDate().toString());
                printer.print(t.getType().toString());
                printer.print(Values.Amount.format(t.getAmount()));
                printer.print(t.getCurrencyCode());
                printer.print(t.getShares() != 0 ? Values.Share.format(t.getShares()) : ""); //$NON-NLS-1$

                printSecurityInfo(printer, t);

                printer.print(escapeNull(t.getNote()));
                printer.println();
            }
        }
    }

    public void exportAccountTransactions(File directory, List<Account> accounts) throws IOException
    {
        for (Account account : accounts)
            exportAccountTransactions(new File(directory, account.getName() + ".csv"), account); //$NON-NLS-1$
    }

    public void exportPortfolioTransactions(File file, Portfolio portfolio) throws IOException
    {
        try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))
        {
            CSVPrinter printer = new CSVPrinter(writer);
            printer.setStrategy(STRATEGY);

            printer.println(new String[] { Messages.CSVColumn_Date, //
                            Messages.CSVColumn_Type, //
                            Messages.CSVColumn_Value, //
                            Messages.CSVColumn_TransactionCurrency, //
                            Messages.CSVColumn_GrossAmount, //
                            Messages.CSVColumn_CurrencyGrossAmount, //
                            Messages.CSVColumn_ExchangeRate, //
                            Messages.CSVColumn_Fees, //
                            Messages.CSVColumn_Taxes, //
                            Messages.CSVColumn_Shares, //
                            Messages.CSVColumn_ISIN, //
                            Messages.CSVColumn_WKN, //
                            Messages.CSVColumn_TickerSymbol, //
                            Messages.CSVColumn_SecurityName, //
                            Messages.CSVColumn_Note });

            for (PortfolioTransaction t : portfolio.getTransactions())
            {
                printer.print(t.getDate().toString());
                printer.print(t.getType().toString());
                printer.print(Values.Amount.format(t.getAmount()));
                printer.print(t.getCurrencyCode());

                // gross amount
                Optional<Unit> grossAmount = t.getUnit(Unit.Type.GROSS_VALUE);
                if (grossAmount.isPresent())
                {
                    Money forex = grossAmount.get().getForex();
                    printer.print(Values.Amount.format(forex.getAmount()));
                    printer.print(forex.getCurrencyCode());
                    printer.print(Values.ExchangeRate.format(grossAmount.get().getExchangeRate()));
                }
                else
                {
                    printer.print(""); //$NON-NLS-1$
                    printer.print(""); //$NON-NLS-1$
                    printer.print(""); //$NON-NLS-1$
                }

                printer.print(Values.Amount.format(t.getUnitSum(Unit.Type.FEE).getAmount()));
                printer.print(Values.Amount.format(t.getUnitSum(Unit.Type.TAX).getAmount()));
                printer.print(Values.Share.format(t.getShares()));

                printSecurityInfo(printer, t);

                printer.print(escapeNull(t.getNote()));

                printer.println();
            }
        }
    }

    private void printSecurityInfo(CSVPrinter printer, Transaction t)
    {
        Security security = t.getSecurity();
        if (security != null)
        {
            printer.print(escapeNull(security.getIsin()));
            printer.print(escapeNull(security.getWkn()));
            printer.print(escapeNull(security.getTickerSymbol()));
            printer.print(escapeNull(security.getName()));
        }
        else
        {
            printer.print(""); //$NON-NLS-1$
            printer.print(""); //$NON-NLS-1$
            printer.print(""); //$NON-NLS-1$
            printer.print(""); //$NON-NLS-1$
        }
    }

    public void exportPortfolioTransactions(File directory, List<Portfolio> portfolios) throws IOException
    {
        for (Portfolio portfolio : portfolios)
            exportPortfolioTransactions(new File(directory, portfolio.getName() + ".csv"), portfolio); //$NON-NLS-1$
    }

    public void exportSecurityMasterData(File file, List<Security> securities) throws IOException
    {
        try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))
        {
            CSVPrinter printer = new CSVPrinter(writer);
            printer.setStrategy(STRATEGY);

            printer.println(new String[] { Messages.CSVColumn_ISIN, //
                            Messages.CSVColumn_WKN, //
                            Messages.CSVColumn_TickerSymbol, //
                            Messages.CSVColumn_SecurityName, //
                            Messages.CSVColumn_Currency, Messages.CSVColumn_Note });

            for (Security s : securities)
            {
                printer.print(escapeNull(s.getIsin()));
                printer.print(escapeNull(s.getWkn()));
                printer.print(escapeNull(s.getTickerSymbol()));
                printer.print(escapeNull(s.getName()));
                printer.print(escapeNull(s.getCurrencyCode()));
                printer.print(escapeNull(s.getNote()));
                printer.println();
            }
        }
    }

    public void exportSecurityPrices(File file, Security security) throws IOException
    {
        try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))
        {
            CSVPrinter printer = new CSVPrinter(writer);
            printer.setStrategy(STRATEGY);

            printer.println(new String[] { Messages.CSVColumn_Date, Messages.CSVColumn_Quote });

            for (SecurityPrice p : security.getPrices())
            {
                printer.print(p.getTime().toString());
                printer.print(Values.Quote.format(p.getValue()));
                printer.println();
            }
        }
    }

    public void exportSecurityPrices(File directory, List<Security> securities) throws IOException
    {
        for (Security security : securities)
            exportSecurityPrices(new File(directory, security.getIsin() + ".csv"), security); //$NON-NLS-1$
    }

    public void exportMergedSecurityPrices(File file, List<Security> securities) throws IOException
    {
        // prepare: (a) find earliest date (b) ignore securities w/o quotes
        LocalDate earliestDate = null;
        List<Security> export = new ArrayList<Security>(securities.size());

        for (Security s : securities)
        {
            List<SecurityPrice> prices = s.getPrices();
            if (!prices.isEmpty())
            {
                export.add(s);

                LocalDate quoteDate = prices.get(0).getTime();
                if (earliestDate == null)
                    earliestDate = quoteDate;
                else
                    earliestDate = earliestDate.isAfter(quoteDate) ? quoteDate : earliestDate;
            }
        }

        try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))
        {
            CSVPrinter printer = new CSVPrinter(writer);
            printer.setStrategy(STRATEGY);

            // write header
            printer.print(Messages.CSVColumn_Date);
            for (Security security : export)
                printer.print(security.getExternalIdentifier());
            printer.println();

            // stop if no securities exist
            if (earliestDate == null)
                return;

            // write quotes
            LocalDate pointer = earliestDate;
            LocalDate today = LocalDate.now();

            while (pointer.compareTo(today) <= 0)
            {
                // check if any quotes exist for that day at all
                int[] indices = new int[export.size()];

                int ii = 0;
                for (Security security : export)
                {
                    SecurityPrice p = new SecurityPrice(pointer, 0);
                    indices[ii] = Collections.binarySearch(security.getPrices(), p);
                    ii++;
                }

                boolean hasValues = false;
                for (ii = 0; ii < indices.length && !hasValues; ii++)
                    hasValues = indices[ii] >= 0;

                if (hasValues)
                {
                    printer.print(pointer.toString());

                    for (ii = 0; ii < indices.length; ii++)
                    {
                        if (indices[ii] < 0)
                            printer.print(""); //$NON-NLS-1$
                        else
                            printer.print(Values.Quote.format(export.get(ii).getPrices().get(indices[ii]).getValue()));
                    }

                    printer.println();
                }

                pointer = pointer.plusDays(1);
            }

        }
    }

    /* package */static String escapeNull(String value)
    {
        return value != null ? value : ""; //$NON-NLS-1$
    }
}
