package de.duehl.basics.io.walker;

/*
 * 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 java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import de.duehl.basics.collections.Stack;

public class DirWalker {

    private final List<DirWalkerObserver> observers;

    private final String startPath;

    private final Stack<String> directories;

    private final List<String> localSubDirectories;
    private final List<String> localFiles;

    public DirWalker(String startPath) {
        this.startPath = startPath;
        observers = new ArrayList<>();
        directories = new Stack<>();
        localSubDirectories = new ArrayList<>();
        localFiles = new ArrayList<>();
        checkIfStartPathIsADirectory();
    }

    private void checkIfStartPathIsADirectory() {
        if (!new File(startPath).isDirectory()) {
            throw new DirWalkerException("Das übergebene Verzeichnis '" + startPath
                    + "' ist kein Verzeichnis im Dateisystem!");
        }
    }

    public void addObserver(DirWalkerObserver observer) {
        observers.add(observer);
    }

    public void removeObserver(DirWalkerObserver observer) {
        observers.remove(observer);
    }

    public void walk() {
        directories.clear();
        directories.push(startPath);

        walkDirectories();
    }

    private void walkDirectories() {
        while (!directories.isEmpty()) {
            String dir = directories.pop();
            workOnDirectory(dir);
        }
    }

    private void workOnDirectory(String dir) {
        File dirAsFile = buildDirAsFile(dir);
        clearLocalFilesAndDirectoriesLists();
        determineLocalFilesAndDirectories(dirAsFile);
        workOnLocalFilesAndDirectories(dir);
    }

    private File buildDirAsFile(String dir) {
        File dirAsFile = new File(dir);
        return dirAsFile;
    }

    private void clearLocalFilesAndDirectoriesLists() {
        localSubDirectories.clear();
        localFiles.clear();
    }

    private void determineLocalFilesAndDirectories(File dirAsFile) {
        for (File subDirOrFile : dirAsFile.listFiles()) {
            if (subDirOrFile.isDirectory()) {
                localSubDirectories.add(subDirOrFile.getPath());
            }
            else {
                localFiles.add(subDirOrFile.getPath());
            }
        }
    }

    private void workOnLocalFilesAndDirectories(String dir) {
        if (localSubDirectories.isEmpty() && localFiles.isEmpty()) {
            visitEmptyDir(dir);
            /*
             * Als leer wird ein Verzeichnis nur dann definiert, wenn es weder Dateien noch
             * Unterverzeichnisse enthält.
             */
        }
        else {
            visitNotEmptyDir(dir);
            workOnLocalSubDirectories();
            workOnLocalFiles();
        }
    }

    private void workOnLocalSubDirectories() {
        Collections.sort(localSubDirectories, new CaseIgnoringStringComparator());
        Collections.reverse(localSubDirectories); // wegen push und pop -> richtige Reihenfolge

        for (String subDir : localSubDirectories) {
            directories.push(subDir);
        }
    }

    private void workOnLocalFiles() {
        Collections.sort(localFiles, new CaseIgnoringStringComparator());

        for (String fileName : localFiles) {
            visitFile(fileName);
        }
    }

    private void visitFile(String fileName) {
        for (DirWalkerObserver observer : observers) {
            observer.visitFile(fileName);
        }
    }

    private void visitEmptyDir(String dirName) {
        for (DirWalkerObserver observer : observers) {
            observer.visitEmptyDir(dirName);
        }
    }

    private void visitNotEmptyDir(String dirName) {
        for (DirWalkerObserver observer : observers) {
            observer.visitNotEmptyDir(dirName);
        }
    }

}
