/*******************************************************************************
 * Copyright (c) 2010 Per Kr. Soreide.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Per Kr. Soreide - initial API and implementation
 *******************************************************************************/
package bndtools.release.api;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
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.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.history.IFileHistory;
import org.eclipse.team.core.history.IFileHistoryProvider;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.core.subscribers.Subscriber;
import org.eclipse.team.core.synchronize.SyncInfoSet;

import aQute.bnd.build.Project;
import aQute.bnd.differ.Baseline;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Domain;
import aQute.bnd.osgi.Jar;
import aQute.bnd.service.RepositoryPlugin;
import aQute.bnd.service.diff.Delta;
import aQute.bnd.service.diff.Diff;
import aQute.bnd.service.diff.Diff.Ignore;
import aQute.bnd.service.diff.Type;
import aQute.bnd.version.Version;

public class ReleaseUtils {

    /**
     * @param jar the jar
     * @return the Jar name as it appears in the Repository e.g. bndtools.release-1.0.0.jar
     */
    public static String getJarFileName(Jar jar) {
        try {
            Domain domain = Domain.domain(jar.getManifest());
            return domain.getBundleSymbolicName()
                .getKey() + '-' + stripVersionQualifier(domain.getBundleVersion()) + ".jar";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Strips the qualifier part of a version string.
     * 
     * @param version
     * @return Version string without qualifier
     */
    public static String stripVersionQualifier(String version) {
        Version ver = new Version(version);
        StringBuilder sb = new StringBuilder();
        sb.append(ver.getMajor());
        sb.append('.');
        sb.append(ver.getMinor());
        sb.append('.');
        sb.append(ver.getMicro());
        return sb.toString();
    }

    public static String getBundleSymbolicName(Jar jar) {
        try {
            Domain domain = Domain.domain(jar.getManifest());
            return domain.getBundleSymbolicName()
                .getKey();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    public static String getBundleVersion(Jar jar) {
        try {
            Domain domain = Domain.domain(jar.getManifest());
            return domain.getBundleVersion();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static IFolder getLocalRepoLocation(RepositoryPlugin repository) {
        try {
            Method m = repository.getClass()
                .getMethod("getRoot");
            if (m.getReturnType() == File.class) {
                return (IFolder) toWorkspaceResource((File) m.invoke(repository));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    public static IResource toWorkspaceResource(File workspaceFile) {
        IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace()
            .getRoot();
        IPath workingCopyPath = Path.fromOSString(workspaceFile.getAbsolutePath());
        IResource resource = wsRoot.getContainerForLocation(workingCopyPath);
        if (resource == null) {
            resource = wsRoot.getFileForLocation(workingCopyPath);
        }
        return resource;
    }

    public static IResource toResource(File file) {
        IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace()
            .getRoot();
        IPath workingCopyPath = Path.fromOSString(file.getAbsolutePath());
        IFile resource = wsRoot.getFileForLocation(workingCopyPath);
        return resource;
    }

    public static IProject getProject(Project project) {
        IResource res = toWorkspaceResource(project.getBase());
        if (res == null) {
            return null;
        }
        return res.getProject();
    }

    public static IFile getJarFileLocation(RepositoryPlugin repository, Jar jar) {
        IFolder repoRoot = ReleaseUtils.getLocalRepoLocation(repository);
        String symbName = ReleaseUtils.getBundleSymbolicName(jar);
        String jarName = ReleaseUtils.getJarFileName(jar);

        IFolder repoSymbNameFolder = repoRoot.getFolder(symbName);
        return repoSymbNameFolder.getFile(jarName);
    }

    /**
     * If the bundle repository is shared e.g. CVS, this will return the remote file revision if it exists
     * 
     * @param repository
     * @param jar
     * @return The file revision or <code>null</code> if the bundle repository is not shared or the Jar does not exist
     *         in the remote repository
     */
    public static IFileRevision getTeamRevision(RepositoryPlugin repository, Jar jar) {
        IFolder repoRoot = ReleaseUtils.getLocalRepoLocation(repository);
        IProject repoProject = repoRoot.getProject();

        RepositoryProvider repoProvider = RepositoryProvider.getProvider(repoProject);
        if (repoProvider == null) {
            return null;
        }

        IFile path = getJarFileLocation(repository, jar);

        IFileRevision[] revs = getTeamRevisions(path, IFileHistoryProvider.SINGLE_REVISION, new NullProgressMonitor());
        if (revs == null) {
            return null;
        }
        return revs.length == 0 ? null : revs[0];

    }

    /**
     * Returns the file revisions for the given resource. If the flags contains IFileHistoryProvider.SINGLE_REVISION
     * then only the revision corresponding to the base corresponding to the local resource is fetched. If the flags
     * contains IFileHistoryProvider.SINGLE_LINE_OF_DESCENT the resulting history will be restricted to a single
     * line-of-descent (e.g. a single branch).
     *
     * @param resource
     * @param flags
     * @param monitor
     * @return the file revisions for the given resource
     */
    public static IFileRevision[] getTeamRevisions(IResource resource, int flags, IProgressMonitor monitor) {

        RepositoryProvider provider = RepositoryProvider.getProvider(resource.getProject());
        if (provider == null) {
            return null;
        }

        IFileHistory history = provider.getFileHistoryProvider()
            .getFileHistoryFor(resource, flags, monitor);
        if (history == null) {
            return new IFileRevision[0];
        }
        return history.getFileRevisions();
    }

    /**
     * Checks if everything is in sync except *.bnd & packageinfo files (which could be updated during the release)
     *
     * @param project
     * @param monitor
     * @return true when everything is in sync
     * @throws CoreException
     */
    public static boolean isTeamProjectUpToDate(IProject project, IProgressMonitor monitor) throws CoreException {
        return getTeamOutOfSyncResources(project, monitor).length == 0;
    }

    public static IResource[] getTeamOutOfSyncResources(IProject project, IProgressMonitor monitor) throws CoreException {
        RepositoryProvider provider = RepositoryProvider.getProvider(project);
        if (provider == null) {
            return new IResource[0];
        }
        Subscriber subscriber = provider.getSubscriber();
        subscriber.refresh(new IResource[] {
            project
        }, IResource.DEPTH_INFINITE, monitor);

        SyncInfoSet sis = new SyncInfoSet();
        subscriber.collectOutOfSync(new IResource[] {
            project
        }, IResource.DEPTH_INFINITE, sis, monitor);
        List<IResource> res = new ArrayList<IResource>();
        for (IResource resource : sis.getResources()) {
            if (resource instanceof IFile) {
                IFile file = (IFile) resource;
                if (file.getName()
                    .endsWith(".bnd")
                    || file.getName()
                        .equals("packageinfo")) {
                    continue;
                }
            }
            res.add(resource);
        }
        return res.toArray(new IResource[0]);
    }

    public static String stripInstructions(String header) {
        if (header == null) {
            return null;
        }
        int idx = header.indexOf(';');
        if (idx > -1) {
            return header.substring(0, idx);
        }
        return header;
    }

    public static boolean needsRelease(Baseline baseline) {
        Delta delta = baseline.getDiff()
            .getDelta(new Ignore() {
                @Override
                public boolean contains(Diff diff) {
                    if ("META-INF/MANIFEST.MF".equals(diff.getName())) { //$NON-NLS-1$
                        return true;
                    }
                    if (diff.getType() == Type.HEADER && diff.getName()
                        .startsWith(Constants.BUNDLE_VERSION)) {
                        return true;
                    }
                    return false;
                }
            });
        if (delta != Delta.UNCHANGED && delta != Delta.IGNORED) {
            return true;
        }
        return false;
    }
}
