/*
 * 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.wizards.newxmlfile;

import static com.android.AndroidConstants.RES_QUALIFIER_SEP;
import static com.android.ide.common.layout.LayoutConstants.HORIZONTAL_SCROLL_VIEW;
import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT;
import static com.android.ide.common.layout.LayoutConstants.SCROLL_VIEW;
import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT;
import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT;
import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP_CHAR;
import static com.android.ide.eclipse.adt.internal.wizards.newxmlfile.ChooseConfigurationPage.RES_FOLDER_ABS;

import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.ResourceQualifier;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
import com.android.ide.eclipse.adt.internal.editors.menu.descriptors.MenuDescriptors;
import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.ProjectCombo;
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
import com.android.resources.ResourceFolderType;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.android.util.Pair;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;

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

/**
 * This is the first page of the {@link NewXmlFileWizard} which provides the ability to create
 * skeleton XML resources files for Android projects.
 * <p/>
 * This page is used to select the project, resource type and file name.
 */
class NewXmlFileCreationPage extends WizardPage {

    @Override
    public void setVisible(boolean visible) {
        super.setVisible(visible);
        // Ensure the initial focus is in the Name field; you usually don't need
        // to edit the default text field (the project name)
        if (visible && mFileNameTextField != null) {
            mFileNameTextField.setFocus();
        }

        validatePage();
    }

    /**
     * Information on one type of resource that can be created (e.g. menu, pref, layout, etc.)
     */
    static class TypeInfo {
        private final String mUiName;
        private final ResourceFolderType mResFolderType;
        private final String mTooltip;
        private final Object mRootSeed;
        private ArrayList<String> mRoots = new ArrayList<String>();
        private final String mXmlns;
        private final String mDefaultAttrs;
        private final String mDefaultRoot;
        private final int mTargetApiLevel;

        public TypeInfo(String uiName,
                        String tooltip,
                        ResourceFolderType resFolderType,
                        Object rootSeed,
                        String defaultRoot,
                        String xmlns,
                        String defaultAttrs,
                        int targetApiLevel) {
            mUiName = uiName;
            mResFolderType = resFolderType;
            mTooltip = tooltip;
            mRootSeed = rootSeed;
            mDefaultRoot = defaultRoot;
            mXmlns = xmlns;
            mDefaultAttrs = defaultAttrs;
            mTargetApiLevel = targetApiLevel;
        }

        /** Returns the UI name for the resource type. Unique. Never null. */
        String getUiName() {
            return mUiName;
        }

        /** Returns the tooltip for the resource type. Can be null. */
        String getTooltip() {
            return mTooltip;
        }

        /**
         * Returns the name of the {@link ResourceFolderType}.
         * Never null but not necessarily unique,
         * e.g. two types use  {@link ResourceFolderType#XML}.
         */
        String getResFolderName() {
            return mResFolderType.getName();
        }

        /**
         * Returns the matching {@link ResourceFolderType}.
         * Never null but not necessarily unique,
         * e.g. two types use  {@link ResourceFolderType#XML}.
         */
        ResourceFolderType getResFolderType() {
            return mResFolderType;
        }

        /**
         * Returns the seed used to fill the root element values.
         * The seed might be either a String, a String array, an {@link ElementDescriptor},
         * a {@link DocumentDescriptor} or null.
         */
        Object getRootSeed() {
            return mRootSeed;
        }

        /**
         * Returns the default root element that should be selected by default. Can be
         * null.
         *
         * @param project the associated project, or null if not known
         */
        String getDefaultRoot(IProject project) {
            return mDefaultRoot;
        }

        /**
         * Returns the list of all possible root elements for the resource type.
         * This can be an empty ArrayList but not null.
         * <p/>
         * TODO: the root list SHOULD depend on the currently selected project, to include
         * custom classes.
         */
        ArrayList<String> getRoots() {
            return mRoots;
        }

        /**
         * If the generated resource XML file requires an "android" XMLNS, this should be set
         * to {@link SdkConstants#NS_RESOURCES}. When it is null, no XMLNS is generated.
         */
        String getXmlns() {
            return mXmlns;
        }

        /**
         * When not null, this represent extra attributes that must be specified in the
         * root element of the generated XML file. When null, no extra attributes are inserted.
         *
         * @param project the project to get the attributes for
         * @param root the selected root element string, never null
         */
        String getDefaultAttrs(IProject project, String root) {
            return mDefaultAttrs;
        }

        /**
         * When not null, represents an extra string that should be written inside
         * the element when constructed
         *
         * @param project the project to get the child content for
         * @param root the chosen root element
         * @return a string to be written inside the root element, or null if nothing
         */
        String getChild(IProject project, String root) {
            return null;
        }

        /**
         * The minimum API level required by the current SDK target to support this feature.
         *
         * @return the minimum API level
         */
        public int getTargetApiLevel() {
            return mTargetApiLevel;
        }
    }

    /**
     * TypeInfo, information for each "type" of file that can be created.
     */
    private static final TypeInfo[] sTypes = {
        new TypeInfo(
                "Layout",                                                   // UI name
                "An XML file that describes a screen layout.",              // tooltip
                ResourceFolderType.LAYOUT,                                  // folder type
                AndroidTargetData.DESCRIPTOR_LAYOUT,                        // root seed
                LINEAR_LAYOUT,                                              // default root
                SdkConstants.NS_RESOURCES,                                  // xmlns
                "",                                                         // not used, see below
                1                                                           // target API level
                ) {

                @Override
                String getDefaultRoot(IProject project) {
                    // TODO: Use GridLayout by default for new SDKs
                    // (when we've ironed out all the usability issues)
                    //Sdk currentSdk = Sdk.getCurrent();
                    //if (project != null && currentSdk != null) {
                    //    IAndroidTarget target = currentSdk.getTarget(project);
                    //    // fill_parent was renamed match_parent in API level 8
                    //    if (target != null && target.getVersion().getApiLevel() >= 13) {
                    //        return GRID_LAYOUT;
                    //    }
                    //}

                    return LINEAR_LAYOUT;
                };

                // The default attributes must be determined dynamically since whether
                // we use match_parent or fill_parent depends on the API level of the
                // project
                @Override
                String getDefaultAttrs(IProject project, String root) {
                    Sdk currentSdk = Sdk.getCurrent();
                    String fill = VALUE_FILL_PARENT;
                    if (currentSdk != null) {
                        IAndroidTarget target = currentSdk.getTarget(project);
                        // fill_parent was renamed match_parent in API level 8
                        if (target != null && target.getVersion().getApiLevel() >= 8) {
                            fill = VALUE_MATCH_PARENT;
                        }
                    }

                    // Only set "vertical" orientation of LinearLayouts by default;
                    // for GridLayouts for example we want to rely on the real default
                    // of the layout
                    String size = String.format(
                            "android:layout_width=\"%1$s\"\n"        //$NON-NLS-1$
                            + "android:layout_height=\"%2$s\"",        //$NON-NLS-1$
                            fill, fill);
                    if (LINEAR_LAYOUT.equals(root)) {
                        return "android:orientation=\"vertical\"\n" + size; //$NON-NLS-1$
                    } else {
                        return size;
                    }
                }

                @Override
                String getChild(IProject project, String root) {
                    // Create vertical linear layouts inside new scroll views
                    if (SCROLL_VIEW.equals(root) || HORIZONTAL_SCROLL_VIEW.equals(root)) {
                        return "    <LinearLayout "         //$NON-NLS-1$
                            + getDefaultAttrs(project, root).replace('\n', ' ')
                            + "></LinearLayout>\n";         //$NON-NLS-1$
                    }
                    return null;
                }
        },
        new TypeInfo("Values",                                              // UI name
                "An XML file with simple values: colors, strings, dimensions, etc.", // tooltip
                ResourceFolderType.VALUES,                                  // folder type
                ResourcesDescriptors.ROOT_ELEMENT,                          // root seed
                null,                                                       // default root
                null,                                                       // xmlns
                null,                                                       // default attributes
                1                                                           // target API level
                ),
        new TypeInfo("Drawable",                                            // UI name
                "An XML file that describes a drawable.",                   // tooltip
                ResourceFolderType.DRAWABLE,                                // folder type
                AndroidTargetData.DESCRIPTOR_DRAWABLE,                      // root seed
                null,                                                       // default root
                SdkConstants.NS_RESOURCES,                                  // xmlns
                null,                                                       // default attributes
                1                                                           // target API level
                ),
        new TypeInfo("Menu",                                                // UI name
                "An XML file that describes an menu.",                      // tooltip
                ResourceFolderType.MENU,                                    // folder type
                MenuDescriptors.MENU_ROOT_ELEMENT,                          // root seed
                null,                                                       // default root
                SdkConstants.NS_RESOURCES,                                  // xmlns
                null,                                                       // default attributes
                1                                                           // target API level
                ),
        new TypeInfo("Color List",                                          // UI name
                "An XML file that describes a color state list.",           // tooltip
                ResourceFolderType.COLOR,                                   // folder type
                AndroidTargetData.DESCRIPTOR_COLOR,                         // root seed
                "selector",  //$NON-NLS-1$                                  // default root
                SdkConstants.NS_RESOURCES,                                  // xmlns
                null,                                                       // default attributes
                1                                                           // target API level
                ),
        new TypeInfo("Property Animation",                                  // UI name
                "An XML file that describes a property animation",          // tooltip
                ResourceFolderType.ANIMATOR,                                // folder type
                AndroidTargetData.DESCRIPTOR_ANIMATOR,                      // root seed
                "set", //$NON-NLS-1$                                        // default root
                SdkConstants.NS_RESOURCES,                                  // xmlns
                null,                                                       // default attributes
                11                                                          // target API level
                ),
        new TypeInfo("Tween Animation",                                     // UI name
                "An XML file that describes a tween animation.",            // tooltip
                ResourceFolderType.ANIM,                                    // folder type
                AndroidTargetData.DESCRIPTOR_ANIM,                          // root seed
                "set", //$NON-NLS-1$                                        // default root
                null,                                                       // xmlns
                null,                                                       // default attributes
                1                                                           // target API level
                ),
        new TypeInfo("AppWidget Provider",                                  // UI name
                "An XML file that describes a widget provider.",            // tooltip
                ResourceFolderType.XML,                                     // folder type
                AndroidTargetData.DESCRIPTOR_APPWIDGET_PROVIDER,            // root seed
                null,                                                       // default root
                SdkConstants.NS_RESOURCES,                                  // xmlns
                null,                                                       // default attributes
                3                                                           // target API level
                ),
        new TypeInfo("Preference",                                          // UI name
                "An XML file that describes preferences.",                  // tooltip
                ResourceFolderType.XML,                                     // folder type
                AndroidTargetData.DESCRIPTOR_PREFERENCES,                   // root seed
                SdkConstants.CLASS_NAME_PREFERENCE_SCREEN,                  // default root
                SdkConstants.NS_RESOURCES,                                  // xmlns
                null,                                                       // default attributes
                1                                                           // target API level
                ),
        new TypeInfo("Searchable",                                          // UI name
                "An XML file that describes a searchable.",                 // tooltip
                ResourceFolderType.XML,                                     // folder type
                AndroidTargetData.DESCRIPTOR_SEARCHABLE,                    // root seed
                null,                                                       // default root
                SdkConstants.NS_RESOURCES,                                  // xmlns
                null,                                                       // default attributes
                1                                                           // target API level
                ),
        // Still missing: Interpolator, Raw and Mipmap. Raw should probably never be in
        // this menu since it's not often used for creating XML files.
    };

    private NewXmlFileWizard.Values mValues;
    private ProjectCombo mProjectButton;
    private Text mFileNameTextField;
    private Combo mTypeCombo;
    private IStructuredSelection mInitialSelection;
    private ResourceFolderType mInitialFolderType;
    private boolean mInternalTypeUpdate;
    private TargetChangeListener mSdkTargetChangeListener;
    private Table mRootTable;
    private TableViewer mRootTableViewer;

    // --- UI creation ---

    /**
     * Constructs a new {@link NewXmlFileCreationPage}.
     * <p/>
     * Called by {@link NewXmlFileWizard#createMainPage}.
     */
    protected NewXmlFileCreationPage(String pageName, NewXmlFileWizard.Values values) {
        super(pageName);
        mValues = values;
        setPageComplete(false);
    }

    public void setInitialSelection(IStructuredSelection initialSelection) {
        mInitialSelection = initialSelection;
    }

    public void setInitialFolderType(ResourceFolderType initialType) {
        mInitialFolderType = initialType;
    }

    /**
     * Called by the parent Wizard to create the UI for this Wizard Page.
     *
     * {@inheritDoc}
     *
     * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
     */
    @SuppressWarnings("unused") // SWT constructors have side effects, they aren't unused
    public void createControl(Composite parent) {
        // This UI is maintained with WindowBuilder.

        Composite composite = new Composite(parent, SWT.NULL);
        composite.setLayout(new GridLayout(2, false /*makeColumnsEqualWidth*/));
        composite.setLayoutData(new GridData(GridData.FILL_BOTH));

        // label before type radios
        Label typeLabel = new Label(composite, SWT.NONE);
        typeLabel.setText("Resource Type:");

        mTypeCombo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
        mTypeCombo.setToolTipText("What type of resource would you like to create?");
        mTypeCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        if (mInitialFolderType != null) {
            mTypeCombo.setEnabled(false);
        }
        mTypeCombo.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TypeInfo type = getSelectedType();
                if (type != null) {
                    onSelectType(type);
                }
            }
        });

        // separator
        Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
        GridData gd2 = new GridData(GridData.GRAB_HORIZONTAL);
        gd2.horizontalAlignment = SWT.FILL;
        gd2.horizontalSpan = 2;
        separator.setLayoutData(gd2);

        // Project: [button]
        String tooltip = "The Android Project where the new resource file will be created.";
        Label projectLabel = new Label(composite, SWT.NONE);
        projectLabel.setText("Project:");
        projectLabel.setToolTipText(tooltip);

        ProjectChooserHelper helper =
                new ProjectChooserHelper(getShell(), null /* filter */);

        mProjectButton = new ProjectCombo(helper, composite, mValues.project);
        mProjectButton.setToolTipText(tooltip);
        mProjectButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mProjectButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                IProject project = mProjectButton.getSelectedProject();
                if (project != mValues.project) {
                    changeProject(project);
                }
            };
        });

        // Filename: [text]
        Label fileLabel = new Label(composite, SWT.NONE);
        fileLabel.setText("File:");
        fileLabel.setToolTipText("The name of the resource file to create.");

        mFileNameTextField = new Text(composite, SWT.BORDER);
        mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mFileNameTextField.setToolTipText(tooltip);
        mFileNameTextField.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                mValues.name = mFileNameTextField.getText();
                validatePage();
            }
        });

        // separator
        Label rootSeparator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
        GridData gd = new GridData(GridData.GRAB_HORIZONTAL);
        gd.horizontalAlignment = SWT.FILL;
        gd.horizontalSpan = 2;
        rootSeparator.setLayoutData(gd);

        // Root Element:
        // [TableViewer]
        Label rootLabel = new Label(composite, SWT.NONE);
        rootLabel.setText("Root Element:");
        new Label(composite, SWT.NONE);

        mRootTableViewer = new TableViewer(composite, SWT.BORDER | SWT.FULL_SELECTION);
        mRootTable = mRootTableViewer.getTable();
        GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
        tableGridData.heightHint = 200;
        mRootTable.setLayoutData(tableGridData);

        setControl(composite);

        // Update state the first time
        setErrorMessage(null);
        setMessage(null);

        initializeFromSelection(mInitialSelection);
        updateAvailableTypes();
        initializeFromFixedType();
        initializeRootValues();
        installTargetChangeListener();

        initialSelectType();
        validatePage();
    }

    private void initialSelectType() {
        TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
        int typeIndex = getTypeComboIndex(mValues.type);
        if (typeIndex == -1) {
            typeIndex = 0;
        } else {
            assert mValues.type == types[typeIndex];
        }
        mTypeCombo.select(typeIndex);
        onSelectType(types[typeIndex]);
        updateRootCombo(types[typeIndex]);
    }

    private void installTargetChangeListener() {
        mSdkTargetChangeListener = new TargetChangeListener() {
            @Override
            public IProject getProject() {
                return mValues.project;
            }

            @Override
            public void reload() {
                if (mValues.project != null) {
                    changeProject(mValues.project);
                }
            }
        };

        AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
    }

    @Override
    public void dispose() {

        if (mSdkTargetChangeListener != null) {
            AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
            mSdkTargetChangeListener = null;
        }

        super.dispose();
    }

    /**
     * Returns the selected root element string, if any.
     *
     * @return The selected root element string or null.
     */
    public String getRootElement() {
        int index = mRootTable.getSelectionIndex();
        if (index >= 0) {
            Object[] roots = (Object[]) mRootTableViewer.getInput();
            return roots[index].toString();
        }
        return null;
    }

    /**
     * Called by {@link NewXmlFileWizard} to initialize the page with the selection
     * received by the wizard -- typically the current user workbench selection.
     * <p/>
     * Things we expect to find out from the selection:
     * <ul>
     * <li>The project name, valid if it's an android nature.</li>
     * <li>The current folder, valid if it's a folder under /res</li>
     * <li>An existing filename, in which case the user will be asked whether to override it.</li>
     * </ul>
     * <p/>
     * The selection can also be set to a {@link Pair} of {@link IProject} and a workspace
     * resource path (where the resource path does not have to exist yet, such as res/anim/).
     *
     * @param selection The selection when the wizard was initiated.
     */
    private boolean initializeFromSelection(IStructuredSelection selection) {
        if (selection == null) {
            return false;
        }

        // Find the best match in the element list. In case there are multiple selected elements
        // select the one that provides the most information and assign them a score,
        // e.g. project=1 + folder=2 + file=4.
        IProject targetProject = null;
        String targetWsFolderPath = null;
        String targetFileName = null;
        int targetScore = 0;
        for (Object element : selection.toList()) {
            if (element instanceof IAdaptable) {
                IResource res = (IResource) ((IAdaptable) element).getAdapter(IResource.class);
                IProject project = res != null ? res.getProject() : null;

                // Is this an Android project?
                try {
                    if (project == null || !project.hasNature(AdtConstants.NATURE_DEFAULT)) {
                        continue;
                    }
                } catch (CoreException e) {
                    // checking the nature failed, ignore this resource
                    continue;
                }

                int score = 1; // we have a valid project at least

                IPath wsFolderPath = null;
                String fileName = null;
                assert res != null; // Eclipse incorrectly thinks res could be null, so tell it no
                if (res.getType() == IResource.FOLDER) {
                    wsFolderPath = res.getProjectRelativePath();
                } else if (res.getType() == IResource.FILE) {
                    if (AdtUtils.endsWithIgnoreCase(res.getName(), DOT_XML)) {
                        fileName = res.getName();
                    }
                    wsFolderPath = res.getParent().getProjectRelativePath();
                }

                // Disregard this folder selection if it doesn't point to /res/something
                if (wsFolderPath != null &&
                        wsFolderPath.segmentCount() > 1 &&
                        SdkConstants.FD_RESOURCES.equals(wsFolderPath.segment(0))) {
                    score += 2;
                } else {
                    wsFolderPath = null;
                    fileName = null;
                }

                score += fileName != null ? 4 : 0;

                if (score > targetScore) {
                    targetScore = score;
                    targetProject = project;
                    targetWsFolderPath = wsFolderPath != null ? wsFolderPath.toString() : null;
                    targetFileName = fileName;
                }
            } else if (element instanceof Pair<?,?>) {
                // Pair of Project/String
                @SuppressWarnings("unchecked")
                Pair<IProject,String> pair = (Pair<IProject,String>)element;
                targetScore = 1;
                targetProject = pair.getFirst();
                targetWsFolderPath = pair.getSecond();
                targetFileName = "";
            }
        }

        if (targetProject == null) {
            // Try to figure out the project from the active editor
            IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
            if (window != null) {
                IWorkbenchPage page = window.getActivePage();
                if (page != null) {
                    IEditorPart activeEditor = page.getActiveEditor();
                    if (activeEditor instanceof AndroidXmlEditor) {
                        Object input = ((AndroidXmlEditor) activeEditor).getEditorInput();
                        if (input instanceof FileEditorInput) {
                            FileEditorInput fileInput = (FileEditorInput) input;
                            targetScore = 1;
                            IFile file = fileInput.getFile();
                            targetProject = file.getProject();
                            IPath path = file.getParent().getProjectRelativePath();
                            targetWsFolderPath = path != null ? path.toString() : null;
                        }
                    }
                }
            }
        }

        if (targetProject == null) {
            // If we didn't find a default project based on the selection, check how many
            // open Android projects we can find in the current workspace. If there's only
            // one, we'll just select it by default.

            IJavaProject[] projects = BaseProjectHelper.getAndroidProjects(new IProjectFilter() {
                public boolean accept(IProject project) {
                    return project.isAccessible();
                }
            });

            if (projects != null && projects.length == 1) {
                targetScore = 1;
                targetProject = projects[0].getProject();
            }
        }

        // Now set the UI accordingly
        if (targetScore > 0) {
            mValues.project = targetProject;
            mValues.folderPath = targetWsFolderPath;
            mProjectButton.setSelectedProject(targetProject);
            mFileNameTextField.setText(targetFileName != null ? targetFileName : ""); //$NON-NLS-1$

            // If the current selection context corresponds to a specific file type,
            // select it.
            if (targetWsFolderPath != null) {
                int pos = targetWsFolderPath.lastIndexOf(WS_SEP_CHAR);
                if (pos >= 0) {
                    targetWsFolderPath = targetWsFolderPath.substring(pos + 1);
                }
                String[] folderSegments = targetWsFolderPath.split(RES_QUALIFIER_SEP);
                if (folderSegments.length > 0) {
                    String folderName = folderSegments[0];
                    selectTypeFromFolder(folderName);
                }
            }
        }

        return true;
    }

    private void initializeFromFixedType() {
        if (mInitialFolderType != null) {
            for (TypeInfo type : sTypes) {
                if (type.getResFolderType() == mInitialFolderType) {
                    mValues.type = type;
                    updateFolderPath(type);
                    break;
                }
            }
        }
    }

    /**
     * Given a folder name, such as "drawable", select the corresponding type in
     * the dropdown.
     */
    void selectTypeFromFolder(String folderName) {
        List<TypeInfo> matches = new ArrayList<TypeInfo>();
        boolean selected = false;

        TypeInfo selectedType = getSelectedType();
        for (TypeInfo type : sTypes) {
            if (type.getResFolderName().equals(folderName)) {
                matches.add(type);
                selected |= type == selectedType;
            }
        }

        if (matches.size() == 1) {
            // If there's only one match, select it if it's not already selected
            if (!selected) {
                selectType(matches.get(0));
            }
        } else if (matches.size() > 1) {
            // There are multiple type candidates for this folder. This can happen
            // for /res/xml for example. Check to see if one of them is currently
            // selected. If yes, leave the selection unchanged. If not, deselect all type.
            if (!selected) {
                selectType(null);
            }
        } else {
            // Nothing valid was selected.
            selectType(null);
        }
    }

    /**
     * Initialize the root values of the type infos based on the current framework values.
     */
    private void initializeRootValues() {
        IProject project = mValues.project;
        for (TypeInfo type : sTypes) {
            // Clear all the roots for this type
            ArrayList<String> roots = type.getRoots();
            if (roots.size() > 0) {
                roots.clear();
            }

            // depending of the type of the seed, initialize the root in different ways
            Object rootSeed = type.getRootSeed();

            if (rootSeed instanceof String) {
                // The seed is a single string, Add it as-is.
                roots.add((String) rootSeed);
            } else if (rootSeed instanceof String[]) {
                // The seed is an array of strings. Add them as-is.
                for (String value : (String[]) rootSeed) {
                    roots.add(value);
                }
            } else if (rootSeed instanceof Integer && project != null) {
                // The seed is a descriptor reference defined in AndroidTargetData.DESCRIPTOR_*
                // In this case add all the children element descriptors defined, recursively,
                // and avoid infinite recursion by keeping track of what has already been added.

                // Note: if project is null, the root list will be empty since it has been
                // cleared above.

                // get the AndroidTargetData from the project
                IAndroidTarget target = null;
                AndroidTargetData data = null;

                target = Sdk.getCurrent().getTarget(project);
                if (target == null) {
                    // A project should have a target. The target can be missing if the project
                    // is an old project for which a target hasn't been affected or if the
                    // target no longer exists in this SDK. Simply log the error and dismiss.

                    AdtPlugin.log(IStatus.INFO,
                            "NewXmlFile wizard: no platform target for project %s",  //$NON-NLS-1$
                            project.getName());
                    continue;
                } else {
                    data = Sdk.getCurrent().getTargetData(target);

                    if (data == null) {
                        // We should have both a target and its data.
                        // However if the wizard is invoked whilst the platform is still being
                        // loaded we can end up in a weird case where we have a target but it
                        // doesn't have any data yet.
                        // Lets log a warning and silently ignore this root.

                        AdtPlugin.log(IStatus.INFO,
                              "NewXmlFile wizard: no data for target %s, project %s",  //$NON-NLS-1$
                              target.getName(), project.getName());
                        continue;
                    }
                }

                IDescriptorProvider provider = data.getDescriptorProvider((Integer)rootSeed);
                ElementDescriptor descriptor = provider.getDescriptor();
                if (descriptor != null) {
                    HashSet<ElementDescriptor> visited = new HashSet<ElementDescriptor>();
                    initRootElementDescriptor(roots, descriptor, visited);
                }

                // Sort alphabetically.
                Collections.sort(roots);
            }
        }
    }

    /**
     * Helper method to recursively insert all XML names for the given {@link ElementDescriptor}
     * into the roots array list. Keeps track of visited nodes to avoid infinite recursion.
     * Also avoids inserting the top {@link DocumentDescriptor} which is generally synthetic
     * and not a valid root element.
     */
    private void initRootElementDescriptor(ArrayList<String> roots,
            ElementDescriptor desc, HashSet<ElementDescriptor> visited) {
        if (!(desc instanceof DocumentDescriptor)) {
            String xmlName = desc.getXmlName();
            if (xmlName != null && xmlName.length() > 0) {
                roots.add(xmlName);
            }
        }

        visited.add(desc);

        for (ElementDescriptor child : desc.getChildren()) {
            if (!visited.contains(child)) {
                initRootElementDescriptor(roots, child, visited);
            }
        }
    }

    /**
     * Changes mProject to the given new project and update the UI accordingly.
     * <p/>
     * Note that this does not check if the new project is the same as the current one
     * on purpose, which allows a project to be updated when its target has changed or
     * when targets are loaded in the background.
     */
    private void changeProject(IProject newProject) {
        mValues.project = newProject;

        // enable types based on new API level
        updateAvailableTypes();
        initialSelectType();

        // update the folder name based on API level
        updateFolderPath(mValues.type);

        // update the Type with the new descriptors.
        initializeRootValues();

        // update the combo
        updateRootCombo(mValues.type);

        validatePage();
    }

    private void onSelectType(TypeInfo type) {
        // Do nothing if this is an internal modification or if the widget has been
        // deselected.
        if (mInternalTypeUpdate) {
            return;
        }

        mValues.type = type;

        if (type == null) {
            return;
        }

        // update the combo
        updateRootCombo(type);

        // update the folder path
        updateFolderPath(type);

        validatePage();
    }

    /** Updates the selected type in the type dropdown control */
    private void setSelectedType(TypeInfo type) {
        TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
        if (types != null) {
            for (int i = 0, n = types.length; i < n; i++) {
                if (types[i] == type) {
                    mTypeCombo.select(i);
                    break;
                }
            }
        }
    }

    /** Returns the selected type in the type dropdown control */
    private TypeInfo getSelectedType() {
        int index = mTypeCombo.getSelectionIndex();
        if (index != -1) {
            TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
            return types[index];
        }

        return null;
    }

    /** Returns the selected index in the type dropdown control */
    private int getTypeComboIndex(TypeInfo type) {
        TypeInfo[] types = (TypeInfo[]) mTypeCombo.getData();
        for (int i = 0, n = types.length; i < n; i++) {
            if (type == types[i]) {
                return i;
            }
        }

        return -1;
    }

    /** Updates the folder path to reflect the given type */
    private void updateFolderPath(TypeInfo type) {
        String wsFolderPath = mValues.folderPath;
        String newPath = null;
        FolderConfiguration config = mValues.configuration;
        ResourceQualifier qual = config.getInvalidQualifier();
        if (qual == null) {
            // The configuration is valid. Reformat the folder path using the canonical
            // value from the configuration.
            newPath = RES_FOLDER_ABS + config.getFolderName(type.getResFolderType());
        } else {
            // The configuration is invalid. We still update the path but this time
            // do it manually on the string.
            if (wsFolderPath.startsWith(RES_FOLDER_ABS)) {
                wsFolderPath = wsFolderPath.replaceFirst(
                        "^(" + RES_FOLDER_ABS +")[^-]*(.*)",         //$NON-NLS-1$ //$NON-NLS-2$
                        "\\1" + type.getResFolderName() + "\\2");    //$NON-NLS-1$ //$NON-NLS-2$
            } else {
                newPath = RES_FOLDER_ABS + config.getFolderName(type.getResFolderType());
            }
        }

        if (newPath != null && !newPath.equals(wsFolderPath)) {
            mValues.folderPath = newPath;
        }
    }

    /**
     * Helper method that fills the values of the "root element" combo box based
     * on the currently selected type radio button. Also disables the combo is there's
     * only one choice. Always select the first root element for the given type.
     *
     * @param type The currently selected {@link TypeInfo}, or null
     */
    private void updateRootCombo(TypeInfo type) {
        IBaseLabelProvider labelProvider = new ColumnLabelProvider() {
            @Override
            public Image getImage(Object element) {
                return IconFactory.getInstance().getIcon(element.toString());
            }
        };
        mRootTableViewer.setContentProvider(new ArrayContentProvider());
        mRootTableViewer.setLabelProvider(labelProvider);

        if (type != null) {
            // get the list of roots. The list can be empty but not null.
            ArrayList<String> roots = type.getRoots();
            mRootTableViewer.setInput(roots.toArray());

            int index = 0; // default is to select the first one
            String defaultRoot = type.getDefaultRoot(mValues.project);
            if (defaultRoot != null) {
                index = roots.indexOf(defaultRoot);
            }
            mRootTable.select(index < 0 ? 0 : index);
            mRootTable.showSelection();
        }
    }

    /**
     * Helper method to select the current type in the type dropdown
     *
     * @param type The TypeInfo matching the radio button to selected or null to deselect them all.
     */
    private void selectType(TypeInfo type) {
        mInternalTypeUpdate = true;
        mValues.type = type;
        if (type == null) {
            if (mTypeCombo.getSelectionIndex() != -1) {
                mTypeCombo.deselect(mTypeCombo.getSelectionIndex());
            }
        } else {
            setSelectedType(type);
        }
        updateRootCombo(type);
        mInternalTypeUpdate = false;
    }

    /**
     * Add the available types in the type combobox, based on whether they are available
     * for the current SDK.
     * <p/>
     * A type is available either if:
     * - if mProject is null, API level 1 is considered valid
     * - if mProject is !null, the project->target->API must be >= to the type's API level.
     */
    private void updateAvailableTypes() {
        IProject project = mValues.project;
        IAndroidTarget target = project != null ? Sdk.getCurrent().getTarget(project) : null;
        int currentApiLevel = 1;
        if (target != null) {
            currentApiLevel = target.getVersion().getApiLevel();
        }

        List<String> items = new ArrayList<String>(sTypes.length);
        List<TypeInfo> types = new ArrayList<TypeInfo>(sTypes.length);
        for (int i = 0, n = sTypes.length; i < n; i++) {
            TypeInfo type = sTypes[i];
            if (type.getTargetApiLevel() <= currentApiLevel) {
                items.add(type.getUiName());
                types.add(type);
            }
        }
        mTypeCombo.setItems(items.toArray(new String[items.size()]));
        mTypeCombo.setData(types.toArray(new TypeInfo[types.size()]));
    }

    /**
     * Validates the fields, displays errors and warnings.
     * Enables the finish button if there are no errors.
     */
    private void validatePage() {
        String error = null;
        String warning = null;

        // -- validate type
        TypeInfo type = mValues.type;
        if (error == null) {
            if (type == null) {
                error = "One of the types must be selected (e.g. layout, values, etc.)";
            }
        }

        // -- validate project
        if (mValues.project == null) {
            error = "Please select an Android project.";
        }

        // -- validate type API level
        if (error == null) {
            IAndroidTarget target = Sdk.getCurrent().getTarget(mValues.project);
            int currentApiLevel = 1;
            if (target != null) {
                currentApiLevel = target.getVersion().getApiLevel();
            }

            assert type != null;
            if (type.getTargetApiLevel() > currentApiLevel) {
                error = "The API level of the selected type (e.g. AppWidget, etc.) is not " +
                        "compatible with the API level of the project.";
            }
        }

        // -- validate filename
        if (error == null) {
            String fileName = mValues.getFileName();
            assert type != null;
            ResourceFolderType folderType = type.getResFolderType();
            error = ResourceNameValidator.create(true, folderType).isValid(fileName);
        }

        // -- validate destination file doesn't exist
        if (error == null) {
            IFile file = mValues.getDestinationFile();
            if (file != null && file.exists()) {
                warning = "The destination file already exists";
            }
        }

        // -- update UI & enable finish if there's no error
        setPageComplete(error == null);
        if (error != null) {
            setMessage(error, IMessageProvider.ERROR);
        } else if (warning != null) {
            setMessage(warning, IMessageProvider.WARNING);
        } else {
            setErrorMessage(null);
            setMessage(null);
        }
    }

    /**
     * Returns the {@link TypeInfo} for the given {@link ResourceFolderType}, or null if
     * not found
     *
     * @param folderType the {@link ResourceFolderType} to look for
     * @return the corresponding {@link TypeInfo}
     */
    static TypeInfo getTypeInfo(ResourceFolderType folderType) {
        for (TypeInfo typeInfo : sTypes) {
            if (typeInfo.getResFolderType() == folderType) {
                return typeInfo;
            }
        }

        return null;
    }
}
