package com.jscape.filetransfer;

import com.jscape.util.Parameters;
import com.jscape.util.collections.Tree;

import java.io.File;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedList;

/**
 * @author Alex Usun
 */
public class DirectoryService {

    public static Tree<Directory<File>> asTree(File directory) {
        Parameters.assertTrue(directory.isDirectory(), "Specified pathname is not a directory.");

        DirectoryEntries<File> entries = list(directory);
        Tree<Directory<File>> directoryTree = Tree.treeFor(new Directory<File>(directory, entries.files));
        addSubDirectories(entries.directories, directoryTree);

        return directoryTree;
    }

    public static Tree<Directory<FileTransferRemoteFile>> asTree(String directoryPath, FileTransfer transfer)
            throws FileTransferException {
        String currentDirectory = transfer.getDir();
        transfer.setDir(directoryPath);

        DirectoryEntries<FileTransferRemoteFile> entries = list(transfer);
        FileTransferRemoteFile directory = new FileTransferRemoteFile(directoryPath, "", true, false, 0, new Date(), true, true);
        Tree<Directory<FileTransferRemoteFile>> directoryTree = Tree.treeFor(new Directory<FileTransferRemoteFile>(directory, entries.files));
        addSubDirectories(entries.directories, directoryTree, transfer);

        transfer.setDir(currentDirectory);

        return directoryTree;
    }

    private static DirectoryEntries<File> list(File directory) {
        Collection<File> directories = new LinkedList<File>();
        Collection<File> files = new LinkedList<File>();

        for (File entry : directory.listFiles()) {
            if (entry.isDirectory()) {
                directories.add(entry);
            } else if (entry.isFile()) {
                files.add(entry);
            }
        }

        return new DirectoryEntries<File>(directories, files);
    }

    private static DirectoryEntries<FileTransferRemoteFile> list(FileTransfer transfer)
            throws FileTransferException {
        Collection<FileTransferRemoteFile> directories = new LinkedList<FileTransferRemoteFile>();
        Collection<FileTransferRemoteFile> files = new LinkedList<FileTransferRemoteFile>();

        for (Enumeration e = transfer.getDirListing(); e.hasMoreElements(); ) {
            FileTransferRemoteFile entry = (FileTransferRemoteFile) e.nextElement();

            if (entry.isDirectory()) {
                directories.add(entry);
            } else {
                files.add(entry);
            }
        }

        return new DirectoryEntries<FileTransferRemoteFile>(directories, files);
    }

    private static void addSubDirectories(Collection<File> subDirectories, Tree<Directory<File>> directoryTree) {
        for (File subDirectory : subDirectories) {
            DirectoryEntries<File> entries = list(subDirectory);
            Tree<Directory<File>> leaf = directoryTree.addLeaf(new Directory<File>(subDirectory, entries.files));
            addSubDirectories(entries.directories, leaf);
        }
    }

    private static void addSubDirectories(Collection<FileTransferRemoteFile> subDirectories,
                                          Tree<Directory<FileTransferRemoteFile>> directoryTree,
                                          FileTransfer transfer)
            throws FileTransferException {
        for (FileTransferRemoteFile subDirectory : subDirectories) {
            transfer.setDir(subDirectory.getFilename());
            DirectoryEntries<FileTransferRemoteFile> entries = list(transfer);
            Tree<Directory<FileTransferRemoteFile>> leaf = directoryTree.addLeaf(new Directory<FileTransferRemoteFile>(subDirectory, entries.files));
            addSubDirectories(entries.directories, leaf, transfer);
            transfer.setDirUp();
        }
    }

    private DirectoryService() {
    }

    private static class DirectoryEntries<T> {

        public final Collection<T> directories;
        public final Collection<T> files;

        private DirectoryEntries(Collection<T> directories, Collection<T> files) {
            this.directories = directories;
            this.files = files;
        }

        @Override
        public String toString() {
            return "DirectoryEntries {"
                    + "directories=" + this.directories
                    + ", files=" + this.files
                    + '}';
        }
    }

    public static class Directory<T> {

        public final T path;
        public final Collection<T> files;

        public Directory(T path, Collection<T> files) {
            this.path = path;
            this.files = files;
        }

        @Override
        public String toString() {
            return "Directory {"
                    + "path=" + this.path
                    + ", files=" + this.files
                    + '}';
        }
    }

}
