package usda.weru.util;

import de.schlichtherle.truezip.file.TFile;
import java.io.FileFilter;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;

/**
 *
 * @author joelevin
 */
public class FileTreeNode extends DefaultMutableTreeNode implements TreeNode {

    private static final long serialVersionUID = 1L;

    /**
     *
     */
    protected TFile c_file;

    /**
     *
     */
    protected FileFilter c_filter;

    /**
     *
     */
    protected Comparator<TFile> c_comparator;

    /**
     *
     */
    protected String c_displayName;

    /**
     *
     */
    protected Map<TFile, FileTreeNode> c_childrenCache;
    private boolean c_createdChildren;

    /**
     *
     * @param file
     */
    public FileTreeNode(TFile file) {
        this(file, null);
    }

    /**
     *
     * @param file
     * @param filter
     */
    public FileTreeNode(TFile file, FileFilter filter) {
        this(file, filter, null);
    }

    /**
     *
     * @param file
     * @param filter
     * @param comparator
     */
    public FileTreeNode(TFile file, FileFilter filter, Comparator<TFile> comparator) {
        c_file = file;
        c_filter = filter;
        c_comparator = comparator != null ? comparator : new DefaultComparator();
    }

    /**
     *
     * @return
     */
    public TFile getFile() {
        return c_file;
    }

    /**
     *
     * @param file
     */
    public void setFile(TFile file) {
        if (file == null || !file.equals(c_file)) {
            c_file = file;
        }
        refreshChildren();
    }

    /**
     *
     */
    protected synchronized void initChildren() {
        if (!c_createdChildren) {
            refreshChildren();
        }
    }

    /**
     *
     */
    public void refreshChildren() {
        if (c_createdChildren) {
            removeAllChildren();
        }

        FileTreeNode[] newchildren = createChildren();
        for (FileTreeNode child : newchildren) {
            insert(child, children != null ? children.size() : 0);
        }

        c_createdChildren = true;

        //keep the cache clean.
        Enumeration<Object> e = Caster.<Enumeration<Object>>cast(children());
        while (e.hasMoreElements()) {
            Object o = e.nextElement();
            if (!c_childrenCache.containsValue(o)) {
                c_childrenCache.values().remove(o);
            }
        }

    }

    /**
     *
     * @return
     */
    public FileTreeNode[] getChildren() {
        FileTreeNode[] temp = new FileTreeNode[getChildCount()];
        for (int i = 0; i < getChildCount(); i++) {
            temp[i] = (FileTreeNode) getChildAt(i);
        }
        return temp;
    }

    private FileTreeNode[] createChildren() {

        TFile[] files = c_file != null ? c_file.listFiles(c_filter, c_file.getArchiveDetector()) : null;

        if (files == null) {
            files = new TFile[0];
        } else if (files.length == 1 && c_file.isArchive() && files[0].isDirectory()) {
            //hide a single directory in a zip file
            files = files[0].listFiles(c_filter, files[0].getArchiveDetector());
            if (files == null) {
                files = new TFile[0];
            }
        }

        if (c_comparator != null) {
            Arrays.sort(files, c_comparator);
        }

        FileTreeNode[] children = new FileTreeNode[files.length];

        for (int i = 0; i < files.length; i++) {
            children[i] = createChild(files[i], c_filter, c_comparator);
        }

        return children;
    }

    private FileTreeNode createChild(TFile file, FileFilter filter, Comparator<TFile> comparator) {
        if (c_childrenCache == null) {
            c_childrenCache = new HashMap<TFile, FileTreeNode>();
        }
        FileTreeNode child = c_childrenCache.get(file);
        if (child == null) {
            child = new FileTreeNode(file, c_filter, c_comparator);
            c_childrenCache.put(file, child);
        }
        return child;
    }

    /**
     *
     * @return
     */
    @Override
    public int getChildCount() {
        initChildren();
        return super.getChildCount();
    }

    /**
     *
     * @return
     */
    @Override
    public boolean getAllowsChildren() {
        return c_file != null ? c_file.isDirectory() : false;
    }

    /**
     *
     * @return
     */
    @Override
    public boolean isLeaf() {
        return !getAllowsChildren();
    }

    /**
     *
     * @return
     */
    public String getDisplayName() {
        return c_displayName;
    }

    /**
     *
     * @param name
     */
    public void setDisplayName(String name) {
        c_displayName = name;
    }

    /**
     *
     * @return
     */
    @Override
    public String toString() {
        if (c_displayName != null) {
            return c_displayName;
        } else if (c_file != null) {
            return c_file.getName();
        } else {
            return super.toString();
        }
    }

    private static class DefaultComparator implements Comparator<TFile>, Serializable {

        private static final long serialVersionUID = 1L;

        @Override
        public int compare(TFile a, TFile b) {
            if (a.isDirectory() == b.isDirectory()) {
                return a.compareTo(b);
            } else if (a.isDirectory()) {
                return -1;
            } else {
                return 1;
            }
        }

    }

    /**
     *
     * @param file
     * @return
     */
    public FileTreeNode getDecendantNode(TFile file) {
        if (c_file == null) {
            return null;
        }
        String currentPath = c_file.getCanOrAbsPath();
        String filePath = file.getCanOrAbsPath();
        if (filePath.startsWith(currentPath) && filePath.length() > currentPath.length()) {
            Stack<String> parts = fileParts(new TFile(filePath.substring(currentPath.length() + 1)));

            return findDecendantNode(this, parts);
        } else {
            return null;
        }
    }

    private static FileTreeNode findDecendantNode(FileTreeNode parent, Stack<String> parts) {
        @SuppressWarnings("unchecked")
        Enumeration<FileTreeNode> enumChildren = Caster.< Enumeration<FileTreeNode> >cast(parent.children());
        // the 'children()' method does not specify the type of enumeration that it returns
        String partToFind = parts.pop();
        while (enumChildren.hasMoreElements()) {
            FileTreeNode temp = enumChildren.nextElement();

            //does this node match the part?
            if (partToFind.equals(temp.getFile().getName())) {
                //part matches

                //is this the final part?
                if (parts.empty()) {
                    return temp;
                } else {
                    return findDecendantNode(temp, parts);
                }
            }
        }
        return null;
    }

    private static Stack<String> fileParts(TFile file) {
        Stack<String> parts = new Stack<String>();
        while (file != null) {
            parts.push(file.getName());

            file = file.getParentFile() != null ? new TFile(file.getParentFile()) : null;
        }
        return parts;
    }

}
