/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */
package com.queplix.core.client.controls.tree;

import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasFocus;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.queplix.core.client.app.vo.FocusMeta;
import com.queplix.core.client.common.StringUtil;
import com.queplix.core.client.common.ui.ButtonData;
import com.queplix.core.client.common.ui.IconButton;
import com.queplix.core.client.common.ui.TreeMod;

import java.util.HashMap;
import java.util.Map;

/**
 * QTreeView implementation
 *
 * @author: Vasily Mikhailitchenko
 * @since: 21 Sept 2006
 */
class QTreeViewImpl extends QTreeView
        implements QTreeModelImpl.ModelChangeListener,
        ClickListener {
    private final static String TREE_IMAGE_BASE = "tree/";

    private final static String CSS_STYLE = "QTree";
    private final static String STYLE_ROOT_HIDDEN = "QTree_invisibleRoot";

    private final static String IMAGE_FOCUS = TREE_IMAGE_BASE + "focus.gif";
    private final static String IMAGE_SUBFOCUS = TREE_IMAGE_BASE
            + "subfocus.gif";
    private final static String IMAGE_TAB = TREE_IMAGE_BASE + "tab.gif";
    private final static String IMAGE_FORM = TREE_IMAGE_BASE + "form.gif";
    private final static ButtonData EXPAND_ALL =
            new ButtonData(null, null, TREE_IMAGE_BASE + "expand_all.gif");
    private final static ButtonData COLLAPSE_ALL =
            new ButtonData(null, null, TREE_IMAGE_BASE + "collapse_all.gif");

    private QTreeModelImpl model;

    private TreeMod tree;
    private TreeItemMod rootItem;

    private QTreeNode rootNode;
    private Map indexMap = new HashMap();
    private Map nodeMap = new HashMap();
    private IconButton expandAllButton;
    private IconButton collapseAllButton;

    private FocusPanel fp;

    public QTreeViewImpl(QTreeModelImpl model) {
        this.model = model;

        model.addModelChangeListener(this);

        initGUI();
    }

    protected void onAttach() {
        super.onAttach();
    }

    /**
     * Initial GUI setup
     * <p/>
     * Mind the difference:
     * TreeNodes are QTreeNode objects
     * TreeItems are GUI elements (GWT)
     */
    private void initGUI() {
        tree = new TreeMod();
        tree.setHeight("100%");
        tree.setWidth("100%");
        rootItem = new TreeItemMod("");
        tree.setRootItem(rootItem);
        tree.addItem(rootItem);

        tree.addStyleName(CSS_STYLE);

        renderTree();
        tree.setImageBase(TREE_IMAGE_BASE);

        expandAllButton = new IconButton(EXPAND_ALL);
        expandAllButton.addClickListener(this);
        collapseAllButton = new IconButton(COLLAPSE_ALL);
        collapseAllButton.addClickListener(this);
        HorizontalPanel hp = new HorizontalPanel();
        hp.add(expandAllButton);
        hp.add(collapseAllButton);
        VerticalPanel panel = new VerticalPanel();
        fp = new FocusPanel();
        panel.add(hp);
        panel.add(tree);

        fp.addFocusListener(new FocusListener() {
            public void onLostFocus(Widget sender) {
                tree.setEventPreviewEnabled(false);
            }

            public void onFocus(Widget sender) {
                tree.setEventPreviewEnabled(true);
            }
        });

        panel.setWidth("100%");
        fp.setWidget(panel);
        fp.setWidth("100%");
        this.initWidget(fp);
    }

    /**
     * Recursively render children and subchildren
     * of a particular QTreeNode
     *
     * @param parentNode - The node, whose children we are going to render
     * @param parentItem - TreeItem corresponding to the node
     * @param level      - current rendering level, affects displayed default icon
     */

    private void renderChildren(QTreeNode parentNode, TreeItemMod parentItem,
                                int level) {
        for(int i = 0, n = parentNode.getChildCount(); i < n; i++) {
            QTreeNode childNode = (QTreeNode) parentNode.getChildren()
                    .elementAt(i);
            String imgSrc = "";

            if(null != childNode.getIcon()) {
                imgSrc = StringUtil.imgSrc(childNode.getIcon());
            } else {
                switch(level) {
                    case 0:
                        imgSrc = StringUtil.imgSrc(IMAGE_FOCUS);
                        break;
                    case 1:
                        imgSrc = StringUtil.imgSrc(IMAGE_SUBFOCUS);
                        break;
                    case 2:
                        imgSrc = StringUtil.imgSrc(IMAGE_TAB);
                        break;
                    case 3:
                        imgSrc = StringUtil.imgSrc(IMAGE_FORM);
                        break;
                }
            }
            TreeItemMod childItem = new TreeItemMod(
                    imgSrc + " " + childNode.getText());

            indexMap.put(childNode.getId(), childItem);
            nodeMap.put(childItem, childNode);

            // Don't render leaf forms with the same name as the parent tab
            if(level != 3 || (parentNode.getChildCount() > 1 || !parentNode
                    .getText().equalsIgnoreCase(childNode.getText()))) {
                parentItem.addItem(childItem);
            }
            renderChildren(childNode, childItem, level + 1);
        }
    }

    private void renderChildren(QTreeNode parentNode, TreeItemMod parentItem) {
        this.renderChildren(parentNode, parentItem, 0);
    }

    /**
     * Render the whole tree
     */
    private void renderTree() {
        rootNode = model.getRoot();
        rootItem.setText(rootNode.getText());
        rootItem.removeItems();
        renderChildren(rootNode, rootItem);
    }


    public void setRootNodeHidden(boolean isRootNodeHidden) {
        if(isRootNodeHidden) {
            tree.addStyleName(STYLE_ROOT_HIDDEN);
        } else {
            tree.removeStyleName(STYLE_ROOT_HIDDEN);
        }
    }

    /**
     * Fired by model when changes occur in it
     */
    public void onModelChanged() {
        this.renderTree();
    }

    public void refresh() {
        this.renderTree();
    }

    /**
     * Getter for root node.
     *
     * @return QTreeNode - tree root
     */
    public QTreeNode getRootNode() {
        return rootNode;
    }


    public void setHeight(String height) {
        super.setHeight(height);
        tree.setHeight(height);
    }

    public void setWidth(String width) {
        super.setWidth(width);
        tree.setWidth(width);
    }

    /**
     * Expand all TreeItems from root
     */
    public void expandAll() {
        rootItem.setState(true, false);
        expandChildren(rootItem);
    }

    /**
     * Expand (non-recursively) root item children
     * To be used in combination with
     * setRootNodeHidden(true)
     */
    public void expandRootChildren() {
        rootItem.setState(true);
    }

    /**
     * Collapse all TreeItems from root
     */
    public void collapseAll() {
        collapseChildren(rootItem);
    }

    /**
     * Recursively expand all Item's children
     * State change events are not fired
     *
     * @param item whose children are expanded
     */
    private void expandChildren(TreeItem item) {
        for(int i = 0, n = item.getChildCount(); i < n; i++) {
            TreeItem child = item.getChild(i);
            child.setState(true, false);
            if(child.getChildCount() > 0) {
                expandChildren(child);
            }
        }
    }

    /**
     * Recursively collapse all Item's children
     * State change events are not fired
     *
     * @param item whose children are collapsed
     */
    private void collapseChildren(TreeItem item) {
        for(int i = 0, n = item.getChildCount(); i < n; i++) {
            TreeItem child = item.getChild(i);
            child.setState(false, false);
            if(child.getChildCount() > 0) {
                collapseChildren(child);
            }
        }
    }

    /**
     * Getter for a tree.
     */
    public Tree getTree() {
        return tree;
    }

    void selectItem(FocusMeta.Index index) {
        TreeItem proposedForSelection = (TreeItem) indexMap.get(index);
        // check if the node is visible. If it's not, it's not attached to any parent
        if(proposedForSelection.getParentItem() != null) {
            TreeItem selectedItem = tree.getSelectedItem();
            if(!proposedForSelection.equals(selectedItem)) {
                if(selectedItem != null) {
                    selectedItem.setSelected(false);
                }
                tree.ensureItemVisible(proposedForSelection);
                tree.selectItem(
                        proposedForSelection); // MARK: listboxes collapse issue
            }
        } else {
            QTreeNode node = (QTreeNode) nodeMap.get(proposedForSelection);
            selectItem((FocusMeta.Index) node.getParentNode().getId());
        }
    }

    public void onClick(Widget sender) {
        if(sender == expandAllButton) {
            expandAll();
        } else if(sender == collapseAllButton) {
            collapseAll();
        }
    }

    private class TreeItemMod extends TreeItem {
        private FocusPanel innerPanel = new FocusPanel();

        public TreeItemMod(String itemText) {
            super();
            HTML html = new HTML(itemText);
            innerPanel.setWidget(html);
            setWidget(innerPanel);
            innerPanel.addFocusListener(new FocusListener() {
                public void onLostFocus(Widget sender) {
                    tree.setEventPreviewEnabled(false);
                }

                public void onFocus(Widget sender) {
                    tree.setEventPreviewEnabled(true);
                }
            });
        }

        public void setFocus(boolean focused) {
            innerPanel.setFocus(focused);
        }

        public HasFocus getFocusableWidget() {
            if(isAttached()) {
                return innerPanel;
            } else {
                return null;
            }
        }
    }
}
