package bndtools.editor.contents;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

import org.bndtools.api.ILogger;
import org.bndtools.api.Logger;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.osgi.framework.Version;

import aQute.lib.io.IO;

/**
 * Models the three available styles for versioning exported packages.
 */
public enum PackageInfoStyle {

    /**
     * The legacy, flat file format supported by bnd. This is still used for Java 4 (and earlier) projects).
     */
    Legacy("packageinfo", "version %s%n", null),

    /**
     * Annotated package-info.java, using the bnd annotations.
     */
    BndAnnotation("package-info.java", "@aQute.bnd.annotation.Version(\"%s\")%npackage %s;%n", "aQute.bnd.annotation.Version"),

    /**
     * Annotated package-info.java, using the OSGi spec annotations from R6.
     */
    SpecAnnotation("package-info.java", "@org.osgi.annotation.versioning.Version(\"%s\")%npackage %s;%n", "org.osgi.annotation.versioning.Version");

    private static final ILogger logger = Logger.getLogger(PackageInfoStyle.class);

    private static final Set<String> PRE_JAVA5_VERSIONS;

    static {
        PRE_JAVA5_VERSIONS = new HashSet<String>();
        PRE_JAVA5_VERSIONS.add(JavaCore.VERSION_CLDC_1_1);
        PRE_JAVA5_VERSIONS.add(JavaCore.VERSION_1_1);
        PRE_JAVA5_VERSIONS.add(JavaCore.VERSION_1_2);
        PRE_JAVA5_VERSIONS.add(JavaCore.VERSION_1_3);
        PRE_JAVA5_VERSIONS.add(JavaCore.VERSION_1_4);
    }

    private final String fileName;
    private final String contentPattern;
    private final String annotationTypeName;
    private final Pattern searchRegex;

    PackageInfoStyle(String fileName, String contentPattern, String annotationTypeName) {
        this.fileName = fileName;
        this.contentPattern = contentPattern;
        this.annotationTypeName = annotationTypeName;
        this.searchRegex = annotationTypeName != null ? Pattern.compile("\\Q" + annotationTypeName + "\\E") : null;
    }

    public String getFileName() {
        return fileName;
    }

    public String format(Version version, String packageName) {
        return String.format(contentPattern, version, packageName);
    }

    public static PackageInfoStyle findExisting(File dir) throws IOException {
        File packageInfoJava = new File(dir, SpecAnnotation.fileName);
        if (packageInfoJava.exists()) {
            String content = IO.collect(packageInfoJava);
            if (SpecAnnotation.searchRegex.matcher(content)
                .find())
                return SpecAnnotation;
            if (BndAnnotation.searchRegex.matcher(content)
                .find())
                return BndAnnotation;
            return null;
        }

        File legacyPackageInfo = new File(dir, Legacy.fileName);
        if (legacyPackageInfo.exists())
            return Legacy;

        return null;
    }

    public static PackageInfoStyle calculatePackageInfoStyle(IProject project) {
        IJavaProject javaProject = JavaCore.create(project);
        if (javaProject == null)
            return Legacy;

        // JDK Compliance and Java Source level must both be at least 1.5 to support
        // annotated package-info.java
        String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
        if (compliance == null || PRE_JAVA5_VERSIONS.contains(compliance))
            return Legacy;
        String sourceLevel = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
        if (sourceLevel == null || PRE_JAVA5_VERSIONS.contains(sourceLevel))
            return Legacy;

        try {
            IType specAnnotation = javaProject.findType(SpecAnnotation.annotationTypeName);
            if (specAnnotation != null)
                return SpecAnnotation;
            IType bndAnnotation = javaProject.findType(BndAnnotation.annotationTypeName);
            if (bndAnnotation != null)
                return BndAnnotation;
        } catch (JavaModelException e) {
            logger.logError("Could not determine classpath visibility for versioning annotations.", e);
        }
        return Legacy;
    }

}
