package de.duehl.swing.ui.highlightingeditor.state;

/*
 * Copyright 2017 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 de.duehl.basics.debug.DebugHelper;
import de.duehl.basics.io.Charset;
import de.duehl.basics.io.FileHelper;
import de.duehl.basics.text.Text;
import de.duehl.swing.ui.highlightingeditor.io.WatchFileOnDiskChange;

import static de.duehl.swing.ui.highlightingeditor.HighlightingEditor.DEFAULT_CHARSET;

/**
 * Diese Klasse hält für den Editor selbst die Information, die bei Undo und Redo gesichert bzw.
 * wieder hergestellt werden müssen.
 *
 * @version 1.01     2017-12-19
 * @author Christian Dühl
 */

public class EditorState {

    private final static boolean DEBUG = true;

    /**
     * Name der Datei, die im Editor angezeigt wird. Der leere String, wenn die Datei bislang
     * keinen Namen hat.
     */
    private String filename;

    /** Zeichensatz der einzulesenden Datei, wird auch beim Speichern verwendet. */
    private Charset charset;

    /** Der Text im Editor. */
    private String text;

    /** Beobachtet externe Änderungen an der bearbeiteten Datei. */
    private WatchFileOnDiskChange watchFileOnDiskChange;

    /** Position des Cursors im Text. */
    private int caretPosition;

    /** Konstruktor. */
    public EditorState() {
        filename = "";
        charset = DEFAULT_CHARSET;
        text = "";
        watchFileOnDiskChange = new WatchFileOnDiskChange();
        caretPosition = 0;
    }

    /** Copy-Konstruktor. */
    public EditorState(EditorState that) {
        this.filename = that.filename;
        this.charset = that.charset;
        this.text = that.text;
        this.watchFileOnDiskChange = new WatchFileOnDiskChange(that.watchFileOnDiskChange);
        this.caretPosition = that.caretPosition;
    }

    /**
     * Getter für den Namen der Datei, die im Editor angezeigt wird. Der leere String, wenn die
     * Datei bislang keinen Namen hat.
     */
    public String getFilename() {
        return filename;
    }

    /** Setter für den Namen der Datei, die im Editor angezeigt wird. */
    public void setFilename(String filename) {
        this.filename = filename;
    }

    /** Getter für den Zeichensatz der einzulesenden Datei, wird auch beim Speichern verwendet. */
    public Charset getCharset() {
        return charset;
    }

    /** Getter für den Text im Editor. */
    public String getText() {
        return text;
    }

    /** Setter für den Text im Editor. */
    private void setText(String text) {
        this.text = text;
    }

    /** Getter für die Position des Cursors im Text. */
    public int getCaretPosition() {
        return caretPosition;
    }

    /** Setter für den Text und die Position des Cursors im Text im Editor. */
    public void setTextAndCaretPosition(String text, int caretPosition) {
        this.text = text;
        this.caretPosition = caretPosition;
    }

    /** Erzeugt den anzuzeigenden Namen im Reiter. */
    public String createNameForDisplay() {
        if (isFilenameEmpty()) {
            return "-----";
        }
        else {
            return getBarename();
        }
    }

    public boolean isFilenameEmpty() {
        return filename.isEmpty();
    }

    public String getBarename() {
        return FileHelper.getBareName(filename);
    }

    public String determineStartDirectory() {
        if (filename.isEmpty()) {
            return "C:";
        }
        else {
            return FileHelper.getDirName(filename);
        }
    }

    /** Legt anzeigen einer leeren, unbenannten Datei Dateinamen und Charset fest. */
    public void newFile() {
        watchFileOnDiskChange.notLoaded();
        filename = "";
        charset = DEFAULT_CHARSET;
    }

    /**
     * Legt beim Öffnen der Datei Dateinamen und Charset fest und liest die Datei ein.
     * Der geladene Text wird zurückgegeben.
     */
    public String openFile(String filename, Charset charset) {
        this.filename = filename;
        this.charset = charset;

        say("openFile  vor eigentlichem Laden");
        String text = loadText();
        say("openFile  nach eigentlichem Laden");

        return text;
    }

    /** Lädt den text der Datei. Ist kein Dateiname vergeben, wir der leere String zurückgegeben. */
    private String loadText() {
        if (isFilenameEmpty()) {
            say("Dateiname ist leer.");
            return "";
        }
        else {
            say("Starte Laden von  " + filename);
            String text = FileHelper.readFileToString(filename, charset);
            say("Fertig mit Laden von  " + filename);
            watchFileOnDiskChange.loadedSuccessfully(filename);
            return text;
        }
    }

    public void saveUnderFilename(String text) {
        setText(text);
        String textToSave = correctTextEndings(text);
        FileHelper.writeTextToFile(textToSave, filename, charset);
        watchFileOnDiskChange.savedSuccessfully(filename);
    }

    private static String correctTextEndings(String text) {
        String textToSave = text;
        if (textToSave.endsWith("\r\n")) {
            textToSave = Text.removeLastCharacters(text, 2);
        }
        else if (textToSave.endsWith("\n")) {
            textToSave = Text.removeLastCharacters(text, 1);
        }
        return textToSave;
    }

    /** Schaltet die Überwachung auf gelöscht oder extern veränderte Dateien ab. */
    public void disableWatchFileOnDiskChange() {
        watchFileOnDiskChange.setEnabled(false);
    }

    /** Gibt an, ob die Datei auf der Festplatte nicht mehr existiert. */
    public boolean doesFileNotExistAnymore() {
        return watchFileOnDiskChange.doesFileNotExistAnymore();
    }

    /**
     * Gibt an, dass die Datei nicht mehr erfolgreich geladen wurde (etwa nach Klick auf "Neue
     * Datei").
     */
    public void notLoaded() {
        watchFileOnDiskChange.notLoaded();
    }

    /**
     * Gibt an, ob die Datei auf der Festplatte nach dem letzten Laden oder Speichern im Editor
     * extern verändert wurde.
     */
    public boolean hasFileChangedOnDisk() {
        return watchFileOnDiskChange.hasFileChangedOnDisk();
    }

    /** Gibt die Länge des Textes an, nachdem alle Umbrüche durch \n ersetzt wurden. */
    public int getLineBreakCorrectedTextLength() {
        String lineBreaksCorrectedText = Text.lineBreaksToBackslashN(text);
        return lineBreaksCorrectedText.length();
    }

    private void say(String message) {
        if (DEBUG) {
            String title = "(" + (null == filename ? "unknown" : filename) + ") - ";
            DebugHelper.sayWithClassAndMethodAndTime(title + message);
        }
    }

    @Override
    public String toString() {
        return "EditorState [filename=" + filename + ", charset=" + charset + ", text=" + text
                + ", caretPosition=" + caretPosition + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + caretPosition;
        result = prime * result + ((charset == null) ? 0 : charset.hashCode());
        result = prime * result + ((filename == null) ? 0 : filename.hashCode());
        result = prime * result + ((text == null) ? 0 : text.hashCode());
        result = prime * result
                + ((watchFileOnDiskChange == null) ? 0 : watchFileOnDiskChange.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        EditorState other = (EditorState) obj;
        if (caretPosition != other.caretPosition) {
            return false;
        }
        if (charset != other.charset) {
            return false;
        }
        if (filename == null) {
            if (other.filename != null) {
                return false;
            }
        }
        else if (!filename.equals(other.filename)) {
            return false;
        }
        if (text == null) {
            if (other.text != null) {
                return false;
            }
        }
        else if (!text.equals(other.text)) {
            return false;
        }
        if (watchFileOnDiskChange == null) {
            if (other.watchFileOnDiskChange != null) {
                return false;
            }
        }
        else if (!watchFileOnDiskChange.equals(other.watchFileOnDiskChange)) {
            return false;
        }
        return true;
    }

}
