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

/*
 * 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.datetime.DateAndTimeHelper;
import de.duehl.basics.debug.DebugHelper;
import de.duehl.basics.io.FileHelper;

/**
 * Diese Klasse überprüft die im Editor angezeigte Datei darauf, ob sie auf der Festplatte
 * gelöscht oder extern verändert wurde.
 *
 * @version 1.01     2017-12-19
 * @author Christian Dühl
 */

public class WatchFileOnDiskChange {

    private static final boolean DEBUG = false;

    /**
     * Gibt an, ob die Datei erfolgreich geladen wurde. Erst dann macht es Sinn, die Datei auf der
     * Festplatte auch zu überprüfen. Vorher kann der Name leer sein oder nur "foo.txt" ohne Pfad
     * enthalten.
     * Wenn eine neue Datei gespeichert wurde, gibt es sie ebenfalls auf der Festplatte.
     */
    private boolean loadedOrSavedSuccessfully;

    /** Der Dateiname der gerade bearbeiteten Datei. */
    private String filename;

    /** Letzter Zeitpunkt des Ladens oder des Schreibens der Datei. */
    private long lastLoadOrSaveTimeInEditor;

    /**
     * Letzte Veränderungszeit der Datei auf der Festplatte. Diese benötigen wir, um festzustellen,
     * wenn eine ältere Datei über die Datei, die wir bearbeiten, kopiert wird.
     */
    private long lastModificationTimeOnDisk;

    /** Gibt an, ob die Überwachung in dieser Klasse aktiv sein soll. */
    private boolean enabled;

    /** Konstruktor. */
    public WatchFileOnDiskChange() {
        enabled = true;
        notLoaded();
    }

    /** Copy-Konstruktor. */
    public WatchFileOnDiskChange(WatchFileOnDiskChange that) {
        this.loadedOrSavedSuccessfully = that.loadedOrSavedSuccessfully;
        this.filename = that.filename;
        this.lastLoadOrSaveTimeInEditor = that.lastLoadOrSaveTimeInEditor;
        this.lastModificationTimeOnDisk = that.lastModificationTimeOnDisk;
        this.enabled = that.enabled;
    }

    /** Gibt an, ob die Überwachung auf gelöscht oder extern veränderte Dateien aktiv sein soll. */
    public void setEnabled(boolean enabled) {
        say("enabled = " + enabled);
        this.enabled = enabled;
    }

    /** Gibt an, dass die Datei unter dem angegebene Dateinamen erfolgreich eingelesen wurde. */
    public void loadedSuccessfully(String filename) {
        say("loaded: " + filename);
        successfullySyncedWithDisk(filename);
    }

    /** Gibt an, dass die Datei unter dem angegebene Dateinamen erfolgreich geschrieben wurde. */
    public void savedSuccessfully(String filename) {
        say("saved: " + filename);
        successfullySyncedWithDisk(filename);
    }

    private void successfullySyncedWithDisk(String filename) {
        this.filename = filename;
        say("filename = " + filename);
        lastLoadOrSaveTimeInEditor = System.currentTimeMillis();
        loadedOrSavedSuccessfully = true;
        lastModificationTimeOnDisk = determineModificationTimeOnDisc();
    }

    /**
     * Gibt an, dass die Datei nicht mehr erfolgreich geladen wurde (etwa nach Klick auf "Neue
     * Datei").
     */
    public void notLoaded() {
        say("not loaded");
        filename = "";
        loadedOrSavedSuccessfully = false;
    }

    /** Gibt an, ob die Datei auf der Festplatte nicht mehr existiert. */
    public boolean doesFileNotExistAnymore() {
        say(this);
        if (!enabled || !loadedOrSavedSuccessfully) {
            say("not checked");
            return false;
        }

        boolean exists = FileHelper.exists(filename);
        say("Die Datei '" + filename + "' existiert" + (exists ? "" : " nicht") + ".");

        return !exists;
    }

    /**
     * Gibt an, ob die Datei auf der Festplatte nach dem letzten Laden oder Speichern im Editor
     * extern verändert wurde.
     */
    public boolean hasFileChangedOnDisk() {
        say(this);
        if (!enabled || !loadedOrSavedSuccessfully) {
            say("not checked");
            return false;
        }


        long modificationTimeOnDisk = determineModificationTimeOnDisc();
        say("lastLoadOrSaveTimeInEditor = " + millisToSTring(lastLoadOrSaveTimeInEditor));
        say("modificationTimeOnDisk     = " + millisToSTring(modificationTimeOnDisk));
        say("lastModificationTimeOnDisk = " + millisToSTring(lastModificationTimeOnDisk));

        boolean newerOnDisc = lastLoadOrSaveTimeInEditor < modificationTimeOnDisk;
        say("newerOnDisc = " + newerOnDisc);

        boolean exchangedByOlderFile = modificationTimeOnDisk < lastModificationTimeOnDisk;
        say("exchangedByOlderFile = " + exchangedByOlderFile);

        boolean changedOnDisc = newerOnDisc ||  exchangedByOlderFile;
        say("changedOnDisc = " + changedOnDisc);

        return changedOnDisc;
    }

    private String millisToSTring(long milliSeconds) {
        //return TimeHelper.milliSecondsToHoursMinutesSecondsMilliSecondsBraces(milliSeconds);
        return DateAndTimeHelper.millisToDateAndTimeString(milliSeconds);
    }

    private long determineModificationTimeOnDisc() {
        return FileHelper.fileLastModifiedAsMillis(filename);
    }

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

    @Override
    public String toString() {
        return "WatchFileOnDiskChange [loadedOrSavedSuccessfully=" + loadedOrSavedSuccessfully
                + ", filename=" + filename + ", lastLoadOrSaveTimeInEditor="
                + lastLoadOrSaveTimeInEditor + ", lastModificationTimeOnDisk="
                + lastModificationTimeOnDisk + ", enabled=" + enabled + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (enabled ? 1231 : 1237);
        result = prime * result + ((filename == null) ? 0 : filename.hashCode());
        result = prime * result
                + (int) (lastLoadOrSaveTimeInEditor ^ (lastLoadOrSaveTimeInEditor >>> 32));
        result = prime * result
                + (int) (lastModificationTimeOnDisk ^ (lastModificationTimeOnDisk >>> 32));
        result = prime * result + (loadedOrSavedSuccessfully ? 1231 : 1237);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        WatchFileOnDiskChange other = (WatchFileOnDiskChange) obj;
        if (enabled != other.enabled) {
            return false;
        }
        if (filename == null) {
            if (other.filename != null) {
                return false;
            }
        }
        else if (!filename.equals(other.filename)) {
            return false;
        }
        if (lastLoadOrSaveTimeInEditor != other.lastLoadOrSaveTimeInEditor) {
            return false;
        }
        if (lastModificationTimeOnDisk != other.lastModificationTimeOnDisk) {
            return false;
        }
        if (loadedOrSavedSuccessfully != other.loadedOrSavedSuccessfully) {
            return false;
        }
        return true;
    }

}
