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

import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH;
import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML;

import com.android.annotations.VisibleForTesting;
import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Removes the layout surrounding the current selection (or if the current selection has
 * children, removes the current layout), and migrates namespace and layout attributes.
 */
@SuppressWarnings("restriction") // XML model
public class UnwrapRefactoring extends VisualRefactoring {
    private Element mContainer;

    /**
     * This constructor is solely used by {@link Descriptor},
     * to replay a previous refactoring.
     * @param arguments argument map created by #createArgumentMap.
     */
    UnwrapRefactoring(Map<String, String> arguments) {
        super(arguments);
    }

    public UnwrapRefactoring(IFile file, LayoutEditor editor, ITextSelection selection,
            ITreeSelection treeSelection) {
        super(file, editor, selection, treeSelection);
    }

    @VisibleForTesting
    UnwrapRefactoring(List<Element> selectedElements, LayoutEditor editor) {
        super(selectedElements, editor);
    }

    @Override
    public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException,
            OperationCanceledException {
        RefactoringStatus status = new RefactoringStatus();

        try {
            pm.beginTask("Checking preconditions...", 6);

            if (mSelectionStart == -1 || mSelectionEnd == -1) {
                status.addFatalError("No selection to wrap");
                return status;
            }

            // Make sure that the selection all has the same parent?
            if (mElements.size() == 0) {
                status.addFatalError("Nothing to unwrap");
                return status;
            }

            Element first = mElements.get(0);

            // Determine the element of the container to be removed.
            // If you've selected a non-container, or you've selected multiple
            // elements, then it's the parent which should be removed. Otherwise,
            // it's the selection itself which represents the container.
            boolean useParent = mElements.size() > 1;
            if (!useParent) {
                if (DomUtilities.getChildren(first).size() == 0) {
                    useParent = true;
                }
            }
            Node parent = first.getParentNode();
            if (parent instanceof Document) {
                mContainer = first;
                List<Element> elements = DomUtilities.getChildren(mContainer);
                if (elements.size() == 0) {
                    status.addFatalError(
                            "Cannot remove container when it has no children");
                        return status;
                }
            } else if (useParent && (parent instanceof Element)) {
                mContainer = (Element) parent;
            } else {
                mContainer = first;
            }

            for (Element element : mElements) {
                if (element.getParentNode() != parent) {
                    status.addFatalError(
                            "All unwrapped elements must share the same parent element");
                    return status;
                }
            }

            // Ensure that if we are removing the root, that it has only one child
            // such that there is a new single root
            if (mContainer.getParentNode() instanceof Document) {
                if (DomUtilities.getChildren(mContainer).size() > 1) {
                    status.addFatalError(
                        "Cannot remove root: it has more than one child "
                            + "which would result in multiple new roots");
                    return status;
                }
            }

            pm.worked(1);
            return status;

        } finally {
            pm.done();
        }
    }

    @Override
    protected VisualRefactoringDescriptor createDescriptor() {
        String comment = getName();
        return new Descriptor(
                mProject.getName(), //project
                comment, //description
                comment, //comment
                createArgumentMap());
    }

    @Override
    public String getName() {
        return "Remove Container";
    }

    @Override
    protected List<Change> computeChanges(IProgressMonitor monitor) {
        // (1) If the removed parent is the root container, transfer its
        //   namespace declarations
        // (2) Remove the root element completely
        // (3) Transfer layout attributes?
        // (4) Check for Java R.file usages?

        IFile file = mEditor.getInputFile();
        List<Change> changes = new ArrayList<Change>();
        MultiTextEdit rootEdit = new MultiTextEdit();

        // Transfer namespace elements?
        if (mContainer.getParentNode() instanceof Document) {
            List<Element> elements = DomUtilities.getChildren(mContainer);
            assert elements.size() == 1;
            Element newRoot = elements.get(0);

            List<Attr> declarations = findNamespaceAttributes(mContainer);
            for (Attr attribute : declarations) {
                if (attribute instanceof IndexedRegion) {
                    setAttribute(rootEdit, newRoot, attribute.getNamespaceURI(),
                            attribute.getPrefix(), attribute.getLocalName(), attribute.getValue());
                }
            }
        }

        // Transfer layout_ attributes (other than width and height)
         List<Element> children = DomUtilities.getChildren(mContainer);
         if (children.size() == 1) {
            List<Attr> layoutAttributes = findLayoutAttributes(mContainer);
            for (Attr attribute : layoutAttributes) {
                String name = attribute.getLocalName();
                if ((name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT))
                        && ANDROID_URI.equals(attribute.getNamespaceURI())) {
                    // Already handled specially
                    continue;
                }
            }
        }

         // Remove the root
         removeElementTags(rootEdit, mContainer, Collections.<Element>emptyList() /* skip */,
                 false /*changeIndentation*/);

         MultiTextEdit formatted = reformat(rootEdit, XmlFormatStyle.LAYOUT);
         if (formatted != null) {
             rootEdit = formatted;
         }

         TextFileChange change = new TextFileChange(file.getName(), file);
         change.setEdit(rootEdit);
         change.setTextType(EXT_XML);
         changes.add(change);
         return changes;
    }

    @Override
    public VisualRefactoringWizard createWizard() {
        return new UnwrapWizard(this, mEditor);
    }

    public static class Descriptor extends VisualRefactoringDescriptor {
        public Descriptor(String project, String description, String comment,
                Map<String, String> arguments) {
            super("com.android.ide.eclipse.adt.refactoring.unwrap", //$NON-NLS-1$
                    project, description, comment, arguments);
        }

        @Override
        protected Refactoring createRefactoring(Map<String, String> args) {
            return new UnwrapRefactoring(args);
        }
    }
}
