/*
 * Copyright (C) 2007 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.manifest.model;

import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiTextAttributeNode;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.actions.OpenNewPackageWizardAction;
import org.eclipse.jdt.ui.actions.ShowInPackageViewAction;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
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.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.dialogs.SelectionDialog;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.TableWrapData;

import java.util.ArrayList;

/**
 * Represents an XML attribute for a package, that can be modified using a simple text field or
 * a dialog to choose an existing package. Also, there's a link to create a new package.
 * <p/>
 * See {@link UiTextAttributeNode} for more information.
 */
public class UiPackageAttributeNode extends UiTextAttributeNode {

    /**
     * Creates a {@link UiPackageAttributeNode} object that will display ui to select or create
     * a package.
     * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node.
     */
    public UiPackageAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) {
        super(attributeDescriptor, uiParent);
    }

    /* (non-java doc)
     * Creates a label widget and an associated text field.
     * <p/>
     * As most other parts of the android manifest editor, this assumes the
     * parent uses a table layout with 2 columns.
     */
    @Override
    public void createUiControl(final Composite parent, final IManagedForm managedForm) {
        setManagedForm(managedForm);
        FormToolkit toolkit = managedForm.getToolkit();
        TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();

        StringBuilder label = new StringBuilder();
        label.append("<form><p><a href='unused'>");  //$NON-NLS-1$
        label.append(desc.getUiName());
        label.append("</a></p></form>");  //$NON-NLS-1$
        FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */,
                label.toString(), true /* setupLayoutData */);
        formText.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent e) {
                super.linkActivated(e);
                doLabelClick();
            }
        });
        formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
        SectionHelper.addControlTooltip(formText, desc.getTooltip());
        
        Composite composite = toolkit.createComposite(parent);
        composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE));
        GridLayout gl = new GridLayout(2, false);
        gl.marginHeight = gl.marginWidth = 0;
        composite.setLayout(gl);
        // Fixes missing text borders under GTK... also requires adding a 1-pixel margin
        // for the text field below
        toolkit.paintBordersFor(composite);
        
        final Text text = toolkit.createText(composite, getCurrentValue());
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        gd.horizontalIndent = 1;  // Needed by the fixed composite borders under GTK
        text.setLayoutData(gd);

        setTextWidget(text);

        Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH);
        
        browseButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                doBrowseClick();
            }
        });
        
    }
    
    /* (non-java doc)
     * Adds a validator to the text field that calls managedForm.getMessageManager().
     */
    @Override
    protected void onAddValidators(final Text text) {
        ModifyListener listener = new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                String package_name = text.getText();
                if (package_name.indexOf('.') < 1) {
                    getManagedForm().getMessageManager().addMessage(text,
                            "Package name should contain at least two identifiers.",
                            null /* data */, IMessageProvider.ERROR, text);
                } else {
                    getManagedForm().getMessageManager().removeMessage(text, text);
                }
            }
        };

        text.addModifyListener(listener);

        // Make sure the validator removes its message(s) when the widget is disposed
        text.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                getManagedForm().getMessageManager().removeMessage(text, text);
            }
        });

        // Finally call the validator once to make sure the initial value is processed
        listener.modifyText(null);
    }

    /**
     * Handles response to the Browse button by creating a Package dialog.
     * */
    private void doBrowseClick() {
        Text text = getTextWidget();
        
        // we need to get the project of the manifest.
        IProject project = getProject();
        if (project != null) {
            
            try {
                SelectionDialog dlg = JavaUI.createPackageDialog(text.getShell(),
                        JavaCore.create(project), 0);
                dlg.setTitle("Select Android Package");
                dlg.setMessage("Select the package for the Android project.");
                SelectionDialog.setDefaultImage(AdtPlugin.getAndroidLogo());

                if (dlg.open() == Window.OK) {
                    Object[] results = dlg.getResult();
                    if (results.length == 1) {
                        setPackageTextField((IPackageFragment)results[0]);
                    }
                }
            } catch (JavaModelException e1) {
            }
        }
    }

    /**
     * Handles response to the Label hyper link being activated.
     */
    private void doLabelClick() {
        // get the current package name
        String package_name = getTextWidget().getText().trim();
        
        if (package_name.length() == 0) {
            createNewPackage();
        } else {
            // Try to select the package in the Package Explorer for the current
            // project and the current editor's site.

            IProject project = getProject();
            if (project == null) {
                AdtPlugin.log(IStatus.ERROR, "Failed to get project for UiPackageAttribute"); //$NON-NLS-1$
                return;
            }

            IWorkbenchPartSite site = getUiParent().getEditor().getSite();
            if (site == null) {
                AdtPlugin.log(IStatus.ERROR, "Failed to get editor site for UiPackageAttribute"); //$NON-NLS-1$
                return;
            }

            for (IPackageFragmentRoot root : getPackageFragmentRoots(project)) {
                IPackageFragment fragment = root.getPackageFragment(package_name);
                if (fragment != null && fragment.exists()) {
                    ShowInPackageViewAction action = new ShowInPackageViewAction(site);
                    action.run(fragment);
                    // This action's run() doesn't provide the status (although internally it could)
                    // so we just assume it worked.
                    return;
                }
            }
        }
    }

    /**
     * Utility method that returns the project for the current file being edited.
     * 
     * @return The IProject for the current file being edited or null.
     */
    private IProject getProject() {
        UiElementNode uiNode = getUiParent();
        AndroidEditor editor = uiNode.getEditor();
        IEditorInput input = editor.getEditorInput();
        if (input instanceof IFileEditorInput) {
            // from the file editor we can get the IFile object, and from it, the IProject.
            IFile file = ((IFileEditorInput)input).getFile();
            return file.getProject();
        }
        
        return null;
    }

    /**
     * Utility method that computes and returns the list of {@link IPackageFragmentRoot}
     * corresponding to the source folder of the specified project.
     * 
     * @param project the project
     * @return an array of IPackageFragmentRoot. Can be empty but not null.
     */
    private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project) {
        ArrayList<IPackageFragmentRoot> result = new ArrayList<IPackageFragmentRoot>();
        try {
            IJavaProject javaProject = JavaCore.create(project);
            IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
            for (int i = 0; i < roots.length; i++) {
                IClasspathEntry entry = roots[i].getRawClasspathEntry();
                if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                    result.add(roots[i]);
                }
            }
        } catch (JavaModelException e) {
        }

        return result.toArray(new IPackageFragmentRoot[result.size()]);
    }
    
    /**
     * Utility method that sets the package's text field to the package fragment's name.
     * */
    private void setPackageTextField(IPackageFragment type) {
        Text text = getTextWidget();

        String name = type.getElementName();
        
        text.setText(name);
    }
    

    /**
     * Displays and handles a "Create Package Wizard".
     * 
     * This is invoked by doLabelClick() when clicking on the hyperlink label with an
     * empty package text field.  
     */
    private void createNewPackage() {
        OpenNewPackageWizardAction action = new OpenNewPackageWizardAction();

        IProject project = getProject();
        action.setSelection(new StructuredSelection(project));
        action.run();

        IJavaElement element = action.getCreatedElement();
        if (element != null &&
                element.exists() &&
                element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
            setPackageTextField((IPackageFragment) element);
        }
    }
    
    @Override
    public String[] getPossibleValues(String prefix) {
        // TODO: compute a list of existing packages for content assist completion
        return null;
    }
}

