/*
 * Copyright 2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * 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 org.gradle.api.internal.plugins.osgi;

import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.plugins.BasePluginConvention;

import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Hans Dockter
 */
public class OsgiHelper {
    /**
     * Bundle-Version must match this pattern
     */
    private static final Pattern OSGI_VERSION_PATTERN = Pattern.compile("[0-9]+(\\.[0-9]+(\\.[0-9]+(\\.[0-9A-Za-z_-]+)?)?)?");

    private static final Pattern ONLY_NUMBERS = Pattern.compile("[0-9]+");
    private static final Pattern QUALIFIER = Pattern.compile("[0-9A-Za-z_\\-]*");

    /**
     * Get the symbolic name as group + "." + archivesBaseName, with the following exceptions
     * <ul>
     * <li>
     * if group has only one section (no dots) and archivesBaseName is not null then the first package
     * name with classes is returned. eg. commons-logging:commons-logging -> org.apache.commons.logging
     * </li>
     * <li>
     * if archivesBaseName is equal to last section of group then group is returned.
     * eg. org.gradle:gradle -> org.gradle
     * </li>
     * <li>
     * if archivesBaseName starts with last section of group that portion is removed.
     * eg. org.gradle:gradle-core -> org.gradle.core
     * </li>
     * <li>
     * if archivesBaseName starts with the full group, the archivesBaseName is return,
     * e.g. org.gradle:org.gradle.core -> org.gradle.core
     * </li>
     * </ul>
     *
     * @param project The project being processed.
     *
     * @return Returns the SymbolicName that should be used for the bundle.
     */
    public String getBundleSymbolicName(Project project) {
        String group = project.getGroup().toString();
        String archiveBaseName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName();
        if (archiveBaseName.startsWith(group)) {
            return archiveBaseName;
        }
        int i = group.lastIndexOf('.');
        String lastSection = group.substring(++i);
        if (archiveBaseName.equals(lastSection)) {
            return group;
        }
        if (archiveBaseName.startsWith(lastSection)) {
            String artifactId = archiveBaseName.substring(lastSection.length());
            if (Character.isLetterOrDigit(artifactId.charAt(0))) {
                return getBundleSymbolicName(group, artifactId);
            } else {
                return getBundleSymbolicName(group, artifactId.substring(1));
            }
        }
        return getBundleSymbolicName(group, archiveBaseName);
    }

    private String getBundleSymbolicName(String groupId, String artifactId) {
        return groupId + "." + artifactId;
    }

    public String getVersion(String version) {

        /* if it's already OSGi compliant don't touch it */
        final Matcher m = OSGI_VERSION_PATTERN.matcher(version);
        if (m.matches()) {
            return version;
        }

        int group = 0;
        boolean groupToken = true;
        String[] groups = new String[4];
        groups[0] = "0";
        groups[1] = "0";
        groups[2] = "0";
        groups[3] = "";
        StringTokenizer st = new StringTokenizer(version, ",./;'?:\\|=+-_*&^%$#@!~", true);
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            if (groupToken) {
                if (group < 3) {
                    if (ONLY_NUMBERS.matcher(token).matches()) {
                        groups[group++] = token;
                        groupToken = false;
                    } else {
                        // if not a number, i.e. 2.ABD
                        groups[3] = token + fillQualifier(st);
                    }
                } else {
                    // Last group; what ever is left take that replace all characters that are not alphanum or '_' or '-'
                    groups[3] = token + fillQualifier(st);
                }
            } else {
                // If a delimiter; if dot, swap to groupToken, otherwise the rest belongs in qualifier.
                if (".".equals(token)) {
                    groupToken = true;
                } else {
                    groups[3] = fillQualifier(st);
                }
            }
        }
        String ver = groups[0] + "." + groups[1] + "." + groups[2];
        String result;
        if (groups[3].length() > 0) {
            result = ver + "." + groups[3];
        } else {
            result = ver;
        }
        if (!OSGI_VERSION_PATTERN.matcher(result).matches()) {
            throw new GradleException("OSGi plugin unable to convert version to a compliant version");
        }
        return result;
    }

    private String fillQualifier(StringTokenizer st) {
        StringBuilder buf = new StringBuilder();
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            if (QUALIFIER.matcher(token).matches()) {
                buf.append(token);
            } else {
                buf.append("_");
            }
        }
        return buf.toString();
    }
}
