/* Copyright (C) 2005-2011 Fabio Riccardi */

package com.lightcrafts.ui.browser.folders;

import com.lightcrafts.platform.Platform;
import com.lightcrafts.utils.directory.DirectoryMonitor;
import com.lightcrafts.utils.file.FileUtil;

import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * A TreeNode representing a folder.
 */
class FolderTreeNode implements TreeNode {

    private final static FileSystemView FileSystemView =
        Platform.getPlatform().getFileSystemView();

    private List<FolderTreeNode> children;

    private FolderTreeNode parent;

    private NodeFileIndex index;  // Maintain the tree-wide index

    private DirectoryMonitor monitor;   // Update the watched folder list

    private File file;            // Determines the icon, node identity

    private File resolvedFile;    // For getFile(), indexing

    private Icon icon;    // FileSystemView icon of "file" (not "resolvedFile")

    private Boolean isLeaf;     // Cached isLeaf(), for efficiency

    private String name;        // Cached toString(), for efficiency

    private boolean isDropTarget;   // Gets a highlight in the cell renderer

    static FolderTreeNode createRoot() {
        NodeFileIndex index = new NodeFileIndex();
        DirectoryMonitor monitor =
            Platform.getPlatform().getDirectoryMonitor();
        if (Platform.getType() == Platform.MacOSX) {
            return new MacOSXRootTreeNode(index, monitor);
        }
        else {
            File[] roots = FileSystemView.getRoots();
            return new FolderTreeNode(roots[0], null, index, monitor);
        }
    }

    FolderTreeNode(
        File file,
        FolderTreeNode parent,
        NodeFileIndex index,
        DirectoryMonitor monitor
    ) {
        this.file = file;
        this.parent = parent;
        this.index = index;
        this.monitor = monitor;
        resolvedFile = FileUtil.resolveAliasFile(this.file);
        try {
            File canonicalFile = resolvedFile.getCanonicalFile();
            resolvedFile = canonicalFile;
        }
        catch (IOException e) {
            // Accept the non-canonical instance
            e.printStackTrace();
        }
        index.add(this);
        monitor.addDirectory(resolvedFile);
    }

    void setIsDropTarget(boolean isTarget) {
        isDropTarget = isTarget;
    }

    boolean isDropTarget() {
        return isDropTarget;
    }

    public Enumeration<FolderTreeNode> children() {
        final Iterator<FolderTreeNode> i = getChildren().iterator();
        return new Enumeration<FolderTreeNode>() {
            public boolean hasMoreElements() {
                return i.hasNext();
            }
            public FolderTreeNode nextElement() {
                return i.next();
            }
        };
    }

    public boolean equals(Object o) {
        if (! (o instanceof FolderTreeNode)) {
            return false;
        }
        FolderTreeNode node = (FolderTreeNode) o;
        return file.equals(node.file);
    }

    public boolean getAllowsChildren() {
        return !isLeaf();
    }

    public TreeNode getChildAt( int index ) {
        return getChildren().get( index );
    }

    public int getChildCount() {
        return getChildren().size();
    }

    public int getIndex(TreeNode node) {
        return getChildren().indexOf(node);
    }

    public TreeNode getParent() {
        return parent;
    }

    public int hashCode() {
        return file.hashCode();
    }

    public boolean isLeaf() {
        if (isLeaf == null) {
            isLeaf = isLeaf(file);
        }
        return isLeaf;
    }

    /**
     * Returns the name of the folder this node represents as it would be
     * displayed in the native file browser of the OS.
     */
    public String toString() {
        if (name == null) {
            name = Platform.getPlatform().getDisplayNameOf( file );
        }
        return name;
    }

    /**
     * Gets the child node having the given name.  This is needed for
     * reconstructing tree paths from their serialized form.
     */
    FolderTreeNode getChildByName(String name) {
        for (FolderTreeNode child : getChildren()) {
            if (name.equals(child.toString())) {
                return child;
            }
        }
        return null;
    }

    File getFile() {
        return resolvedFile;
    }

    /**
     * Gets the icon of the folder this node represents as it would be
     * displayed in the native file browser of the OS.
     *
     * @return Returns said icon.
     */
    Icon getIcon() {
        if ((icon == null) && (file != null)) {
            try {
                icon = FileSystemView.getSystemIcon(file);
            }
            catch ( Throwable t ) {
                // ignore
            }
        }
        return icon;
    }

    TreePath getTreePath() {
        ArrayList<TreeNode> path = new ArrayList<TreeNode>();
        //
        // Construct the TreePath in reverse, i.e., from the current node back
        // to the root, because it's easier.
        //
        for ( TreeNode node = this; node != null; node = node.getParent() )
            path.add( 0, node );
        return new TreePath( path.toArray() );
    }

    /**
     * Checks whether the file this node represents has a special icon, for
     * the Windows tree cell renderer.
     */
    boolean hasSpecialIcon() {
        return  FileSystemView.isDrive(file) ||
                FileSystemView.isFileSystem(file) ||
                FileSystemView.isRoot(file);
    }

    // Compute (or recompute) the chilren of this node.  Useful in the TreeNode
    // methods, and also when folder modifications are detected.
    void updateChildren() {
        if (children != null) {
            for (FolderTreeNode child : children) {
                // don't bother recursing; leak a little
                monitor.removeDirectory(child.resolvedFile);
                index.remove(child);
                child.parent = null;
            }
        }
        children = new ArrayList<FolderTreeNode>();

        File[] files = FileUtil.listFiles(
            resolvedFile, FolderFilter.INSTANCE, false
        );
        if (files != null && files.length > 0) {
            Arrays.sort(files);
            for (File file : files) {
                children.add(new FolderTreeNode(file, this, index, monitor));
            }
        }
        Collections.sort(children, FolderTreeNodeComparator.INSTANCE);
    }

    NodeFileIndex getIndex() {
        return index;
    }

    DirectoryMonitor getDirectoryMonitor() {
        return monitor;
    }

    List<FolderTreeNode> getChildren() {
        if (children == null) {
            updateChildren();
        }
        return children;
    }

    /**
     * Checks whether the given File is a leaf node.  For our purposes, a leaf
     * node must be a file and not an alias to a folder.
     */
    private static boolean isLeaf(File file) {
        if (FileSystemView.isDrive(file )         ||
            FileSystemView.isFileSystemRoot(file) ||
            FileSystemView.isFloppyDrive(file)    ||
            FileSystemView.isRoot(file)
        ) {
            return false;
        }
        file = FileUtil.isFolder( file );
        return file == null;
/*
        if ( file == null )
            return true;
        if ( file instanceof SmartFolder ) {
            //
            // Always consider a SmartFolder to be a non-leaf node because
            // determining whether it contains subfolders via listFiles() may
            // be expensive.
            //
            return false;
        }
        return !FileUtil.containsAtLeastOne( file, FolderFilter.INSTANCE );
*/
    }
}
/* vim:set et sw=4 ts=4: */
