/*
@VaadinApache2LicenseForJavaFiles@
 */
package com.vaadin.terminal.gwt.client.ui.tree;

import java.util.Iterator;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.AbstractFieldState;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.TooltipInfo;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector;
import com.vaadin.terminal.gwt.client.ui.Connect;
import com.vaadin.terminal.gwt.client.ui.tree.VTree.TreeNode;
import com.vaadin.ui.Tree;

@Connect(Tree.class)
public class TreeConnector extends AbstractComponentConnector implements
        Paintable {

    public static final String ATTRIBUTE_NODE_STYLE = "style";
    public static final String ATTRIBUTE_NODE_CAPTION = "caption";
    public static final String ATTRIBUTE_NODE_ICON = "icon";

    public static final String ATTRIBUTE_ACTION_CAPTION = "caption";
    public static final String ATTRIBUTE_ACTION_ICON = ATTRIBUTE_NODE_ICON;

    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
        if (!isRealUpdate(uidl)) {
            return;
        }

        getWidget().rendering = true;

        getWidget().client = client;

        if (uidl.hasAttribute("partialUpdate")) {
            handleUpdate(uidl);
            getWidget().rendering = false;
            return;
        }

        getWidget().paintableId = uidl.getId();

        getWidget().immediate = getState().isImmediate();

        getWidget().disabled = !isEnabled();
        getWidget().readonly = isReadOnly();

        getWidget().dragMode = uidl.hasAttribute("dragMode") ? uidl
                .getIntAttribute("dragMode") : 0;

        getWidget().isNullSelectionAllowed = uidl
                .getBooleanAttribute("nullselect");

        if (uidl.hasAttribute("alb")) {
            getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb");
        }

        getWidget().body.clear();
        // clear out any references to nodes that no longer are attached
        getWidget().clearNodeToKeyMap();
        TreeNode childTree = null;
        UIDL childUidl = null;
        for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
            childUidl = (UIDL) i.next();
            if ("actions".equals(childUidl.getTag())) {
                updateActionMap(childUidl);
                continue;
            } else if ("-ac".equals(childUidl.getTag())) {
                getWidget().updateDropHandler(childUidl);
                continue;
            }
            childTree = getWidget().new TreeNode();
            updateNodeFromUIDL(childTree, childUidl);
            getWidget().body.add(childTree);
            childTree.addStyleDependentName("root");
            childTree.childNodeContainer.addStyleDependentName("root");
        }
        if (childTree != null && childUidl != null) {
            boolean leaf = !childUidl.getTag().equals("node");
            childTree.addStyleDependentName(leaf ? "leaf-last" : "last");
            childTree.childNodeContainer.addStyleDependentName("last");
        }
        final String selectMode = uidl.getStringAttribute("selectmode");
        getWidget().selectable = !"none".equals(selectMode);
        getWidget().isMultiselect = "multi".equals(selectMode);

        if (getWidget().isMultiselect) {
            if (BrowserInfo.get().isTouchDevice()) {
                // Always use the simple mode for touch devices that do not have
                // shift/ctrl keys (#8595)
                getWidget().multiSelectMode = VTree.MULTISELECT_MODE_SIMPLE;
            } else {
                getWidget().multiSelectMode = uidl
                        .getIntAttribute("multiselectmode");
            }
        }

        getWidget().selectedIds = uidl.getStringArrayVariableAsSet("selected");

        // Update lastSelection and focusedNode to point to *actual* nodes again
        // after the old ones have been cleared from the body. This fixes focus
        // and keyboard navigation issues as described in #7057 and other
        // tickets.
        if (getWidget().lastSelection != null) {
            getWidget().lastSelection = getWidget().getNodeByKey(
                    getWidget().lastSelection.key);
        }
        if (getWidget().focusedNode != null) {
            getWidget().setFocusedNode(
                    getWidget().getNodeByKey(getWidget().focusedNode.key));
        }

        if (getWidget().lastSelection == null
                && getWidget().focusedNode == null
                && !getWidget().selectedIds.isEmpty()) {
            getWidget().setFocusedNode(
                    getWidget().getNodeByKey(
                            getWidget().selectedIds.iterator().next()));
            getWidget().focusedNode.setFocused(false);
        }

        getWidget().rendering = false;

    }

    @Override
    protected Widget createWidget() {
        return GWT.create(VTree.class);
    }

    @Override
    public VTree getWidget() {
        return (VTree) super.getWidget();
    }

    private void handleUpdate(UIDL uidl) {
        final TreeNode rootNode = getWidget().getNodeByKey(
                uidl.getStringAttribute("rootKey"));
        if (rootNode != null) {
            if (!rootNode.getState()) {
                // expanding node happened server side
                rootNode.setState(true, false);
            }
            renderChildNodes(rootNode, (Iterator) uidl.getChildIterator());
        }
    }

    /**
     * Registers action for the root and also for individual nodes
     * 
     * @param uidl
     */
    private void updateActionMap(UIDL uidl) {
        final Iterator<?> it = uidl.getChildIterator();
        while (it.hasNext()) {
            final UIDL action = (UIDL) it.next();
            final String key = action.getStringAttribute("key");
            final String caption = action
                    .getStringAttribute(ATTRIBUTE_ACTION_CAPTION);
            String iconUrl = null;
            if (action.hasAttribute(ATTRIBUTE_ACTION_ICON)) {
                iconUrl = getConnection().translateVaadinUri(
                        action.getStringAttribute(ATTRIBUTE_ACTION_ICON));
            }
            getWidget().registerAction(key, caption, iconUrl);
        }

    }

    public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl) {
        String nodeKey = uidl.getStringAttribute("key");
        treeNode.setText(uidl.getStringAttribute(ATTRIBUTE_NODE_CAPTION));
        treeNode.key = nodeKey;

        getWidget().registerNode(treeNode);

        if (uidl.hasAttribute("al")) {
            treeNode.actionKeys = uidl.getStringArrayAttribute("al");
        }

        if (uidl.getTag().equals("node")) {
            if (uidl.getChildCount() == 0) {
                treeNode.childNodeContainer.setVisible(false);
            } else {
                renderChildNodes(treeNode, (Iterator) uidl.getChildIterator());
                treeNode.childrenLoaded = true;
            }
        } else {
            treeNode.addStyleName(TreeNode.CLASSNAME + "-leaf");
        }
        if (uidl.hasAttribute(ATTRIBUTE_NODE_STYLE)) {
            treeNode.setNodeStyleName(uidl
                    .getStringAttribute(ATTRIBUTE_NODE_STYLE));
        }

        String description = uidl.getStringAttribute("descr");
        if (description != null && getConnection() != null) {
            // Set tooltip
            TooltipInfo info = new TooltipInfo(description);
            getConnection().registerTooltip(this, nodeKey, info);
        } else {
            // Remove possible previous tooltip
            getConnection().registerTooltip(this, nodeKey, null);
        }

        if (uidl.getBooleanAttribute("expanded") && !treeNode.getState()) {
            treeNode.setState(true, false);
        }

        if (uidl.getBooleanAttribute("selected")) {
            treeNode.setSelected(true);
            // ensure that identifier is in selectedIds array (this may be a
            // partial update)
            getWidget().selectedIds.add(nodeKey);
        }

        treeNode.setIcon(uidl.getStringAttribute(ATTRIBUTE_NODE_ICON));
    }

    void renderChildNodes(TreeNode containerNode, Iterator<UIDL> i) {
        containerNode.childNodeContainer.clear();
        containerNode.childNodeContainer.setVisible(true);
        while (i.hasNext()) {
            final UIDL childUidl = i.next();
            // actions are in bit weird place, don't mix them with children,
            // but current node's actions
            if ("actions".equals(childUidl.getTag())) {
                updateActionMap(childUidl);
                continue;
            }
            final TreeNode childTree = getWidget().new TreeNode();
            updateNodeFromUIDL(childTree, childUidl);
            containerNode.childNodeContainer.add(childTree);
            if (!i.hasNext()) {
                childTree
                        .addStyleDependentName(childTree.isLeaf() ? "leaf-last"
                                : "last");
                childTree.childNodeContainer.addStyleDependentName("last");
            }
        }
        containerNode.childrenLoaded = true;
    }

    @Override
    public boolean isReadOnly() {
        return super.isReadOnly() || getState().isPropertyReadOnly();
    }

    @Override
    public AbstractFieldState getState() {
        return (AbstractFieldState) super.getState();
    }

}
