/*
 * Copyright 2010 The gwtquery plugins team.
 * 
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 * 
 * 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 gwtquery.plugins.droppable.client.gwt;

import static com.google.gwt.query.client.GQuery.$;

import java.util.List;

import com.google.gwt.dom.client.Element;
import com.google.gwt.query.client.Function;
import com.google.gwt.user.cellview.client.CellTree.CellTreeMessages;
import com.google.gwt.user.cellview.client.CellTreeNodeView;
import com.google.gwt.view.client.SelectionModel;
import com.google.gwt.view.client.TreeViewModel.NodeInfo;

/**
 * A view of a tree node awith drag and drop support.
 *
 * @param <T> the type that this view contains
 */
class DragAndDropCellTreeNodeView<T> extends CellTreeNodeView<T> {
    /**
     * The {@link com.google.gwt.view.client.HasData} used to show children. This
     * class is intentionally static because we might move it to a new
     * {@link DragAndDropCellTreeNodeView}, and we don't want non-static
     * references to the old {@link DragAndDropCellTreeNodeView}.
     *
     * @param <C> the child item type
     */
    protected static class DragAndDropNodeCellList<C> extends NodeCellList<C> {
        /**
         * The view used by the NodeCellList.
         */
        protected class View extends NodeCellList<C>.View {
            private final Element childContainer;

            public View(Element childContainer) {

                super(childContainer);
                this.childContainer = childContainer;
            }

            @Override
            public void replaceAllChildren(List<C> values, SelectionModel<? super C> selectionModel, boolean
                    stealFocus) {
                // first clean all cell
                cleanAllCell();

                super.replaceAllChildren(values, selectionModel, stealFocus);

                // add drag and drop behaviour
                addDragAndDropBehaviour(values, 0);
            }

            @Override
            public void replaceChildren(List<C> values, int start, SelectionModel<? super C> selectionModel, boolean
                    stealFocus) {
                // clean cell before they are replaced
                int end = start + values.size();
                for (int rowIndex = start; rowIndex < end; rowIndex++) {
                    Element oldCell = getRowElement(rowIndex);
                    DragAndDropCellWidgetUtils.get().cleanCell(oldCell);
                }

                super.replaceChildren(values, start, selectionModel, stealFocus);

                // add drag and drop behaviour
                addDragAndDropBehaviour(values, start);
            }

            @SuppressWarnings("unchecked")
            protected void addDragAndDropBehaviour(List<C> values, int start) {

                int end = start + values.size();

                for (int rowIndex = start; rowIndex < end; rowIndex++) {
                    C value = values.get(rowIndex - start);
                    Element newCell = getRowElement(rowIndex);

                    if (!(getNodeInfo() instanceof DragAndDropNodeInfo<?>)) {
                        continue;
                    }
                    final DragAndDropNodeInfo<C> dndNodeInfo = (DragAndDropNodeInfo<C>) getNodeInfo();

                    DragAndDropCellWidgetUtils.get().maybeMakeDraggableOrDroppable(newCell, value, dndNodeInfo
                            .getCellDragAndDropBehaviour(), dndNodeInfo.getDraggableOptions(), dndNodeInfo
                            .getDroppableOptions(), ((DragAndDropCellTreeNodeView) getNodeView()).getTree()
                            .ensureDragAndDropHandlers());
                }
            }

            protected void cleanAllCell() {
                $(childContainer).children().each(new Function() {
                    @Override
                    public void f(Element div) {
                        DragAndDropCellWidgetUtils.get().cleanCell((Element) div.getChild(0).cast());
                    }
                });
            }

            private Element getRowElement(int indexOnPage) {
                if (indexOnPage >= 0 && childContainer.getChildCount() > indexOnPage) {
                    return childContainer.getChild(indexOnPage).getChild(0).cast();
                }
                return null;
            }
        }

        // keep the view to clean it during the cleanup
        private View view;

        public DragAndDropNodeCellList(final NodeInfo<C> nodeInfo, final CellTreeNodeView<?> nodeView, int pageSize) {
            super(nodeInfo, nodeView, pageSize);
        }

        /**
         * Cleanup this node view.
         */
        public void cleanup() {
            super.cleanup();
            view.cleanAllCell();
        }

        @Override
        protected NodeCellList<C>.View createView() {
            if (view == null) {
                view = new View(getNodeView().ensureChildContainer());
            }
            return view;
        }
    }

    private DragAndDropCellTree tree;
    private CellTreeMessages messages;

    /**
     * Construct a {@link DragAndDropCellTreeNodeView}.
     *
     * @param tree           the parent {@link DragAndDropCellTreeNodeView}
     * @param parent         the parent {@link DragAndDropCellTreeNodeView}
     * @param parentNodeInfo the {@link NodeInfo} of the parent
     * @param elem           the outer element of this {@link DragAndDropCellTreeNodeView}
     * @param value          the value of this node
     */
    public DragAndDropCellTreeNodeView(final DragAndDropCellTree tree, final CellTreeNodeView<?> parent, NodeInfo<T>
            parentNodeInfo, Element elem, T value, CellTreeMessages messages) {
        super(tree, parent, parentNodeInfo, elem, value, messages);
        this.messages = messages;
        this.tree = tree;
    }

    protected <C> CellTreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo, Element childElem, C childValue,
                                                         Object viewData) {
        return new DragAndDropCellTreeNodeView<C>(tree, this, nodeInfo, childElem, childValue, messages);
    }

    protected DragAndDropCellTree getTree() {
        return tree;
    }

    @Override
    protected <C> NodeCellList<C> createNodeCellList(NodeInfo<C> nodeInfo) {
        return new DragAndDropNodeCellList<C>(nodeInfo, this, tree.getDefaultNodeSize());
    }
}
