package usda.weru.util.tree;

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.LinkedList;
import java.util.List;
import java.util.Stack;
import javax.swing.tree.TreeNode;


public class WepsTreeNodeFile extends WepsTreeNode {

    private static final long serialVersionUID = 1L;

    protected TFile file;
    protected FileFilter filter;
    protected Comparator<TFile> comparator;


    public WepsTreeNodeFile(TFile file) {
        this(file, null);
    }

    public WepsTreeNodeFile(TFile file, FileFilter filter) {
        this(file, filter, null);
    }

    public WepsTreeNodeFile(TFile file, FileFilter filter, Comparator<TFile> comparator) {
        this.file = file;
        this.filter = filter;
        this.comparator = comparator != null ? comparator : new DefaultComparator();
        
        // calling this checks AND sets / intialize the flag variable
        getAllowsChildren();
    }

    /**
     * Returns the TFile for this node
     * @return 
     */
    @Override
    public TFile getNodeData() {
        return file;
    }
    
    @Override
    public Object getUserObject() {
        return file;
    }

    public void setFile(TFile file) {
        if (file == null || !file.equals(this.file)) {
            this.file = file;
        }
        refreshChildren();
    }


    @Override
    protected WepsTreeNodeFile[] createChildren() {
        super.createChildren();

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

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

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

        WepsTreeNodeFile[] childrenRet = new WepsTreeNodeFile[files.length];

        for (int i = 0; i < files.length; i++) {
            childrenRet[i] = createChild(files[i], filter, comparator);
        }

        return childrenRet;
    }

    protected WepsTreeNodeFile createChild(TFile file, FileFilter filter, Comparator<TFile> comparator) {
        WepsTreeNodeFile child = (WepsTreeNodeFile)childCacheGet(file.getAbsolutePath());
        if (child == null) {
            child = new WepsTreeNodeFile(file, this.filter, this.comparator);
            childCachePut(file.getAbsolutePath(), child);
        }
        return child;
    }


    @Override
    public boolean getAllowsChildren() {
        allowsChildren = file != null ? file.isDirectory() : false;
        return allowsChildren;
    }

    @Override
    public String toString() {
        if (displayName != null) {
            return displayName;
        } else if (file != null) {
            return 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;
            }
        }

    }

    public WepsTreeNodeFile getDecendantNode(TFile file) {
        if (this.file == null) {
            return null;
        }
        String currentPath = this.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 WepsTreeNodeFile findDecendantNode(WepsTreeNodeFile parent, Stack<String> parts) {
        @SuppressWarnings("unchecked")
        Enumeration<? extends TreeNode> enumChildren = (Enumeration<? extends TreeNode>) parent.children();
        // the 'children()' method does not specify the type of enumeration that it returns
        String partToFind = parts.pop();
        while (enumChildren.hasMoreElements()) {
            Object o = enumChildren.nextElement();
            if (o instanceof WepsTreeNodeFile) {
                WepsTreeNodeFile temp = (WepsTreeNodeFile)o;

                //does this node match the part?
                if (partToFind.equals(temp.file.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;
    }

    
    @Override
    public boolean updateSubtree() {

        List<TFile> nodeFileChildren = new LinkedList<>();
        final List<WepsTreeNodeFile> removed = new LinkedList<>();

        for (WepsTreeNode child : getChildren()) {
            if (child instanceof WepsTreeNodeFile) {
                if (!((WepsTreeNodeFile)child).file.exists()) {
                    removed.add((WepsTreeNodeFile)child);
                } else {
                    nodeFileChildren.add(((WepsTreeNodeFile)child).file);
                }
            }
        }

        List<TFile> added = new LinkedList<>();
        TFile[] listFiles = file.listFiles();
        listFiles = listFiles != null ? listFiles : new TFile[0];
        for (TFile lfile : listFiles) {
            if (!nodeFileChildren.contains(lfile)) {
                added.add(lfile);
            }
        }


        for (WepsTreeNode child : getChildren()) {
            if (child instanceof WepsTreeNodeFile) {
                if (!removed.contains((WepsTreeNodeFile)child)) {
                    ((WepsTreeNodeFile)child).updateSubtree();
                }
            }
        }
        
        return (!removed.isEmpty() || !added.isEmpty());

    }

}
