package de.renew.navigator.gui;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import de.renew.navigator.models.Directory;
import de.renew.navigator.models.TreeElement;

/**
 * The FileTreeNode class is a basic TreeNode implementation for Files.
 * It loads its children on request, can reload the children but doesn't offer methods to add or remove Children directly.
 * For using the DefaultTreeModel, the MutableTreeNode interface has to be implemented.
 * This is done by the MutableFileTreeNode class which is extending this class.
 * Cause this class is loading its children as FileTreeNodes, the protected loadChildren method has to be overwritten on extension of this class.
 *
 * @author Hannes Ahrens (4ahrens)
 * @version March 2009
 */
public class FileTreeNode implements MutableTreeNode {
    private final Vector<TreeNode> _children;
    private final TreeElement _model;
    private TreeNode _parent = null;

    /**
     * This constructor creates a root node.
     *
     * @param treeElement the file to be contained
     */
    public FileTreeNode(TreeElement treeElement) {
        assert (treeElement != null);
        this._children = new Vector<>();
        this._model = treeElement;
    }

    /**
     * @see javax.swing.tree.TreeNode#children()
     */
    @Override
    public Enumeration<TreeNode> children() {
        return _children.elements();
    }

    /**
     * Only directories allow children in a file system.
     * @see javax.swing.tree.TreeNode#getAllowsChildren()
     *
     * @return is contained file a directory?
     */
    @Override
    public boolean getAllowsChildren() {
        return _model instanceof Directory;
    }

    /**
     * @see javax.swing.tree.TreeNode#getChildAt(int)
     */
    @Override
    public TreeNode getChildAt(int childIndex) {
        return _children.elementAt(childIndex);
    }

    /**
     * @see javax.swing.tree.TreeNode#getChildCount()
     */
    @Override
    public int getChildCount() {
        return _children.size();
    }

    /**
     * @see javax.swing.tree.TreeNode#getIndex(javax.swing.tree.TreeNode)
     */
    @Override
    public int getIndex(TreeNode node) {
        return _children.indexOf(node);
    }

    /**
     * @see javax.swing.tree.TreeNode#getParent()
     */
    @Override
    public TreeNode getParent() {
        return _parent;
    }

    /**
     * @see javax.swing.tree.MutableTreeNode#insert(javax.swing.tree.MutableTreeNode, int)
     */
    @Override
    public void insert(MutableTreeNode child, int index) {
        child.setParent(this);
        _children.add(index, child);
    }

    /**
     * Adds a tree node as child.
     *
     * @param child the child node to add
     */
    public void add(MutableTreeNode child) {
        child.setParent(this);
        _children.add(child);
    }

    /**
     * @see javax.swing.tree.MutableTreeNode#remove(int)
     */
    @Override
    public void remove(int index) {
        _children.remove(index);
    }

    /**
     * @see javax.swing.tree.MutableTreeNode#remove(javax.swing.tree.MutableTreeNode)
     */
    @Override
    public void remove(MutableTreeNode node) {
        _children.remove(node);
    }

    /**
     * @see javax.swing.tree.MutableTreeNode#removeFromParent()
     */
    @Override
    public void removeFromParent() {
        if (_parent != null && _parent instanceof MutableTreeNode) {
            ((MutableTreeNode) _parent).remove(this);
        }
    }

    /**
     * @see javax.swing.tree.MutableTreeNode#setParent(javax.swing.tree.MutableTreeNode)
     */
    @Override
    public void setParent(MutableTreeNode newParent) {
        _parent = newParent;
    }

    /**
     * This method is currently doing nothing.
     * @see javax.swing.tree.MutableTreeNode#setUserObject(java.lang.Object)
     */
    @Override
    public void setUserObject(Object object) {}

    /**
     * This method is giving the tree path from the root node.
     * @see javax.swing.tree.DefaultMutableTreeNode#getPath()
     *
     * @return all parent TreeNodes + this
     */
    public TreePath getPath() {
        LinkedList<TreeNode> path = new LinkedList<>();
        TreeNode node = this;

        while (node != null) {
            path.addFirst(node);
            node = node.getParent();
        }

        return new TreePath(path.toArray(new TreeNode[0]));
    }

    /**
     * All non directory files are leafs.
     * @see javax.swing.tree.TreeNode#isLeaf()
     *
     * @return is no directory
     */
    @Override
    public boolean isLeaf() {
        return !getAllowsChildren() || _children.isEmpty();
    }

    /**
     * @return the contained file of this class
     */
    public File getFile() {
        return _model.getFile();
    }

    /**
     * The toString method gets used by the JTree to determine the text for representing a node.
     * Therefore, the filename gets returned.
     *
     * @return the name of this node
     */
    @Override
    public String toString() {
        return _model.getName();
    }

    /**
     * @return a list of all opened directories.
     */
    public List<FileTreeNode> getOpenedDirectories() {
        if (!(_model instanceof Directory directory)) {
            return Collections.emptyList();
        }
        if (!directory.isOpened()) {
            return Collections.emptyList();
        }

        List<FileTreeNode> result = new ArrayList<>();
        for (TreeNode child : _children) {
            if (child instanceof FileTreeNode) {
                result.addAll(((FileTreeNode) child).getOpenedDirectories());
            }
        }
        result.add(this);

        return result;
    }

    /**
     * @return the model of this node
     */
    public TreeElement getModel() {
        return _model;
    }

    /**
     * @return The tooltip text for this node.
     */
    public String getToolTip() {
        return _model.getFile().getAbsolutePath();
    }

    /**
     * @return the children of this node
     */
    public Vector<TreeNode> getChildren() {
        return _children;
    }

    /**
     * @return the parent node of this node
     */
    public TreeNode getParentNode() {
        return _parent;
    }
}