/*
 * Copyright (c) 2006-2015 DMDirc Developers
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.dmdirc.addons.ui_swing.framemanager.tree;

import com.dmdirc.GlobalWindow;
import com.dmdirc.addons.ui_swing.WindowComparator;
import com.dmdirc.interfaces.config.AggregateConfigProvider;

import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;

/**
 * A simple sorted tree data model based on DefaultTreeModel.
 */
public class TreeViewModel extends DefaultTreeModel {

    /** A version number for this class. */
    private static final long serialVersionUID = 1;
    /** Window comparator. */
    private final WindowComparator comparator;
    /** Configuration provider to read settings from. */
    private final AggregateConfigProvider globalConfig;

    /**
     * Creates a tree in which any node can have children.
     *
     * @param globalConfig The configuration provider to read settings from.
     * @param root         a TreeNode object that is the root of the tree.
     */
    public TreeViewModel(final AggregateConfigProvider globalConfig, final TreeNode root) {
        super(root, false);

        this.globalConfig = globalConfig;
        comparator = new WindowComparator();
    }

    /**
     * Inserts a new node into the tree and fires the appropriate events.
     *
     * @param newChild child to be added.
     * @param parent   parent child is to be added too.
     */
    public final void insertNodeInto(final TreeViewNode newChild, final MutableTreeNode parent) {
        insertNodeInto(newChild, parent, getIndex(newChild, parent));
    }

    /**
     * Compares the new child with the existing children or parent to decide where it needs to be
     * inserted.
     *
     * @param newChild new node to be inserted.
     * @param parent   node the new node will be inserted into.
     *
     * @return index where new node is to be inserted.
     */
    private int getIndex(final TreeViewNode newChild, final TreeNode parent) {
        if (newChild.getWindow().getContainer() instanceof GlobalWindow) {
            return 0;
        }

        if (parent.equals(root) && !globalConfig.getOptionBool("ui", "sortrootwindows")) {
            return parent.getChildCount();
        }

        if (globalConfig.getOptionBool("ui", "sortchildwindows")) {
            for (int i = 0; i < parent.getChildCount(); i++) {
                final TreeViewNode child = (TreeViewNode) parent.getChildAt(i);
                if (sortBefore(newChild, child)
                        || !sortAfter(newChild, child)
                        && newChild.getUserObject().toString().compareToIgnoreCase(
                        child.getUserObject().toString()) < 0) {
                    return i;
                }
            }
        }

        return parent.getChildCount();
    }

    /**
     * Compares the types of the specified nodes' objects to see if the new node should be sorted
     * before the other.
     *
     * @param newChild The new child to be tested
     * @param child    The existing child that it's being tested against
     *
     * @return True iff newChild should be sorted before child
     */
    private boolean sortBefore(final TreeViewNode newChild, final TreeViewNode child) {
        return comparator.compare(newChild.getWindow(), child.
                getWindow()) <= -1;
    }

    /**
     * Compares the types of the specified nodes' objects to see if the new node should be sorted
     * after the other.
     *
     * @param newChild The new child to be tested
     * @param child    The existing child that it's being tested against
     *
     * @return True iff newChild should be sorted before child
     */
    private boolean sortAfter(final TreeViewNode newChild, final TreeViewNode child) {
        return comparator.compare(newChild.getWindow(), child.
                getWindow()) >= 1;
    }

    /**
     * Returns the root node for this model.
     *
     * @return Root node
     */
    public TreeViewNode getRootNode() {
        return (TreeViewNode) getRoot();
    }

}
