/*
 * Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
 *
 * This file is part of Loop Habit Tracker.
 *
 * Loop Habit Tracker is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Loop Habit Tracker is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.isoron.uhabits.core.io;

import android.support.annotation.*;

import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;

import java.io.*;
import java.text.*;
import java.util.*;
import java.util.zip.*;

/**
 * Class that exports the application data to CSV files.
 */
public class HabitsCSVExporter
{
    private List<Habit> selectedHabits;

    private List<String> generateDirs;

    private List<String> generateFilenames;

    private String exportDirName;
    /**
     * Delimiter used in a CSV file.
     */
    private final String DELIMITER = ",";

    @NonNull
    private final HabitList allHabits;

    public HabitsCSVExporter(@NonNull HabitList allHabits,
                             @NonNull List<Habit> selectedHabits,
                             @NonNull File dir)
    {
        this.allHabits = allHabits;
        this.selectedHabits = selectedHabits;
        this.exportDirName = dir.getAbsolutePath() + "/";

        generateDirs = new LinkedList<>();
        generateFilenames = new LinkedList<>();
    }

    public String writeArchive() throws IOException
    {
        String zipFilename;

        writeHabits();
        zipFilename = writeZipFile();
        cleanup();

        return zipFilename;
    }

    private void addFileToZip(ZipOutputStream zos, String filename)
        throws IOException
    {
        FileInputStream fis =
            new FileInputStream(new File(exportDirName + filename));
        ZipEntry ze = new ZipEntry(filename);
        zos.putNextEntry(ze);

        int length;
        byte bytes[] = new byte[1024];
        while ((length = fis.read(bytes)) >= 0) zos.write(bytes, 0, length);

        zos.closeEntry();
        fis.close();
    }

    private void cleanup()
    {
        for (String filename : generateFilenames)
            new File(exportDirName + filename).delete();

        for (String filename : generateDirs)
            new File(exportDirName + filename).delete();

        new File(exportDirName).delete();
    }

    @NonNull
    private String sanitizeFilename(String name)
    {
        String s = name.replaceAll("[^ a-zA-Z0-9\\._-]+", "");
        return s.substring(0, Math.min(s.length(), 100));
    }

    private void writeHabits() throws IOException
    {
        String filename = "Habits.csv";
        new File(exportDirName).mkdirs();
        FileWriter out = new FileWriter(exportDirName + filename);
        generateFilenames.add(filename);
        allHabits.writeCSV(out);
        out.close();

        for (Habit h : selectedHabits)
        {
            String sane = sanitizeFilename(h.getName());
            String habitDirName =
                String.format("%03d %s", allHabits.indexOf(h) + 1, sane);
            habitDirName = habitDirName.trim() + "/";

            new File(exportDirName + habitDirName).mkdirs();
            generateDirs.add(habitDirName);

            writeScores(habitDirName, h.getScores());
            writeCheckmarks(habitDirName, h.getCheckmarks());
        }

        writeMultipleHabits();
    }

    private void writeScores(String habitDirName, ScoreList scores)
        throws IOException
    {
        String path = habitDirName + "Scores.csv";
        FileWriter out = new FileWriter(exportDirName + path);
        generateFilenames.add(path);
        scores.writeCSV(out);
        out.close();
    }

    private void writeCheckmarks(String habitDirName, CheckmarkList checkmarks)
        throws IOException
    {
        String filename = habitDirName + "Checkmarks.csv";
        FileWriter out = new FileWriter(exportDirName + filename);
        generateFilenames.add(filename);
        checkmarks.writeCSV(out);
        out.close();
    }

    /**
     * Writes a scores file and a checkmarks file containing scores and checkmarks of every habit.
     * The first column corresponds to the date. Subsequent columns correspond to a habit.
     * Habits are taken from the list of selected habits.
     * Dates are determined from the oldest repetition date to the newest repetition date found in
     * the list of habits.
     *
     * @throws IOException if there was problem writing the files
     */
    private void writeMultipleHabits() throws IOException
    {
        String scoresFileName = "Scores.csv";
        String checksFileName = "Checkmarks.csv";
        generateFilenames.add(scoresFileName);
        generateFilenames.add(checksFileName);
        FileWriter scoresWriter = new FileWriter(exportDirName + scoresFileName);
        FileWriter checksWriter = new FileWriter(exportDirName + checksFileName);

        writeMultipleHabitsHeader(scoresWriter);
        writeMultipleHabitsHeader(checksWriter);

        Timestamp[] timeframe = getTimeframe();
        Timestamp oldest = timeframe[0];
        Timestamp newest = DateUtils.getToday();

        List<int[]> checkmarks = new ArrayList<>();
        List<double[]> scores = new ArrayList<>();
        for (Habit h : selectedHabits)
        {
            checkmarks.add(h.getCheckmarks().getValues(oldest, newest));
            scores.add(h.getScores().getValues(oldest, newest));
        }

        int days = oldest.daysUntil(newest);
        SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
        for (int i = 0; i <= days; i++)
        {
            Date day = newest.minus(i).toJavaDate();

            String date = dateFormat.format(day);
            StringBuilder sb = new StringBuilder();
            sb.append(date).append(DELIMITER);
            checksWriter.write(sb.toString());
            scoresWriter.write(sb.toString());

            for(int j = 0; j < selectedHabits.size(); j++)
            {
                checksWriter.write(String.valueOf(checkmarks.get(j)[i]));
                checksWriter.write(DELIMITER);
                String score =
                        String.format("%.4f", ((float) scores.get(j)[i]));
                scoresWriter.write(score);
                scoresWriter.write(DELIMITER);
            }
            checksWriter.write("\n");
            scoresWriter.write("\n");
        }
        scoresWriter.close();
        checksWriter.close();
    }

    /**
     * Writes the first row, containing header information, using the given writer.
     * This consists of the date title and the names of the selected habits.
     *
     * @param out the writer to use
     * @throws IOException if there was a problem writing
     */
    private void writeMultipleHabitsHeader(Writer out) throws IOException
    {
        out.write("Date" + DELIMITER);
        for (Habit h : selectedHabits) {
            out.write(h.getName());
            out.write(DELIMITER);
        }
        out.write("\n");
    }

    /**
     * Gets the overall timeframe of the selected habits.
     * The timeframe is an array containing the oldest timestamp among the habits and the
     * newest timestamp among the habits.
     * Both timestamps are in milliseconds.
     *
     * @return the timeframe containing the oldest timestamp and the newest timestamp
     */
    private Timestamp[] getTimeframe()
    {
        Timestamp oldest = Timestamp.ZERO.plus(1000000);
        Timestamp newest = Timestamp.ZERO;
        for (Habit h : selectedHabits)
        {
            if(h.getRepetitions().getOldest() == null || h.getRepetitions().getNewest() == null)
                continue;
            Timestamp currOld = h.getRepetitions().getOldest().getTimestamp();
            Timestamp currNew = h.getRepetitions().getNewest().getTimestamp();
            oldest = currOld.isOlderThan(oldest) ? oldest : currOld;
            newest = currNew.isNewerThan(newest) ? newest : currNew;
        }
        return new Timestamp[]{oldest, newest};
    }

    private String writeZipFile() throws IOException
    {
        SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
        String date = dateFormat.format(DateUtils.getStartOfToday());
        String zipFilename =
            String.format("%s/Loop Habits CSV %s.zip", exportDirName, date);

        FileOutputStream fos = new FileOutputStream(zipFilename);
        ZipOutputStream zos = new ZipOutputStream(fos);

        for (String filename : generateFilenames)
            addFileToZip(zos, filename);

        zos.close();
        fos.close();

        return zipFilename;
    }
}
