package de.duehl.basics.io.textfile.split;

/*
 * Copyright 2019 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.util.ArrayList;
import java.util.List;

import de.duehl.basics.io.Charset;
import de.duehl.basics.io.FileHelper;
import de.duehl.basics.io.FineFileWriter;
import de.duehl.basics.io.Writer;
import de.duehl.basics.io.textfile.FullNormalTextFileReader;
import de.duehl.basics.io.textfile.FullTextFileReader;
import de.duehl.basics.text.NumberString;

/**
 * Diese Klasse teilt eine Textdatei in Teile auf.
 *
 * @version 1.01     2019-12-03
 * @author Christian Dühl
 */

public class TextFileSplitter {

    /** Name der aufzuteilenden Textdatei. */
    private final String filename;

    /** Das Verzeichnis mit den aufgeteilten Eingabedateien. */
    private final String splittedDirectory;

    /** Kodierung der aufzuteilenden Textdatei wie auch der erzeugten Teile. */
    private final Charset charset;

    /** Gewünschte Anzahl an Datensätzen in jedem Teil. */
    private final int size;

    /** Gibt an, ob die Datei in der ersten Zeile Titel enthält. */
    private final boolean hasTitles;

    /** Nummer der gerade bearbeiteten Ausgabedatei. */
    private int opendFileNumber;

    /** Anzahl gelesener Eingabe-Datenzeilen. */
    private int numberOfReadInputLines;

    /** Objekt zum Schreiben der Teildateien. */
    private Writer writer;

    /** Anzahl der erzeugten aufgeteilten Dateien. */
    private int numberOfCreatedSplitFiles;

    /** Namen der erzeugten aufgeteilten Dateien. */
    private List<String> filenamesOfCreatedSplitFiles;

    /** Erlaubt (true, default) oder verhindert (false) Ausgaben auf STDOUT. */
    private boolean verbose;

    /**
     * Konstruktor der die aufgeteilten Dateien im gleichen Verzeichnis wie die aufzuteilende Datei
     * erzeugt.
     *
     * @param filename
     *            Name der aufzuteilenden Textdatei.
     * @param charset
     *            Kodierung der aufzuteilenden Textdatei wie auch der erzeugten Teile.
     * @param size
     *            Gewünschte Anzahl an Datensätzen in jedem Teil.
     * @param hasTitles
     *            Gibt an, ob die Datei in der ersten Zeile Titel enthält.
     */
    public TextFileSplitter(String filename, Charset charset, int size, boolean hasTitles) {
        this(filename, FileHelper.getDirName(filename), charset, size, hasTitles);
    }

    /**
     * Konstruktor.
     *
     * @param filename
     *            Name der aufzuteilenden Textdatei.
     * @param splittedDirectory
     *            Das Verzeichnis mit den aufgeteilten Eingabedateien.
     * @param charset
     *            Kodierung der aufzuteilenden Textdatei wie auch der erzeugten Teile.
     * @param size
     *            Gewünschte Anzahl an Datensätzen in jedem Teil.
     * @param hasTitles
     *            Gibt an, ob die Datei in der ersten Zeile Titel enthält.
     */
    public TextFileSplitter(String filename, String splittedDirectory, Charset charset, int size,
            boolean hasTitles) {
        this.filename = filename;
        this.splittedDirectory = splittedDirectory;
        this.charset = charset;
        this.size = size;
        this.hasTitles = hasTitles;

        verbose = true;
    }

    /** Verhindert Ausgaben auf STDOUT. */
    public void beQuiet() {
        verbose = false;
    }

    /** Teilt die Datei auf. */
    public void work() {
        opendFileNumber = 0;
        numberOfReadInputLines = 0;
        numberOfCreatedSplitFiles = 0;
        filenamesOfCreatedSplitFiles = new ArrayList<>();

        workOnInputFile();
        closeActualOutputFile();
        say("Fertig.");
    }

    private void workOnInputFile() {
        say("Lese " + filename);
        FullTextFileReader reader = new FullNormalTextFileReader(filename, charset);
        reader.setLineDescription("Datensätze zum Aufteilen");
        reader.setProgressTextStart("Einlesen");
        if (hasTitles) {
            reader.skipFirstLine();
        }
        reader.skipBlankLines();
        reader.beQuiet();
        reader.setNumberOfLinesReadToPrintProgress(1_000);
        reader.read(line -> analyseLine(line, reader.getSkippedFirstLine()));
    }

    private void analyseLine(String line, String titlesIfAny) {
        if (0 == opendFileNumber) {
            openNextOutputFile(titlesIfAny);
        }
        else if (numberOfReadInputLines % size == 0) {
            closeActualOutputFile();
            openNextOutputFile(titlesIfAny);
        }
        ++numberOfReadInputLines;
        writer.writeln(line);
    }

    private void openNextOutputFile(String titlesIfAny) {
        ++opendFileNumber;
        String outputFilename = generateOutputFilename();
        say("Öffne " + outputFilename + " zum Schreiben.");
        writer = new FineFileWriter(outputFilename, charset);
        if (hasTitles && !titlesIfAny.isEmpty()) {
            writer.writeln(titlesIfAny);
        }

        ++numberOfCreatedSplitFiles;
        filenamesOfCreatedSplitFiles.add(outputFilename);
    }

    private String generateOutputFilename() {
        String bareFilename = FileHelper.getBareName(filename);
        String bareOutputFilename = FileHelper.insertBeforeExtension(bareFilename,
                "_" + NumberString.addLeadingZeroes(opendFileNumber, 4));
        String outputFilename = FileHelper.concatPathes(splittedDirectory, bareOutputFilename);

        if (filename.equals(outputFilename)) {
            throw new RuntimeException("Ausgabename = Eingabename: " + filename);
        }

        return outputFilename;
    }

    private void closeActualOutputFile() {
        writer.close();
    }

    private void say(String message) {
        if (verbose) {
            System.out.println(message);
        }
    }

    /** Gibt die Anzahl der erzeugten aufgeteilten Dateien zurück. */
    public int getNumberOfCreatedSplitFiles() {
        return numberOfCreatedSplitFiles;
    }

    /** Gibt die Namen der erzeugten aufgeteilten Dateien zurück. */
    public List<String> getFilenamesOfCreatedSplitFiles() {
        return filenamesOfCreatedSplitFiles;
    }

}
