/*
 * Copyright (C) 2008 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.parts;

import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.sdklib.SdkConstants;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.DragTracker;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gef.editpolicies.LayoutEditPolicy;
import org.eclipse.gef.editpolicies.SelectionEditPolicy;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gef.requests.DropRequest;
import org.eclipse.gef.tools.SelectEditPartTracker;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import java.util.List;

/**
 * An {@link EditPart} for a {@link UiElementNode}.
 */
public abstract class UiElementEditPart extends AbstractGraphicalEditPart
    implements IUiUpdateListener {
    
    public UiElementEditPart(UiElementNode uiElementNode) {
        setModel(uiElementNode);
    }

    //-------------------------
    // Derived classes must define these

    abstract protected void hideSelection();
    abstract protected void showSelection();

    //-------------------------
    // Base class overrides
    
    @Override
    public DragTracker getDragTracker(Request request) {
        return new SelectEditPartTracker(this);
    }

    @Override
    protected void createEditPolicies() {
        /*
         * This is no longer needed, as a selection edit policy is set by the parent layout.
         * Leave this code commented out right now, I'll want to play with this later.
         * 
        installEditPolicy(EditPolicy.SELECTION_FEEDBACK_ROLE,
                new NonResizableSelectionEditPolicy(this));
         */
    }
    
    /* (non-javadoc)
     * Returns a List containing the children model objects.
     * Must not return null, instead use the super which returns an empty list.
     */
    @SuppressWarnings("unchecked")
    @Override
    protected List getModelChildren() {
        return getUiNode().getUiChildren();
    }

    @Override
    public void activate() {
        super.activate();
        getUiNode().addUpdateListener(this);
    }
    
    @Override
    public void deactivate() {
        super.deactivate();
        getUiNode().removeUpdateListener(this);
    }

    @Override
    protected void refreshVisuals() {
        if (getFigure().getParent() != null) {
            ((GraphicalEditPart) getParent()).setLayoutConstraint(this, getFigure(), getBounds());
        }
        
        // update the visuals of the children as well
        refreshChildrenVisuals();
    }
    
    protected void refreshChildrenVisuals() {
        if (children != null) {
            for (Object child : children) {
                if (child instanceof UiElementEditPart) {
                    UiElementEditPart childPart = (UiElementEditPart)child;
                    childPart.refreshVisuals();
                }
            }
        }
    }

    //-------------------------
    // IUiUpdateListener implementation

    public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) {
        // TODO: optimize by refreshing only when needed
        switch(state) {
        case ATTR_UPDATED:
            refreshVisuals();
            break;
        case CHILDREN_CHANGED:
            refreshChildren();
            
            // new children list, need to update the layout
            refreshVisuals();
            break;
        case CREATED:
            refreshVisuals();
            break;
        case DELETED:
            // pass
            break;
        }
    }

    //-------------------------
    // Local methods

    /** @return The object model casted to an {@link UiElementNode} */
    public final UiElementNode getUiNode() {
        return (UiElementNode) getModel();
    }
    
    protected final ElementDescriptor getDescriptor() {
        return getUiNode().getDescriptor();
    }
    
    protected final UiElementEditPart getEditPartParent() {
        EditPart parent = getParent();
        if (parent instanceof UiElementEditPart) {
            return (UiElementEditPart)parent; 
        }
        return null;
    }
    
    /**
     * Returns a given XML attribute.
     * @param attrName The local name of the attribute.
     * @return the attribute as a {@link String}, if it exists, or <code>null</code>
     */
    protected final String getStringAttr(String attrName) {
        UiElementNode uiNode = getUiNode();
        if (uiNode.getXmlNode() != null) {
            Node xmlNode = uiNode.getXmlNode();
            if (xmlNode != null) {
                NamedNodeMap nodeAttributes = xmlNode.getAttributes();
                if (nodeAttributes != null) {
                    Node attr = nodeAttributes.getNamedItemNS(
                            SdkConstants.NS_RESOURCES, attrName);
                    if (attr != null) {
                        return attr.getNodeValue();
                    }
                }
            }
        }
        return null;
    }
    
    protected final Rectangle getBounds() {
        UiElementNode model = (UiElementNode)getModel();
        
        Object editData = model.getEditData();

        if (editData != null) {
            // assert with fully qualified class name to prevent import changes to another
            // Rectangle class.
            assert (editData instanceof org.eclipse.draw2d.geometry.Rectangle);
    
            return (Rectangle)editData;
        }

        // return a dummy rect
        return new Rectangle(0, 0, 0, 0);
    }

    /**
     * Returns the EditPart that should be used as the target for the specified Request. 
     * <p/>
     * For instance this is called during drag'n'drop with a CreateRequest.
     * <p/>
     * Reject being a target for elements which descriptor does not allow children.
     * 
     * {@inheritDoc}
     */
    @Override
    public EditPart getTargetEditPart(Request request) {
        if (request != null && request.getType() == RequestConstants.REQ_CREATE) {
            // Reject being a target for elements which descriptor does not allow children.
            if (!getUiNode().getDescriptor().hasChildren()) {
                return null;
            }
        }
        return super.getTargetEditPart(request);
    }

    /**
     * Used by derived classes {@link UiDocumentEditPart} and {@link UiLayoutEditPart}
     * to accept drag'n'drop of new items from the palette.
     * 
     * @param layoutEditPart The layout edit part where this policy is installed. It can
     *        be either a {@link UiDocumentEditPart} or a {@link UiLayoutEditPart}.
     */
    protected void installLayoutEditPolicy(final UiElementEditPart layoutEditPart) {
        // This policy indicates how elements can be constrained by the layout.
        // TODO Right now we use the XY layout policy since our constraints are
        // handled by the android rendering engine rather than GEF. Tweak as
        // appropriate.
        installEditPolicy(EditPolicy.LAYOUT_ROLE,  new LayoutEditPolicy() {

            /**
             * We don't allow layout children to be resized yet.
             * <p/>
             * Typical choices would be:
             * <ul>
             * <li> ResizableEditPolicy, to allow for selection, move and resize.
             * <li> NonResizableEditPolicy, to allow for selection, move but not resize.
             * <li> SelectionEditPolicy to allow for only selection.
             * </ul>
             * <p/>
             * TODO: make this depend on the part layout. For an AbsoluteLayout we should
             * probably use a NonResizableEditPolicy and SelectionEditPolicy for the rest.
             * Whether to use ResizableEditPolicy or NonResizableEditPolicy should depend
             * on the child in an AbsoluteLayout.
             */
            @Override
            protected EditPolicy createChildEditPolicy(EditPart child) {
                if (child instanceof UiElementEditPart) {
                    return new NonResizableSelectionEditPolicy((UiElementEditPart) child);
                }
                return null;
            }

            @Override
            protected Command getCreateCommand(CreateRequest request) {
                // We store the ElementDescriptor in the request.factory.type
                Object newType = request.getNewObjectType();
                if (newType instanceof ElementDescriptor) {
                    Point where = request.getLocation().getCopy();
                    Point origin = getLayoutContainer().getClientArea().getLocation();
                    where.translate(origin.getNegated());
                    
                    // The host is the EditPart where this policy is installed,
                    // e.g. this UiElementEditPart.
                    EditPart host = getHost();
                    if (host instanceof UiElementEditPart) {
                        
                        return new ElementCreateCommand((ElementDescriptor) newType,
                                (UiElementEditPart) host,
                                where);
                    }
                }
                
                return null;
            }

            @Override
            protected Command getMoveChildrenCommand(Request request) {
                // TODO Auto-generated method stub
                return null;
            }
            
            @Override
            public void showLayoutTargetFeedback(Request request) {
                super.showLayoutTargetFeedback(request);
                
                // for debugging
                // System.out.println("target: " + request.toString() + " -- " + layoutEditPart.getUiNode().getBreadcrumbTrailDescription(false));
                
                if (layoutEditPart instanceof UiLayoutEditPart &&
                        request instanceof DropRequest) {
                    Point where = ((DropRequest) request).getLocation().getCopy();
                    Point origin = getLayoutContainer().getClientArea().getLocation();
                    where.translate(origin.getNegated());

                    ((UiLayoutEditPart) layoutEditPart).showDropTarget(where);
                }
            }

            @Override
            protected void eraseLayoutTargetFeedback(Request request) {
                super.eraseLayoutTargetFeedback(request);
                if (layoutEditPart instanceof UiLayoutEditPart) {
                    ((UiLayoutEditPart) layoutEditPart).hideDropTarget();
                }
            }
            
            @Override
            protected IFigure createSizeOnDropFeedback(CreateRequest createRequest) {
                // TODO understand if this is useful for us or remove
                return super.createSizeOnDropFeedback(createRequest);
            }
            
        });
    }
    
    protected static class NonResizableSelectionEditPolicy extends SelectionEditPolicy {
        
        private final UiElementEditPart mEditPart;

        public NonResizableSelectionEditPolicy(UiElementEditPart editPart) {
            mEditPart = editPart;
        }
        
        @Override
        protected void hideSelection() {
            mEditPart.hideSelection();
        }

        @Override
        protected void showSelection() {
            mEditPart.showSelection();
        }
    }
}
