/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package org.python.pydev.ui.wizards.project;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard;
import org.eclipse.ui.wizards.newresource.BasicNewResourceWizard;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.log.Log;
import org.python.pydev.navigator.ui.PydevPackageExplorer;
import org.python.pydev.navigator.ui.PydevPackageExplorer.PydevCommonViewer;
import org.python.pydev.plugin.PyStructureConfigHelpers;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.ui.dialogs.PyDialogHelpers;
import org.python.pydev.ui.wizards.gettingstarted.AbstractNewProjectWizard;

/**
 * Python Project creation wizard
 *
 * <ul>
 * <li>Asks users information about Python project
 * <li>Launches another thread to create Python project. A progress monitor is shown in UI thread
 * </ul>
 *
 * TODO: Add a checkbox asking should a skeleton of a Python program generated
 *
 * @author Mikko Ohtamaa
 * @author Fabio Zadrozny
 */
public class PythonProjectWizard extends AbstractNewProjectWizard implements IExecutableExtension {

    /**
     * The current selection.
     */
    protected IStructuredSelection selection;

    public static final String WIZARD_ID = "org.python.pydev.ui.wizards.project.PythonProjectWizard";

    protected IWizardNewProjectNameAndLocationPage projectPage;
    protected IWizardNewProjectExistingSourcesPage sourcesPage;

    Shell shell;

    /** Target project created by this wizard */
    IProject generatedProject;

    /** Exception throw by generator thread */
    Exception creationThreadException;

    private IProject createdProject;

    private IConfigurationElement fConfigElement;

    protected IWorkbench workbench;

    @Override
    public void init(IWorkbench workbench, IStructuredSelection currentSelection) {
        this.selection = currentSelection;
        this.workbench = workbench;
        initializeDefaultPageImageDescriptor();
        projectPage = createProjectPage();
        sourcesPage = createSourcesPage();
        PyDialogHelpers.enableAskInterpreterStep(false);
    }

    @Override
    public void dispose() {
        super.dispose();
        PyDialogHelpers.enableAskInterpreterStep(true);
    }

    /**
     * Creates the project page.
     */
    protected IWizardNewProjectNameAndLocationPage createProjectPage() {
        return new NewProjectNameAndLocationWizardPage("Setting project properties");
    }

    /**
     * Creates the sources page.
     */
    protected IWizardNewProjectExistingSourcesPage createSourcesPage() {
        return new NewProjectExistingSourcesWizardPage("Setting project sources");
    }

    /**
     * Returns the sources page.
     */
    protected IWizardNewProjectExistingSourcesPage getSourcesPage() {
        return sourcesPage;
    }

    /**
     * Returns the page that should appear after the sources page, or null if no page comes after it.
     */
    protected WizardPage getPageAfterSourcesPage() {
        return referencePage;
    }

    /**
     * Add wizard pages to the instance
     *
     * @see org.eclipse.jface.wizard.IWizard#addPages()
     */
    @Override
    public void addPages() {
        addPage(projectPage);
        addPage(sourcesPage);
        addProjectReferencePage();
    }

    /**
     * Creates a new project resource with the entered name.
     *
     * @return the created project resource, or <code>null</code> if the project was not created
     */
    protected IProject createNewProject(final Object... additionalArgsToConfigProject) {
        // get a project handle
        final IProject newProjectHandle = projectPage.getProjectHandle();

        // get a project descriptor
        IPath defaultPath = Platform.getLocation();
        IPath newPath = projectPage.getLocationPath();
        if (defaultPath.equals(newPath)) {
            newPath = null;
        } else {
            //The user entered the path and it's the same as it'd be if he chose the default path.
            IPath withName = defaultPath.append(newProjectHandle.getName());
            if (newPath.toFile().equals(withName.toFile())) {
                newPath = null;
            }
        }

        IWorkspace workspace = ResourcesPlugin.getWorkspace();
        final IProjectDescription description = workspace.newProjectDescription(newProjectHandle.getName());
        description.setLocation(newPath);

        // update the referenced project if provided
        if (referencePage != null) {
            IProject[] refProjects = referencePage.getReferencedProjects();
            if (refProjects.length > 0) {
                description.setReferencedProjects(refProjects);
            }
        }

        final String projectType = projectPage.getProjectType();
        String projectInterpreter = projectPage.getProjectInterpreter();
        if (projectInterpreter == null || projectInterpreter.trim().isEmpty()) {
            projectInterpreter = IPythonNature.DEFAULT_INTERPRETER;
        }
        final String projectInterpreterFinal = projectInterpreter;

        // define the operation to create a new project
        WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
            @Override
            protected void execute(IProgressMonitor monitor) throws CoreException {

                createAndConfigProject(newProjectHandle, description, projectType, projectInterpreterFinal, monitor,
                        additionalArgsToConfigProject);
            }
        };

        // run the operation to create a new project
        try {
            getContainer().run(true, true, op);
        } catch (InterruptedException e) {
            return null;
        } catch (InvocationTargetException e) {
            Throwable t = e.getTargetException();
            if (t instanceof CoreException) {
                if (((CoreException) t).getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
                    MessageDialog.openError(getShell(), "Unable to create project",
                            "Another project with the same name (and different case) already exists.");
                } else {
                    ErrorDialog
                            .openError(getShell(), "Unable to create project", null, ((CoreException) t).getStatus());
                }
            } else {
                // Unexpected runtime exceptions and errors may still occur.
                Log.log(IStatus.ERROR, t.toString(), t);
                MessageDialog.openError(getShell(), "Unable to create project", t.getMessage());
            }
            return null;
        }

        return newProjectHandle;
    }

    protected ICallback<List<IContainer>, IProject> getSourceFolderHandlesCallback = new ICallback<List<IContainer>, IProject>() {
        @Override
        public List<IContainer> call(IProject projectHandle) {
            final int sourceFolderConfigurationStyle = projectPage.getSourceFolderConfigurationStyle();
            List<IContainer> ret = new ArrayList<IContainer>();
            switch (sourceFolderConfigurationStyle) {

                case IWizardNewProjectNameAndLocationPage.PYDEV_NEW_PROJECT_CREATE_PROJECT_AS_SRC_FOLDER:
                    //if the user hasn't selected to create a source folder, use the project itself for that.
                    ret = new ArrayList<IContainer>();
                    ret.add(projectHandle);
                    return ret;

                case IWizardNewProjectNameAndLocationPage.PYDEV_NEW_PROJECT_EXISTING_SOURCES:
                    return new ArrayList<IContainer>();

                case IWizardNewProjectNameAndLocationPage.PYDEV_NEW_PROJECT_NO_PYTHONPATH:
                    return new ArrayList<IContainer>();

                default:
                    IContainer folder = projectHandle.getFolder("src");
                    ret = new ArrayList<IContainer>();
                    ret.add(folder);
                    return ret;
            }
        }
    };

    protected ICallback<List<IPath>, IProject> getExistingSourceFolderHandlesCallback = new ICallback<List<IPath>, IProject>() {
        @Override
        public List<IPath> call(IProject projectHandle) {
            if (projectPage.getSourceFolderConfigurationStyle() == IWizardNewProjectNameAndLocationPage.PYDEV_NEW_PROJECT_EXISTING_SOURCES) {
                List<IPath> eSources = sourcesPage.getExistingSourceFolders();
                if (eSources.size() > 0) {
                    return eSources;
                }
            }
            return null;
        }
    };

    /**
     * This method can be overridden to provide a custom creation of the project.
     *
     * It should create the project, configure the folders in the pythonpath (source folders and external folders
     * if applicable), set the project type and project interpreter.
     */
    protected void createAndConfigProject(final IProject newProjectHandle, final IProjectDescription description,
            final String projectType, final String projectInterpreter, IProgressMonitor monitor,
            Object... additionalArgsToConfigProject) throws CoreException {
        ICallback<List<IContainer>, IProject> getSourceFolderHandlesCallback = this.getSourceFolderHandlesCallback;
        ICallback<List<IPath>, IProject> getExistingSourceFolderHandlesCallback = this.getExistingSourceFolderHandlesCallback;

        PyStructureConfigHelpers.createPydevProject(description, newProjectHandle, monitor, projectType,
                projectInterpreter, getSourceFolderHandlesCallback, null, getExistingSourceFolderHandlesCallback);
    }

    /**
     * The user clicked Finish button
     *
     * Launches another thread to create Python project. A progress monitor is shown in the UI thread.
     */
    @Override
    public boolean performFinish() {
        createdProject = createNewProject();

        IWorkingSet[] workingSets = projectPage.getWorkingSets();
        if (workingSets.length > 0) {
            PlatformUI.getWorkbench().getWorkingSetManager().addToWorkingSets(createdProject, workingSets);

            //Workaround to properly show project in Package Explorer: if Top Level Elements are
            //working sets, and the destination working set of the new project is selected, that set
            //must be reselected in order to display the project.
            PydevPackageExplorer pView = (PydevPackageExplorer) PlatformUI.getWorkbench()
                    .getActiveWorkbenchWindow().getActivePage()
                    .findView("org.python.pydev.navigator.view");
            if (pView != null) {
                IWorkingSet[] inputSets = ((PydevCommonViewer) pView.getCommonViewer()).getSelectedWorkingSets();
                if (inputSets != null && inputSets.length == 1) {
                    IWorkingSet inputSet = inputSets[0];
                    if (inputSet != null) {
                        for (IWorkingSet destinationSet : workingSets) {
                            if (inputSet.equals(destinationSet)) {
                                pView.getCommonViewer().setInput(inputSet);
                                break;
                            }
                        }
                    }
                }
            }
        }

        // Switch to default perspective (will ask before changing)
        BasicNewProjectResourceWizard.updatePerspective(fConfigElement);
        BasicNewResourceWizard.selectAndReveal(createdProject, workbench.getActiveWorkbenchWindow());

        return true;
    }

    public IProject getCreatedProject() {
        return createdProject;
    }

    /**
     * Set Python logo to top bar
     */
    protected void initializeDefaultPageImageDescriptor() {
        ImageDescriptor desc = PydevPlugin
                .imageDescriptorFromPlugin(PydevPlugin.getPluginID(), "icons/python_logo.png");//$NON-NLS-1$
        setDefaultPageImageDescriptor(desc);
    }

    @Override
    public void setInitializationData(IConfigurationElement config, String propertyName, Object data)
            throws CoreException {
        this.fConfigElement = config;
    }
}
