/**
 * Copyright (c) 2009 Symbian Foundation and/or its subsidiary(-ies).
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of the License "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Symbian Foundation - initial contribution.
 * Contributors:
 * Description:
 * Overview:
 * Details:
 * Platforms/Drives/Compatibility:
 * Assumptions/Requirement/Pre-requisites:
 * Failures and causes:
 */
package org.symbian.tools.wrttools.util;

import java.io.File;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
import java.util.zip.ZipEntry;

import javax.swing.filechooser.FileSystemView;

import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.internal.wizards.datatransfer.TarEntry;
import org.eclipse.ui.part.ISetSelectionTarget;
import org.eclipse.wst.jsdt.core.IIncludePathEntry;
import org.eclipse.wst.jsdt.core.IJavaScriptProject;
import org.eclipse.wst.jsdt.core.JavaScriptCore;
import org.eclipse.wst.jsdt.core.LibrarySuperType;
import org.eclipse.wst.jsdt.internal.core.JavaProject;
import org.eclipse.wst.jsdt.internal.ui.wizards.buildpaths.BuildPathsBlock;
import org.eclipse.wst.validation.ValidationFramework;
import org.symbian.tools.wrttools.Activator;
import org.symbian.tools.wrttools.WidgetProjectNature;
import org.symbian.tools.wrttools.wizards.WrtLibraryWizardPage;

@SuppressWarnings("restriction")
public final class ProjectUtils {
    public static final String EXCLUDE_MARKER_ID = "org.symbian.tools.wrttools.excluded";

    private static final class FocusOnProjectJob extends Job {

        private final Display display;
        private final IProject[] projects;

        public FocusOnProjectJob(IProject[] projects, Display display) {
            super("Preparing projects");
            this.projects = projects;
            this.display = display;
            setRule(this.projects.length == 1 ? projects[0] : projects[0].getWorkspace().getRoot());
            setUser(false);
        }

        @Override
        protected IStatus run(IProgressMonitor monitor) {
            try {
                final Collection<IFile> files = new HashSet<IFile>(projects.length);
                for (IProject project : projects) {
                    IFile index = CoreUtil.getIndexFile(project);
                    if (index != null && index.isAccessible()) {
                        files.add(index);
                    }
                }
                final IFile[] filesArray = files.toArray(new IFile[files.size()]);
                if (filesArray.length > 0) {
                    display.asyncExec(new Runnable() {

                        public void run() {
                            IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                                    .getActivePage();
                            IViewReference reference = activePage.findViewReference(IPageLayout.ID_PROJECT_EXPLORER);
                            IWorkbenchPart part = reference.getPart(false);
                            if (part instanceof ISetSelectionTarget) {
                                StructuredSelection selection;
                                if (filesArray.length == 1) {
                                    selection = new StructuredSelection(filesArray[0]);
                                } else {
                                    selection = new StructuredSelection(filesArray);
                                }
                                ((ISetSelectionTarget) part).selectReveal(selection);
                            }
                            try {
                                IDE.openEditors(activePage, filesArray);
                                ResourcesPlugin.getWorkspace().run(new TouchAllResources(projects),
                                        new NullProgressMonitor());
                            } catch (CoreException e) {
                                Activator.log(e);
                            }
                        }
                    });
                }
            } catch (CoreException e) {
                Activator.log(e);
            }
            return Status.OK_STATUS;
        }
    }

    private static final class TouchAllResources implements IWorkspaceRunnable {
        private final IProject[] projects;

        public TouchAllResources(IProject[] projects) {
            this.projects = projects;

        }

        public void run(IProgressMonitor monitor) throws CoreException {
            for (IProject project : projects) {
                project.accept(new IResourceVisitor() {
                    public boolean visit(IResource resource) throws CoreException {
                        if (resource.isAccessible() && resource.getType() == IResource.FILE
                                && resource.getFileExtension().equals("js")) {
                            resource.touch(new NullProgressMonitor());
                        }
                        return true;
                    }
                });
            }
        }
    }

    private static final String DEFAULT_APTANA_WORKSPACE = "Aptana Studio Workspace";
    private static final Collection<String> EXCLUDED;

    public static final String PREVIEW_FOLDER = "preview";

    public static final String PREVIEW_FRAME_FILE = "wrt_preview_frame.html";

    public static final String PREVIEW_MAIN_FILE = "wrt_preview_main.html";

    static {
        EXCLUDED = new TreeSet<String>(Arrays.asList(".project", ProjectUtils.PREVIEW_FOLDER,
                ProjectUtils.PREVIEW_FRAME_FILE, ProjectUtils.PREVIEW_MAIN_FILE));
    }

    private static boolean accepted(Object object) {
        final String name;
        if (object instanceof ZipEntry) {
            name = ((ZipEntry) object).getName();
        } else if (object instanceof TarEntry) {
            name = ((TarEntry) object).getName();
        } else if (object instanceof File) {
            name = ((File) object).getAbsolutePath();
        } else {
            throw new IllegalArgumentException("Unforeseen entry type: " + object.getClass());
        }
        IPath path = new Path(name);
        return isValidProjectFile(path.lastSegment());
    }

    public static void addWrtNature(IProject project) {
        if (!hasWrtNature(project)) {
            try {
                IProjectDescription description = project.getDescription();
                String[] natureIds = description.getNatureIds();
                String[] newNatures = new String[natureIds.length + 1];
                System.arraycopy(natureIds, 0, newNatures, 1, natureIds.length);
                newNatures[0] = WidgetProjectNature.ID;
                description.setNatureIds(newNatures);

                ICommand[] buildSpec = description.getBuildSpec();
                for (int i = 0; i < buildSpec.length; i++) {
                    ICommand command = buildSpec[i];
                    if (JavaScriptCore.BUILDER_ID.equals(command.getBuilderName())) {
                        buildSpec[i] = buildSpec[buildSpec.length - 1];
                        buildSpec[buildSpec.length - 1] = command;
                        description.setBuildSpec(buildSpec);
                        break;
                    }
                }

                project.setDescription(description, new NullProgressMonitor());
            } catch (CoreException e) {
                Activator.log(e);
            }
        }
    }

    public static IProject createWrtProject(String name, URI uri, IProgressMonitor monitor) throws CoreException {
        uri = isDefaultProjectLocation(uri) ? null : uri;
        monitor.beginTask("Create project resources", 30);
        IWorkspace workspace = ResourcesPlugin.getWorkspace();
        IProject project = workspace.getRoot().getProject(name);
        BuildPathsBlock.createProject(project, uri, new SubProgressMonitor(monitor, 10));
        BuildPathsBlock.addJavaNature(project, new SubProgressMonitor(monitor, 10));

        ValidationFramework.getDefault().addValidationBuilder(project);
        ValidationFramework.getDefault().applyChanges(ValidationFramework.getDefault().getProjectSettings(project),
                true);

        IJavaScriptProject jsProject = JavaScriptCore.create(project);
        jsProject.setRawIncludepath(null, new SubProgressMonitor(monitor, 5)); // See bug 3037
        final IIncludePathEntry[] includepath = jsProject.getRawIncludepath();
        final IIncludePathEntry[] newIncludePath = new IIncludePathEntry[includepath.length + 1];

        System.arraycopy(includepath, 0, newIncludePath, 0, includepath.length);
        newIncludePath[includepath.length] = JavaScriptCore.newContainerEntry(new Path(
                WrtLibraryWizardPage.CONTAINER_ID));

        jsProject.setRawIncludepath(newIncludePath, new SubProgressMonitor(monitor, 5));

        LibrarySuperType superType = new LibrarySuperType(new Path(WrtLibraryWizardPage.CONTAINER_ID), jsProject,
                "Window");
        ((JavaProject) jsProject).setCommonSuperType(superType);

        addWrtNature(project);

        excludeResources(project);

        monitor.done();
        return project;
    }

    private static void excludeResources(IProject project) {
        IFile file = project.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
        if (file.exists()) {
            exclude(file);
        }
        IFolder settings = project.getFolder(".settings");
        if (settings.exists()) {
            excludeFolder(settings);
            exclude(settings);
        }
    }

    private static void excludeFolder(IFolder folder) {
        exclude(folder);
        try {
            IResource[] members = folder.members();
            for (IResource resource : members) {
                if (resource.getType() == IResource.FOLDER) {
                    excludeFolder((IFolder) resource);
                } else {
                    exclude(resource);
                }
            }
        } catch (CoreException e) {
            Activator.log(e);
        }
    }

    public static List<Object> filterExternalProjectEntries(List<Object> fileSystemObjects) {
        List<Object> result = new LinkedList<Object>();
        for (Object object : fileSystemObjects) {
            if (accepted(object)) {
                result.add(object);
            }
        }
        return result;
    }

    public static void focusOn(IProject... projects) {
        new FocusOnProjectJob(projects, Display.getCurrent()).schedule(50);
    }

    public static String getDefaultAptanaLocation() {
        File myDocuments = FileSystemView.getFileSystemView().getDefaultDirectory();
        File file = new File(myDocuments, DEFAULT_APTANA_WORKSPACE); // Windows
        if (file.exists()) {
            return file.getAbsolutePath();
        }
        file = new File(myDocuments, "Documents" + File.separator + DEFAULT_APTANA_WORKSPACE); // Mac OS X
        if (file.exists()) {
            return file.getAbsolutePath();
        }
        return "";
    }

    public static boolean hasWrtNature(IProject project) {
        try {
            return project.hasNature(WidgetProjectNature.ID);
        } catch (CoreException e) {
            Activator.log(e);
            return false;
        }
    }

    public static File isWrtProject(File[] contents) {
        for (int i = 0; i < contents.length; i++) {
            File file = contents[i];
            if (file.isFile() && file.getName().equalsIgnoreCase(CoreUtil.METADATA_FILE)) {
                return file.getParentFile();
            }
        }
        return null;
    }

    private static boolean isDefaultProjectLocation(URI uri) {
        if (uri == null) {
            return true;
        }
        File file = new File(uri);
        IPath project = new Path(file.getAbsolutePath());
        IPath workspace = ResourcesPlugin.getWorkspace().getRoot().getLocation();
        return workspace.isPrefixOf(project);
    }

    public static boolean isPlist(IResource resource) {
        return resource.getType() == IResource.FILE && resource.getName().equalsIgnoreCase(CoreUtil.METADATA_FILE);
    }

    private static boolean isValidProjectFile(String fileName) {
        return !EXCLUDED.contains(fileName);
    }

    public static boolean isExcluded(IResource resource) {
        if (resource.getType() == IResource.PROJECT || resource.getType() == IResource.ROOT) {
            return false;
        }
        if (!resource.exists()) {
            return false;
        }
        try {
            IMarker[] markers = resource.findMarkers(EXCLUDE_MARKER_ID, false, IResource.DEPTH_ZERO);
            IPath path = resource.getProjectRelativePath();
            return markers.length != 0 || (path.segmentCount() > 1 && ".settings".equals(path.segment(0)))
                    || isExcluded(resource.getParent());
        } catch (CoreException e) {
            Activator.log(e);
            return false;
        }

    }

    public static void exclude(IResource resource) {
        try {
            resource.createMarker(EXCLUDE_MARKER_ID);
        } catch (CoreException e) {
            Activator.log(e);
        }
    }

    public static void include(IResource resource) {
        try {
            IMarker[] markers = resource.findMarkers(EXCLUDE_MARKER_ID, false, IResource.DEPTH_ZERO);
            for (IMarker marker : markers) {
                marker.delete();
            }
        } catch (CoreException e) {
            Activator.log(e);
        }
    }

    private ProjectUtils() {
        // Do not instantiate
    }
}
