/*
 * 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.refactoring.changes;

import com.android.ide.common.layout.LayoutConstants;
import com.android.ide.eclipse.adt.internal.refactoring.core.RefactoringUtil;

import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ltk.core.refactoring.DocumentChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.w3c.dom.Attr;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

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

/**
 * A text change that operates on android layout using WTP SSE model.
 * It is base class for Rename Package and Rename Type changes
*/
@SuppressWarnings("restriction")
public class AndroidLayoutChange extends DocumentChange {

    private IDocument mDocument;

    private ITextFileBufferManager mManager;

    private IFile mFile;

    private IStructuredModel mModel;

    private Set<AndroidLayoutChangeDescription> mChanges;

    /**
     * Creates a new <code>AndroidLayoutChange</code>
     *
     * @param file the layout file
     * @param document the document
     * @param manager the buffer manager
     * @param changes the list of changes
     */
    public AndroidLayoutChange(IFile file, IDocument document, ITextFileBufferManager manager,
            Set<AndroidLayoutChangeDescription> changes) {
        super("", document); //$NON-NLS-1$
        mFile = file;
        mDocument = document;
        mManager = manager;
        mChanges = changes;
        try {
            this.mModel = getModel(document);
        } catch (Exception ignore) {
        }
        if (mModel != null) {
            addEdits();
        }
    }

    @Override
    public String getName() {
        return mFile.getName();
    }

    @Override
    public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException,
            OperationCanceledException {
        RefactoringStatus status = super.isValid(pm);
        if (mModel == null) {
            status.addFatalError("Invalid the " + getName() + " file.");
        }
        return status;
    }

    @Override
    public void setTextType(String type) {
        super.setTextType(mFile.getFileExtension());
    }

    @Override
    public void dispose() {
        super.dispose();
        RefactoringUtil.fixModel(mModel, mDocument);

        if (mManager != null) {
            try {
                mManager.disconnect(mFile.getFullPath(), LocationKind.NORMALIZE,
                        new NullProgressMonitor());
            } catch (CoreException e) {
                RefactoringUtil.log(e);
            }
        }
    }

    // ----

    /**
     * Adds text edits for this change
     */
    private void addEdits() {
        MultiTextEdit multiEdit = new MultiTextEdit();
        for (AndroidLayoutChangeDescription change : mChanges) {
            if (!change.isStandalone()) {
                TextEdit edit = createTextEdit(LayoutConstants.VIEW,
                        LayoutConstants.ATTR_CLASS,
                        change.getClassName(),
                        change.getNewName());
                if (edit != null) {
                    multiEdit.addChild(edit);
                }
            } else {
                List<TextEdit> edits = createElementTextEdit(change.getClassName(),
                        change.getNewName());
                for (TextEdit edit : edits) {
                    multiEdit.addChild(edit);
                }
            }
        }
        setEdit(multiEdit);
    }

    /**
     * Returns the text changes which change class (custom layout viewer) in layout file
     *
     * @param className the class name
     * @param newName the new class name
     *
     * @return list of text changes
     */
    private List<TextEdit> createElementTextEdit(String className, String newName) {
        IDOMDocument xmlDoc = getDOMDocument();
        List<TextEdit> edits = new ArrayList<TextEdit>();
        NodeList nodes = xmlDoc.getElementsByTagName(className);
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node instanceof IDOMElement) {
                IDOMElement domNode = (IDOMElement) node;
                IStructuredDocumentRegion firstRegion = domNode.getFirstStructuredDocumentRegion();
                if (firstRegion != null) {
                    int offset = firstRegion.getStartOffset();
                    edits.add(new ReplaceEdit(offset + 1, className.length(), newName));
                }
                IStructuredDocumentRegion endRegion = domNode.getEndStructuredDocumentRegion();
                if (endRegion != null) {
                    int offset = endRegion.getStartOffset();
                    edits.add(new ReplaceEdit(offset + 2, className.length(), newName));
                }
            }

        }
        return edits;
    }

    /**
     * Returns the SSE DOM document
     *
     * @return the attribute value
     */
    private IDOMDocument getDOMDocument() {
        IDOMModel xmlModel = (IDOMModel) mModel;
        IDOMDocument xmlDoc = xmlModel.getDocument();
        return xmlDoc;
    }

    /**
     * Returns the text change that set new value of attribute
     *
     * @param attribute the attribute
     * @param newValue the new value
     *
     * @return the text change
     */
    private TextEdit createTextEdit(Attr attribute, String newValue) {
        if (attribute == null)
            return null;

        if (attribute instanceof IDOMAttr) {
            IDOMAttr domAttr = (IDOMAttr) attribute;
            String region = domAttr.getValueRegionText();
            int offset = domAttr.getValueRegionStartOffset();
            if (region != null && region.length() >= 2) {
                return new ReplaceEdit(offset + 1, region.length() - 2, newValue);
            }
        }
        return null;
    }


    /**
     * Returns the text change that change the value of attribute from oldValue to newValue
     *
     * @param elementName the element name
     * @param argumentName the attribute name
     * @param oldName the old value
     * @param newName the new value
     *
     * @return the text change
     */
    private TextEdit createTextEdit(String elementName, String argumentName, String oldName,
            String newName) {
        IDOMDocument xmlDoc = getDOMDocument();
        String name = null;
        Attr attr = findAttribute(xmlDoc, elementName, argumentName, oldName);
        if (attr != null) {
            name = attr.getValue();
        }
        if (name != null && newName != null) {
            TextEdit edit = createTextEdit(attr, newName);
            return edit;
        }
        return null;
    }

    /**
     * Finds the attribute with values oldName
     *
     * @param xmlDoc the document
     * @param element the element
     * @param attributeName the attribute
     * @param oldValue the value
     *
     * @return the attribute
     */
    private Attr findAttribute(IDOMDocument xmlDoc, String element, String attributeName,
            String oldValue) {
        NodeList nodes = xmlDoc.getElementsByTagName(element);
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            NamedNodeMap attributes = node.getAttributes();
            if (attributes != null) {
                Attr attribute = RefactoringUtil.findAndroidAttributes(attributes, attributeName);
                if (attribute != null) {
                    String value = attribute.getValue();
                    if (value != null && value.equals(oldValue)) {
                        return attribute;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Returns the SSE model for a document
     *
     * @param document the document
     * @return the model
     */
    private IStructuredModel getModel(IDocument document) {

        IModelManager manager = StructuredModelManager.getModelManager();
        IStructuredModel model = manager.getExistingModelForRead(document);
        if (model == null && document instanceof IStructuredDocument) {
            model = manager.getModelForRead((IStructuredDocument) document);
        }

        return model;
    }
}
