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

/*
 * Copyright 2024 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.Collections;
import java.util.List;

import de.duehl.basics.io.Charset;
import de.duehl.basics.io.Reader;
import de.duehl.basics.io.Writer;
import de.duehl.basics.text.Text;
import de.duehl.basics.io.FineFileReader;
import de.duehl.basics.io.FineFileWriter;

/**
 * Diese Klasse schneidet einen bestimmten Teil der Spalten einer Textdatei aus und speichert ihn
 * als neue Datei ab.
 *
 *     Anmerkung:
 *
 *     Für sehr lange Dateien lese ich nicht erstmal alles ein und arbeite dann auf den Zeilen,
 *     sondern lese Zeile für Zeile und schreibe die dann gleich in die Ergebnisdatei. Das ist
 *     etwas hässlicher, aber hilfreich, wenn die Datei nicht in den Speicher passt.
 *
 * @version 1.01     2024-01-31
 * @author Christian Dühl
 */
public class ColumnsCutter {

    /** Der Name der zu bearbeitenden Datei mit Pfad. */
    private final String inputFilename;

    /** Der Name der zu erzeugenden Datei bestehend aus den gewünschten Spalten mit Pfad. */
    private final String outputFilename;

    /** Die Nummern der zu behaltenden Spalten (1-basiert). */
    private final List<Integer> columnNumbersToKeep;

    /** Das Charset der EIn- und Ausgabedatei. */
    private Charset charset;

    /** Die Anzahl der Spalten in der ersten Zeile. */
    private int numberOfColumnsInFirstLine;

    /** Der Writer, der die Ausgabedatei erzeugt. */
    private Writer writer;

    /** Die Zeilennummer der Eingabedatei für Fehlermeldungen (1-basiert). */
    private int lineNumber;

    /** Die höchste Spaltennummer, die behalten werden soll (1-basiert). */
    private final int highestColumnNumberToKeep;

    /**
     * Konstruktor.
     *
     * @param inputFilename
     *            Der Name der zu bearbeitenden Datei mit Pfad.
     * @param outputFilename
     *            Der Name der zu erzeugenden Datei bestehend aus den gewünschten Spalten mit Pfad.
     * @param lastColumnNumberToKeep
     *            Die Nummer der letzten zu behaltenden Spalte (1-basiert).
     */
    public ColumnsCutter(String inputFilename, String outputFilename,
            int lastColumnNumberToKeep) {
        this(inputFilename, outputFilename, createNumberList(1, lastColumnNumberToKeep));
    }

    /**
     * Konstruktor.
     *
     * @param inputFilename
     *            Der Name der zu bearbeitenden Datei mit Pfad.
     * @param outputFilename
     *            Der Name der zu erzeugenden Datei bestehend aus den gewünschten Spalten mit Pfad.
     * @param firstColumnNumberToKeep
     *            Die Nummer der ersten zu behaltenden Spalte (1-basiert).
     * @param lastColumnNumberToKeep
     *            Die Nummer der letzten zu behaltenden Spalte (1-basiert).
     */
    public ColumnsCutter(String inputFilename, String outputFilename,
            int firstColumnNumberToKeep, int lastColumnNumberToKeep) {
        this(inputFilename, outputFilename,
                createNumberList(firstColumnNumberToKeep, lastColumnNumberToKeep));
    }

    /**
     * Erstellt eine Liste mit den fortlaufenden Nummern von der ersten zu behaltenden Spalte bis
     * zur letzten zu behaltenden Spalte.
     *
     * @param firstColumnNumberToKeep
     *            Die Nummer der ersten zu behaltenden Spalte (1-basiert).
     * @param lastColumnNumberToKeep
     *            Die Nummer der letzten zu behaltenden Spalte (1-basiert).
     * @return Die erzeugte Liste mit den zu behaltenden Spaltennummern (1-basiert).
     */
    private static List<Integer> createNumberList(int firstColumnNumberToKeep,
            int lastColumnNumberToKeep) {
        List<Integer> columnNumbersToKeep = new ArrayList<>();

        if (firstColumnNumberToKeep > lastColumnNumberToKeep) {
            throw new RuntimeException("Die Nummer der ersten zu behaltenden Spalte ist größer "
                    + "als die Nummer der letzten zu behaltenden Spalte.\n"
                    + "\t" + "firstColumnNumberToKeep = " + firstColumnNumberToKeep + "\n"
                    + "\t" + "lastColumnNumberToKeep = " + lastColumnNumberToKeep + "\n");
        }

        for (int column = firstColumnNumberToKeep; column <= lastColumnNumberToKeep; ++column) {
            columnNumbersToKeep.add(column);
        }

        return columnNumbersToKeep;
    }

    /**
     * Konstruktor.
     *
     * @param inputFilename
     *            Der Name der zu bearbeitenden Datei mit Pfad.
     * @param outputFilename
     *            Der Name der zu erzeugenden Datei bestehend aus den gewünschten Spalten mit Pfad.
     * @param columnNumbersToKeep
     *            Die Nummern der zu behaltenden Spalten (1-basiert).
     */
    public ColumnsCutter(String inputFilename, String outputFilename,
            List<Integer> columnNumbersToKeep) {
        this.inputFilename = inputFilename;
        this.outputFilename = outputFilename;
        this.columnNumbersToKeep = columnNumbersToKeep;

        Collections.sort(columnNumbersToKeep);

        charset = Charset.ISO_8859_1;
        highestColumnNumberToKeep = determineHighestColumnNumberToKeep();
    }

    private int determineHighestColumnNumberToKeep() {
        int highestColumnNumber = -1;

        for (int columnNumber : columnNumbersToKeep) {
            if (highestColumnNumber < columnNumber) {
                highestColumnNumber = columnNumber;
            }
        }

        return highestColumnNumber;
    }

    /**
     * Im Normalfall wird ISO_8859_1 verwendet, wenn UTF_8 verwendet werden soll, muss vor dem
     * Aufruf von cut() diese Methode aufgerufen werden.
     */
    public void useUtf8() {
        charset = Charset.UTF_8;
    }

    /** Führt das Ausschneiden der gewünschten Spalten durch. */
    public void cut() {
        Reader reader = new FineFileReader(inputFilename, charset);
        writer = new FineFileWriter(outputFilename, charset);

        String firstLine = reader.readNextLine();
        lineNumber = reader.getLineNumber();
        numberOfColumnsInFirstLine = analyseColumnSizeOfFirstLine(firstLine);
        workWithLine(firstLine);

        String line = reader.readNextLine();
        while (line != null) {
            lineNumber = reader.getLineNumber();
            workWithLine(line);
            line = reader.readNextLine();
        }

        writer.close();
        reader.close();
    }

    private int analyseColumnSizeOfFirstLine(String firstLine) {
        if (null == firstLine) {
            return 0;
        }
        else {
            List<String> columns = Text.splitByTabulatorNotConsumingWhitespace(firstLine);
            return columns.size();
        }
    }

    private void workWithLine(String line) {
        List<String> columns = Text.splitByTabulatorNotConsumingWhitespace(line);

        int numberOfColumnsInLine = columns.size();
        warnIfNumberOfColumnsDiffer(numberOfColumnsInLine);
        checkAllWantedColumnsAreInLine(numberOfColumnsInLine);

        List<String> wantedColumns = new ArrayList<>();
        for (int columnNumber : columnNumbersToKeep) { // Das geht so, da die Nummern sortiert sind!
            String wantedColumn = columns.get(columnNumber - 1);
            wantedColumns.add(wantedColumn);
        }

        String outputLine = Text.joinWithTabulator(wantedColumns);
        writer.writeln(outputLine);
    }

    private void warnIfNumberOfColumnsDiffer(int numberOfColumnsInLine) {
        if (numberOfColumnsInLine != numberOfColumnsInFirstLine) {
            Text.say("Die Zeile " + lineNumber + " hat eine andere Spaltenzahl ("
                    + numberOfColumnsInLine + ") als die erste Zeile (" + numberOfColumnsInFirstLine
                    + ").");
        }
    }

    private void checkAllWantedColumnsAreInLine(int numberOfColumnsInLine) {
        if (numberOfColumnsInLine < highestColumnNumberToKeep) {
            throw new RuntimeException("Eine Zeile hat nicht genügend viele Spalten.\n"
                    + "\t" + "Eingabedatei                       : " + inputFilename + "\n"
                    + "\t" + "Zeilennummer                       : " + lineNumber + "\n"
                    + "\t" + "Anzahl Spalten                     : " + numberOfColumnsInLine + "\n"
                    + "\t" + "Höchste zu behaltende Spaltennummer: "
                            + highestColumnNumberToKeep + "\n");
        }
    }

/*
 * TODO Testen.
 */

}
