/*
 * Zettelkasten - nach Luhmann
 * Copyright (C) 2001-2015 by Daniel Lüdecke (http://www.danielluedecke.de)
 * 
 * Homepage: http://zettelkasten.danielluedecke.de
 * 
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation; either version 3 of 
 * the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program;
 * if not, see <http://www.gnu.org/licenses/>.
 * 
 * 
 * Dieses Programm ist freie Software. Sie können es unter den Bedingungen der GNU
 * General Public License, wie von der Free Software Foundation veröffentlicht, weitergeben
 * und/oder modifizieren, entweder gemäß Version 3 der Lizenz oder (wenn Sie möchten)
 * jeder späteren Version.
 * 
 * Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, daß es Ihnen von Nutzen sein 
 * wird, aber OHNE IRGENDEINE GARANTIE, sogar ohne die implizite Garantie der MARKTREIFE oder 
 * der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Details finden Sie in der 
 * GNU General Public License.
 * 
 * Sie sollten ein Exemplar der GNU General Public License zusammen mit diesem Programm 
 * erhalten haben. Falls nicht, siehe <http://www.gnu.org/licenses/>.
 */
package de.danielluedecke.zettelkasten.util;

import de.danielluedecke.zettelkasten.database.Daten;
import de.danielluedecke.zettelkasten.util.misc.TreeUserObject;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

/**
 *
 * @author Luedeke
 */
public class TreeUtil {

    private static final List<String> collapsedNodes = new ArrayList<>();

    private static int expandLevel = -1;

    /**
     * Sets the level to which sub branches a tree will be expanded.
     *
     * @param level the "depth" of the tree expansion.
     */
    public static void setExpandLevel(int level) {
        expandLevel = level;
    }

    public static int getExpandLevel() {
        return expandLevel;
    }

    /**
     * This method extracts a node's ID.
     *
     * @param node the node where we want to extract the ID
     * @return the ID of the node's name (userobject) as string, or {@code null}
     * if an error occured or nothing was found.
     */
    public static boolean nodeIsRoot(DefaultMutableTreeNode node) {
        if (node != null) {
            TreeUserObject userObject = (TreeUserObject) node.getUserObject();
            return userObject.getId().equals(Constants.ROOT_ID_NAME);
        }
        return false;
    }

    /**
     * This method returns the text of a node which is used in the DesktopFrame.
     * This node usually has an id in its text. This method cuts off this id, so
     * only the "cleaned" node-text wil be returned.
     *
     * @param node the node which text should be retrieved
     * @return a "cleaned" node-text with the id truncated, or {@code null} if
     * an error occured.
     */
    public static String getNodeText(DefaultMutableTreeNode node) {
        String uebertext = null;
        if (node != null) {
            TreeUserObject userObject = (TreeUserObject) node.getUserObject();
            uebertext = userObject.toString();
        }
        return uebertext;
    }

    /**
     * This method extracts a node's ID.
     *
     * @param node the node where we want to extract the ID
     * @return the ID of the node's name (userobject) as string, or {@code null}
     * if an error occured or nothing was found.
     */
    public static String getNodeID(DefaultMutableTreeNode node) {
        if (node != null) {
            TreeUserObject userObject = (TreeUserObject) node.getUserObject();
            return userObject.getId();
        }
        return null;
    }

    /**
     * This method extracts the timestamp-id of a node's name.
     *
     * @param node the node where we want to extract the timestamp-id
     * @return the timestamp-id of the node's name (userobject) as string, or
     * {@code null} if an error occured or nothing was found.
     */
    public static String getNodeTimestamp(DefaultMutableTreeNode node) {
        if (node != null) {
            TreeUserObject userObject = (TreeUserObject) node.getUserObject();
            return userObject.getId();
        }
        return null;
    }

    /**
     * This method extracts an entry's number from a node's text (userobject)
     * and returns it as integer-value
     *
     * @param node the node from which the entry-number should be extracted
     * @return the entry-number, or -1 if an error occured.
     */
    public static int extractEntryNumberFromNode(DefaultMutableTreeNode node) {
        TreeUserObject userObject = (TreeUserObject) node.getUserObject();
        String entrynumber = userObject.getNr();
        if (entrynumber != null && !entrynumber.isEmpty()) {
            try {
                return Integer.parseInt(entrynumber);
            } catch (NumberFormatException e) {
                return -1;
            }
        }
        return -1;
    }

    private static void expandAllTrees(TreePath parent, JTree tree) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) parent.getLastPathComponent();
        if (node.getChildCount() >= 0) {
            for (Enumeration e = node.children(); e.hasMoreElements();) {
                DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement();
                TreePath path = parent.pathByAddingChild(n);
                expandAllTrees(path, tree);
            }
        }
        // retrieve treenode user object
        TreeUserObject userObject = (TreeUserObject) node.getUserObject();
        // check whether deepest level is reached.
        if ((expandLevel != -1 && node.getLevel() < expandLevel) || -1 == expandLevel) {
            // check whether treenode-id is in the list of collapsed items
            if (userObject != null && collapsedNodes.contains(userObject.getId())) {
                // if yes, collapse treenode
                tree.collapsePath(parent);
            } else {
                // else expand it
                tree.expandPath(parent);
            }
        } else {
            tree.collapsePath(parent);
        }
    }

    /**
     * If expand is true, expands all nodes in the tree. Otherwise, collapses
     * all nodes in the tree.
     *
     * @param tree
     */
    public static void expandAllTrees(JTree tree) {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
        expandAllTrees(new TreePath(root), tree);
    }

    private static void expandAllTrees(TreePath parent, boolean expand, JTree tree) {
        TreeNode node = (TreeNode) parent.getLastPathComponent();
        if (node.getChildCount() >= 0) {
            for (Enumeration e = node.children(); e.hasMoreElements();) {
                TreeNode n = (TreeNode) e.nextElement();
                TreePath path = parent.pathByAddingChild(n);
                expandAllTrees(path, expand, tree);
            }
        }
        if (expand) {
            tree.expandPath(parent);
        } else {
            tree.collapsePath(parent);
        }
    }

    /**
     * If expand is true, expands all nodes in the tree. Otherwise, collapses
     * all nodes in the tree.
     *
     * @param expand
     * @param tree
     */
    public static void expandAllTrees(boolean expand, JTree tree) {
        TreeNode root = (TreeNode) tree.getModel().getRoot();
        expandAllTrees(new TreePath(root), expand, tree);
    }

    private static void retrieveCollapsedNodes(TreePath parent, JTree tree) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) parent.getLastPathComponent();
        if (node.getChildCount() >= 0) {
            for (Enumeration e = node.children(); e.hasMoreElements();) {
                DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement();
                TreePath path = parent.pathByAddingChild(n);
                retrieveCollapsedNodes(path, tree);
            }
        }
        // retrieve treenode user object
        TreeUserObject userObject = (TreeUserObject) node.getUserObject();
        // check for valid value
        if (userObject != null && userObject.isCollapsed()) {
            // if treenode is collapsed, remember state
            collapsedNodes.add(userObject.getId());
        }
    }

    /**
     * If expand is true, expands all nodes in the tree. Otherwise, collapses
     * all nodes in the tree.
     *
     * @param tree
     * @return
     */
    public static List<String> retrieveCollapsedNodes(JTree tree) {
        // clear collapsed nodes
        collapsedNodes.clear();
        // if we have no tree, do nothing
        if (null == tree) {
            return null;
        }
        // get root
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
        // if we have no root, return
        if (null == root) {
            return null;
        }
        // else retrieve all collapsed nodes and save their ID's in a list
        retrieveCollapsedNodes(new TreePath(root), tree);
        return collapsedNodes;
    }

    public static String retrieveNodeTitle(Daten data, boolean showNumber, String nr) {
        StringBuilder sb = new StringBuilder("");
        String zettelTitle = data.getZettelTitle(Integer.parseInt(nr));
        if (showNumber || zettelTitle.isEmpty()) {
            sb.append(nr);
            if (!zettelTitle.isEmpty()) {
                sb.append(": ");
            }
        }
        if (!zettelTitle.isEmpty()) {
            sb.append(zettelTitle);
        }
        return sb.toString();
    }

    /**
     * Clears the array that saves all currently collapsed nodes from the
     * jLuhmannTree, so the tree cabn be fully expanded.
     */
    public static void resetCollapsedNodes() {
        // clear collapsed nodes
        collapsedNodes.clear();
    }

    /**
     * This method selects the first entry in a jTree that start with the text
     * that is entered in the filter-textfield.
     *
     * @param tree the jTree where the item should be selected
     * @param textfield the related filtertextfield that contains the user-input
     */
    public static void selectByTyping(javax.swing.JTree tree, javax.swing.JTextField textfield) {
        DefaultTreeModel dtm = (DefaultTreeModel) tree.getModel();
        MutableTreeNode root = (MutableTreeNode) dtm.getRoot();
        String text = textfield.getText().toLowerCase();
        if (root != null && !text.isEmpty()) {
            for (int cnt = 0; cnt < dtm.getChildCount(root); cnt++) {
                MutableTreeNode child = (MutableTreeNode) dtm.getChild(root, cnt);
                String childtext = child.toString().toLowerCase();
                if (childtext.startsWith(text)) {
                    TreePath tp = new TreePath(root);
                    tp = tp.pathByAddingChild(child);
                    tree.setSelectionPath(tp);
                    tree.scrollPathToVisible(tp);
                    return;
                }
            }
        }
    }
}