/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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.android.ide.eclipse.adt.internal.editors.layout.gle2;

import com.android.ide.common.api.INode;
import com.android.ide.common.api.InsertType;
import com.android.ide.common.layout.BaseLayoutRule;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;

import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerDropAdapter;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.TransferData;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/** Drop listener for the outline page */
/*package*/ class OutlineDropListener extends ViewerDropAdapter {
    private final OutlinePage mOutlinePage;

    public OutlineDropListener(OutlinePage outlinePage, TreeViewer treeViewer) {
        super(treeViewer);
        mOutlinePage = outlinePage;
    }

    @Override
    public void dragEnter(DropTargetEvent event) {
        if (event.detail == DND.DROP_NONE && GlobalCanvasDragInfo.getInstance().isDragging()) {
            // For some inexplicable reason, we get DND.DROP_NONE from the palette
            // even though in its drag start we set DND.DROP_COPY, so correct that here...
            int operation = DND.DROP_COPY;
            event.detail = operation;
        }
        super.dragEnter(event);
    }

    @Override
    public boolean performDrop(Object data) {
        final DropTargetEvent event = getCurrentEvent();
        if (event == null) {
            return false;
        }
        int location = determineLocation(event);
        if (location == LOCATION_NONE) {
            return false;
        }

        final SimpleElement[] elements;
        SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance();
        if (sxt.isSupportedType(event.currentDataType)) {
            if (data instanceof SimpleElement[]) {
                elements = (SimpleElement[]) data;
            } else {
                return false;
            }
        } else {
            return false;
        }
        if (elements.length == 0) {
            return false;
        }

        // Determine target:
        CanvasViewInfo parent = OutlinePage.getViewInfo(event.item.getData());
        if (parent == null) {
            return false;
        }

        int index = -1;
        UiViewElementNode parentNode = parent.getUiViewNode();
        if (location == LOCATION_BEFORE || location == LOCATION_AFTER) {
            UiViewElementNode node = parentNode;
            parent = parent.getParent();
            if (parent == null) {
                return false;
            }
            parentNode = parent.getUiViewNode();

            // Determine index
            index = 0;
            for (UiElementNode child : parentNode.getUiChildren()) {
                if (child == node) {
                    break;
                }
                index++;
            }
            if (location == LOCATION_AFTER) {
                index++;
            }
        }

        // Copy into new position.
        final LayoutCanvas canvas = mOutlinePage.getEditor().getCanvasControl();
        final NodeProxy targetNode = canvas.getNodeFactory().create(parentNode);

        // Record children of the target right before the drop (such that we can
        // find out after the drop which exact children were inserted)
        Set<INode> children = new HashSet<INode>();
        for (INode node : targetNode.getChildren()) {
            children.add(node);
        }

        String label = MoveGesture.computeUndoLabel(targetNode, elements, event.detail);
        final int indexFinal = index;
        canvas.getLayoutEditor().wrapUndoEditXmlModel(label, new Runnable() {
            public void run() {
                InsertType insertType = MoveGesture.getInsertType(event, targetNode);
                canvas.getRulesEngine().setInsertType(insertType);

                Object sourceCanvas = GlobalCanvasDragInfo.getInstance().getSourceCanvas();
                boolean createNew = event.detail == DND.DROP_COPY || sourceCanvas != canvas;
                BaseLayoutRule.insertAt(targetNode, elements, createNew, indexFinal);
                targetNode.applyPendingChanges();

                // Clean up drag if applicable
                if (event.detail == DND.DROP_MOVE) {
                    GlobalCanvasDragInfo.getInstance().removeSource();
                }
            }
        });

        // Now find out which nodes were added, and look up their corresponding
        // CanvasViewInfos
        final List<INode> added = new ArrayList<INode>();
        for (INode node : targetNode.getChildren()) {
            if (!children.contains(node)) {
                added.add(node);
            }
        }
        // Select the newly dropped nodes
        final SelectionManager selectionManager = canvas.getSelectionManager();
        selectionManager.setOutlineSelection(added);

        canvas.redraw();

        return true;
    }

    @Override
    public boolean validateDrop(Object target, int operation,
            TransferData transferType) {
        DropTargetEvent event = getCurrentEvent();
        if (event == null) {
            return false;
        }
        int location = determineLocation(event);
        if (location == LOCATION_NONE) {
            return false;
        }

        SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance();
        if (!sxt.isSupportedType(transferType)) {
            return false;
        }

        CanvasViewInfo parent = OutlinePage.getViewInfo(event.item.getData());
        if (parent == null) {
            return false;
        }

        UiViewElementNode parentNode = parent.getUiViewNode();

        if (location == LOCATION_ON) {
            // Targeting the middle of an item means to add it as a new child
            // of the given element. This is only allowed on some types of nodes.
            if (!DescriptorsUtils.canInsertChildren(parentNode.getDescriptor(),
                    parent.getViewObject())) {
                return false;
            }
        }

        // Check that the drop target position is not a child or identical to
        // one of the dragged items
        SelectionItem[] sel = GlobalCanvasDragInfo.getInstance().getCurrentSelection();
        if (sel != null) {
            for (SelectionItem item : sel) {
                if (isAncestor(item.getViewInfo().getUiViewNode(), parentNode)) {
                    return false;
                }
            }
        }

        return true;
    }

    /** Returns true if the given parent node is an ancestor of the given child node  */
    private boolean isAncestor(UiElementNode parent, UiElementNode child) {
        while (child != null) {
            if (child == parent) {
                return true;
            }
            child = child.getUiParent();
        }
        return false;
    }
}
