/*
 * 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.eclipse.adt.internal.project.AndroidManifestHelper;
import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener;
import com.android.ide.eclipse.adt.internal.refactoring.core.RefactoringUtil;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.xml.AndroidManifest;

import org.eclipse.core.filebuffers.ITextFileBufferManager;
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.IDocument;
import org.eclipse.ltk.core.refactoring.DocumentChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
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.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
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.IDOMModel;
import org.w3c.dom.Attr;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.Map;

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

    protected IFile mAndroidManifest;

    protected String mAppPackage;

    protected IStructuredModel mModel;

    protected IDocument mDocument;

    protected Map<String, String> mElements;

    protected String mNewName;

    protected String mOldName;

    protected ITextFileBufferManager mManager;

    /**
     * Creates a new <code>AndroidDocumentChange</code> for the given
     * {@link IDocument}.
     *
     * @param document the document this change is working on
     */
    public AndroidDocumentChange(IDocument document) {
        super(SdkConstants.FN_ANDROID_MANIFEST_XML, document);
    }

     @Override
    public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException,
            OperationCanceledException {
        RefactoringStatus status = super.isValid(pm);
        if (mModel == null) {
            status.addFatalError("File " + SdkConstants.FN_ANDROID_MANIFEST_XML + " is invalid.");
        } else {
            mAppPackage = getAppPackage();
            if (mAppPackage == null) {
                status.addFatalError("Invalid package in the "
                        + SdkConstants.FN_ANDROID_MANIFEST_XML + " file.");
            }
        }
        BasicXmlErrorListener errorListener = new BasicXmlErrorListener();
        AndroidManifestHelper.parseForError(mAndroidManifest, errorListener);

        if (errorListener.mHasXmlError == true) {
            status.addFatalError("File " + SdkConstants.FN_ANDROID_MANIFEST_XML + " is invalid.");
        }
        return status;
    }

    /**
     * Finds the attribute with values oldName
     *
     * @param xmlDoc the document
     * @param element the element
     * @param attributeName the attribute
     * @param oldName the value
     *
     * @return the attribute
     */
    private Attr findAttribute(IDOMDocument xmlDoc, String element, String attributeName,
            String oldName) {
        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) {
                        String fullName = AndroidManifest.combinePackageAndClassName(
                                getAppPackage(), value);
                        if (fullName != null && fullName.equals(oldName)) {
                            return attribute;
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * Returns the attribute value
     *
     * @param xmlDoc the document
     * @param element the element
     * @param attributeName the attribute
     *
     * @return the attribute value
     */
    protected String getElementAttribute(IDOMDocument xmlDoc, String element,
            String attributeName, boolean useNamespace) {
        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;
                if (useNamespace) {
                    attribute = RefactoringUtil.findAndroidAttributes(attributes, attributeName);
                } else {
                    attribute = (Attr) attributes.getNamedItem(attributeName);
                }
                if (attribute != null) {
                    return attribute.getValue();
                }
            }
        }
        return null;
    }

    /**
     * Returns the SSE model for a document
     *
     * @param document the document
     * @return the model
     *
     */
    protected IStructuredModel getModel(IDocument document) {
        if (mModel != null) {
            return mModel;
        }
        IStructuredModel model;
        model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
        if (model == null) {
            if (document instanceof IStructuredDocument) {
                IStructuredDocument structuredDocument = (IStructuredDocument) document;
                model = StructuredModelManager.getModelManager()
                        .getModelForRead(structuredDocument);
            }
        }
        return model;
    }

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

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

    /**
     * Returns the application package
     *
     * @return the package name
     */
    protected String getAppPackage() {
        if (mAppPackage == null) {
            IDOMDocument xmlDoc = getDOMDocument();
            mAppPackage = getElementAttribute(xmlDoc, AndroidManifest.NODE_MANIFEST,
                    AndroidManifest.ATTRIBUTE_PACKAGE, false);
        }
        return mAppPackage;
    }

    /**
     * Returns the text change that set new value of attribute
     *
     * @param attribute the attribute
     * @param newValue the new value
     *
     * @return the text change
     */
    protected 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
     * and combine package
     *
     * @param elementName the element name
     * @param attributeName the attribute name
     * @param oldValue the old value
     * @param newValue the new value
     *
     * @return the text change
     */
    protected TextEdit createTextEdit(String elementName, String attributeName, String oldValue,
            String newValue) {
        return createTextEdit(elementName, attributeName, oldValue, newValue, true);
    }

    /**
     * Returns the text change that change the value of attribute from oldValue to newValue
     *
     * @param elementName the element name
     * @param attributeName the attribute name
     * @param oldName the old value
     * @param newName the new value
     * @param combinePackage combine package ?
     *
     * @return the text change
     */
    protected TextEdit createTextEdit(String elementName, String attributeName, String oldName,
            String newName, boolean combinePackage) {
        IDOMDocument xmlDoc = getDOMDocument();
        String name = null;
        Attr attr = findAttribute(xmlDoc, elementName, attributeName, oldName);
        if (attr != null) {
            name = attr.getValue();
        }
        if (name != null) {
            String newValue;
            if (combinePackage) {
                newValue = AndroidManifest.extractActivityName(newName, getAppPackage());
            } else {
                newValue = newName;
            }
            if (newValue != null) {
                TextEdit edit = createTextEdit(attr, newValue);
                return edit;
            }
        }
        return null;
    }

}
