package de.duehl.basics.io;

/*
 * Copyright 2022 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.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import de.duehl.basics.io.exceptions.IORuntimeException;
import de.duehl.basics.text.Lines;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse stellt einen Writer dar, im Falle von Fehlern werden Ausnahmen geworfen.
 *
 * @version 1.03     2022-05-27
 * @author Christian Dühl
 */

public class FineFileWriter implements Writer {

    /** Der Schreiberling. */
    private final OutputStreamWriter writer;

    /** Name der Ausgabedatei. */
    private final String fileName;

    /** Gibt an ob an eine bestehende Datei angehängt wird. */
    private final boolean append;

    /** Zeichensatz der einzulesenden Datei (z.B. "ISO-8859-1", "UTF-8"). */
    private final Charset charset;

    /**
     * Konstruktor aus einem File.
     *
     * @param file
     *            Zu schreibende Datei.
     * @throws UnsupportedEncodingException
     * @throws FileNotFoundException
     */
    public FineFileWriter(File file) {
        this(file.getAbsolutePath(), false);
    }

    /**
     * Konstruktor aus einem Dateinamen zu einer Datei im Format "ISO-8859-1".
     *
     * @param fileName
     *            Der Name der auszugebenden Datei.
     * @throws UnsupportedEncodingException
     * @throws FileNotFoundException
     */
    public FineFileWriter(String fileName) {
        this(fileName, false);
    }

    /**
     * Konstruktor zum Anlegen oder Anhängen an eine Datei im Format "ISO-8859-1".
     *
     * @param fileName
     *            Der Name der auszugebenden Datei.
     * @param append
     *            Gibt an, ob an die Datei angehängt wird.
     * @throws IORuntimeException
     *             Wenn die Datei (der Pfad) nicht gefunden wurde oder falls eine unbekannte
     *             Kodierung an den OutputStreamWriter übergeben wurde.
     */
    public FineFileWriter(String fileName, boolean append) {
        this(fileName, Charset.ISO_8859_1, append);
    }

    /**
     * Konstruktor zum Anlegen einer neuen Datei mit Angabe des Zeichensatz.
     *
     * @param fileName
     *            Der Name der auszugebenden Datei.
     * @param charset
     *            Zeichensatz der zu schreibenden Datei.
     * @throws IORuntimeException
     *             Wenn die Datei (der Pfad) nicht gefunden wurde oder falls eine unbekannte
     *             Kodierung an den OutputStreamWriter übergeben wurde.
     */
    public FineFileWriter(String fileName, Charset charset) {
        this(fileName, charset, false);
    }

    /**
     * Konstruktor
     *
     * @param file
     *            Der Name der auszugebenden Datei.
     * @param charset
     *            Zeichensatz der zu schreibenden Datei.
     * @param append
     *            Gibt an, ob an die Datei angehängt wird.
     * @throws IORuntimeException
     *             Wenn die Datei (der Pfad) nicht gefunden wurde oder falls eine unbekannte
     *             Kodierung an den OutputStreamWriter übergeben wurde.
     */
    public FineFileWriter(String file, Charset charset, boolean append) {
        this.fileName = file;
        this.append = append;
        this.charset = charset;
        this.writer = openWriter();
    }

    /**
     * Öffnet den Writer. Die Fehlerbehandlung erfolgt hier.
     *
     * @return Writer-Objekt.
     *
     * @throws IORuntimeException
     *             Wenn die Datei (der Pfad) nicht gefunden wurde oder falls eine unbekannte
     *             Kodierung an den OutputStreamWriter übergeben wurde.
     */
    private OutputStreamWriter openWriter() {
        try {
            return tryToOpenWriter();
        }
        catch (FileNotFoundException e) {
            throw new IORuntimeException("Der Pfad zur Ausgabedatei '" + fileName + "' wurde "
                    + "nicht gefunden. (" + e.getMessage() + ")");
        }
        catch (UnsupportedEncodingException e) {
            throw new IORuntimeException("Bei der Erzeugung des OutputStreamWriters für die "
                    + "Eingabedatei '" + fileName + "' wurde eine nicht unterstützte Kodierung "
                    + "verwendet. (" + e.getMessage() + ")");
        }
    }

    /**
     * Öffnet den Writer.
     *
     * @return Writer-Objekt.
     *
     * @throws FileNotFoundException
     *             Wenn die Datei (der Pfad) nicht gefunden wurde.
     * @throws UnsupportedEncodingException
     *             Falls eine unbekannte Kodierung an den OutputStreamWriter
     *             übergeben wurde.
     */
    private OutputStreamWriter tryToOpenWriter() throws FileNotFoundException,
            UnsupportedEncodingException {
        OutputStream outputStream = new FileOutputStream(fileName, append);
        return new OutputStreamWriter(outputStream, charset.getCharsetAsString());
        // BufferedWriter drum wickeln?
    }

    /**
     * Schreibt den übergebenen String auf den Writer.
     *
     * @param text
     *            Der auszugebende Text.
     * @throws IORuntimeException
     *             Falls beim Schreiben ein Fehler auftrat.
     */
    @Override
    public void write(String text) {
        try {
            writer.write(text);
            //writer.flush(); /* bremst */
        }
        catch (IOException e) {
            throw new IORuntimeException("Beim Schreiben in die Datei '" + fileName + "' trat ein "
                    + "Fehler auf. (" + e.getMessage() + ")");
        }
    }

    /**
     * Schreibt die übergebene Zahl auf den Writer.
     *
     * @param number
     *            Die auszugebende Zahl.
     * @throws IORuntimeException
     *             Falls beim Schreiben ein Fehler auftrat.
     */
    @Override
    public void write(int number) {
        write(Integer.toString(number));
    }

    /** Gibt einen Zeilenumbruch aus. */
    @Override
    public void writeln() {
        write(Text.LINE_BREAK);
    }

    /**
     * Schreibt den übergebenen String auf den Writer und fügt einen Zeilenumbruch an.
     *
     * @param text
     *            Der auszugebende Text.
     * @throws IORuntimeException
     *             Falls beim Schreiben ein Fehler auftrat.
     */
    @Override
    public void writeln(String text) {
        write(text + Text.LINE_BREAK);
    }

    /**
     * Schließt den Writer.
     *
     * @throws IORuntimeException
     *             Falls beim Schließen ein Fehler auftrat.
     */
    @Override
    public void close() {
        try {
            writer.close();
        }
        catch (IOException e) {
            throw new IORuntimeException("Beim Schließen der Datei '" + fileName + "' trat ein "
                    + "Fehler auf. (" + e.getMessage() + ")");
        }
    }

    /**
     * Veranlasst den Writer alle Daten im Zwischenspeicher in die Datei zu schreiben.
     *
     * @throws IORuntimeException
     *             Falls beim flush ein Fehler auftrat.
     */
    @Override
    public void flush() {
        try {
            writer.flush();
        }
        catch (IOException e) {
            throw new IORuntimeException("Beim Flushen der Datei '" + fileName + "' trat ein "
                    + "Fehler auf. (" + e.getMessage() + ")");
        }
    }

    /**
     * Schreibt alle Zeilen der übergebenen Liste auf den Writer und fügt jeweils einen
     * Zeilenumbruch an.
     *
     * @param list
     *            Die Liste mit den auszugebenden Zeilen.
     * @throws IORuntimeException
     *             Falls beim Schreiben ein Fehler auftrat.
     */
    @Override
    public void writeAllLines(List<String> list) {
        for (String text : list) {
            write(text + Text.LINE_BREAK);
        }
    }

    /**
     * Schreibt alle Zeilen der übergebenen Liste auf den Writer und fügt jeweils einen
     * Zeilenumbruch an.
     *
     * @param lines
     *            Die auszugebenden Zeilen.
     * @throws IORuntimeException
     *             Falls beim Schreiben ein Fehler auftrat.
     */
    @Override
    public void writeAllLines(Lines lines) {
        writeAllLines(lines.getLines());
    }

    /**
     * Schreibt eine Menge von Strings tabgetrennt in eine Zeile und fügt einen Zeilenumbruch an.
     *
     * @param fields
     *            Menge der tabgetrennt auszugebenden Strings.
     */
    @Override
    public void writeFieldsLine(List<String> fields) {
        boolean firstField = true;
        for (String field : fields) {
            if (firstField) {
                firstField = false;
            }
            else {
                write("\t");
            }
            write(field);
        }
        writeln();
    }

    /**
     * Schreibt eine Menge von Strings tabgetrennt in eine Zeile und fügt einen Zeilenumbruch an.
     *
     * @param fields
     *            Menge der tabgetrennt auszugebenden Strings.
     */
    @Override
    public void writeFieldsLine(String ... fields) {
        writeFieldsLine(Arrays.asList(fields));
    }

    /**
     * Schreibt eine Menge von Strings tabgetrennt in eine Zeile und fügt einen
     * Zeilenumbruch an.
     *
     * @param fields
     *            Menge der tabgetrennt auszugebenden Strings.
     * @param moreFields
     *            Weitere Menge von tabgetrennt auszugebenden Strings.
     */
    @Override
    public void writeFieldsLine(List<String> fields, String... moreFields) {
        List<String> totalFields = new ArrayList<>();
        totalFields.addAll(fields);
        totalFields.addAll(Arrays.asList(moreFields));
        writeFieldsLine(totalFields);
    }

    /** Getter für den Namen der Ausgabedatei. */
    @Override
    public String getFileName() {
        return fileName;
    }

}
