/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2011 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.addon.deploy.impl.deployable;

import org.apache.felix.utils.version.VersionRange;
import org.osgi.framework.Version;
import org.ow2.jonas.addon.deploy.api.deployable.IAddonMetadata;
import org.ow2.jonas.addon.deploy.impl.util.AddonUtil;
import org.ow2.jonas.addon.properties.ServicePropertiesImpl;
import org.ow2.jonas.properties.ServiceProperties;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

/**
 * {@inheritDoc}
 */
public class AddonMetaData implements IAddonMetadata {

    /**
     * The logger
     */
    private static Log logger = LogFactory.getLog(AddonMetaData.class);

    /**
     * The name of this addon
     */
    private String name;

    /**
     * The description of this addon
     */
    private String description;

    /**
     * The tenant id of this addon
     */
    private String tenantId;

    /**
     * The instance of this addon
     */
    private String instance;

    /**
     * The author of this addon
     */
    private String author;

    /**
     * The licence of this addon
     */
    private String licence;

    /**
     * Indicates a version of JOnAS that this addon can run in.
     */
    private VersionRange jonasVersions;

    /**
     * The autostart property
     */
    private Boolean autostart;

    /**
     * Indicates a list of version of the jvm that this plugin can run in.
     */
    private VersionRange jvmVersions;

    /**
     * Provides property for the resolver
     */
    private List<String> provides;

    /**
     * Requirements property for the resolver
     */
    private List<String> requirements;

    /**
     * Properties of the service (if the addon represents a JOnAS service)
     */
    private ServiceProperties serviceProperties;

    /**
     * The metadata file
     */
    private File metaDataFile;

    /**
     * The name of the JOnAS service associated to the addon. Null if the addon is not a JOnAS service
     */
    private String service;

    /**
     * The name of the implementation of the JOnAS service
     */
    private String implementation;

    /**
     * Default constructor
     */
    public AddonMetaData() {
        this.autostart = false;
        this.provides = new ArrayList<String>();
        this.requirements = new ArrayList<String>();
    }

    /**
     * {@inheritDoc}
     */
    public String getName() {
        return name;
    }

    /**
     * {@inheritDoc}
     */
    public String getDescription() {
        return description;
    }

    /**
     * {@inheritDoc}
     */
    public String getTenantId() {
        return tenantId;
    }

    /**
     * {@inheritDoc}
     */
    public String getInstance() {
        return instance;
    }

    /**
     * {@inheritDoc}
     */
    public String getAuthor() {
        return author;
    }

    /**
     * {@inheritDoc}
     */
    public String getLicence() {
        return licence;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isJOnASVersionSupported(final String jonasVersion) {
        try {
            return isVersionSupported(this.jonasVersions, convert(jonasVersion));
        } catch (Exception e) {
            logger.error("The given version '" + jonasVersion + "'is incorrect. Cannot convert it as a "
                    + Version.class.getName() + " object.", e);
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public Boolean getAutostart() {
        return autostart;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isJvmVersionSupported(final String jvmVersion) {
        try {
            return isVersionSupported(this.jvmVersions, convert(jvmVersion));
        } catch (Exception e) {
            logger.error("The given version '" + jvmVersion + "'is incorrect. Cannot convert it as a "
                    + Version.class.getName() + " object.", e);
            return false;
        }
    }

    /**
     * @param versionRange The {@link VersionRange} as a {@link String}
     * @param version The {@link Version} as a {@link String}
     * @return true if the version is supported
     */
    public static boolean isVersionSupported(final String versionRange, final String version) throws Exception {
        return isVersionSupported(parse(versionRange), convert(version));
    }

    /**
     * @param versionRange The {@link VersionRange}
     * @param version The {@link Version}
     * @return true if the version is supported
     */
    public static boolean isVersionSupported(final VersionRange versionRange, final Version version) {
        if (versionRange != null && version != null) {
            return versionRange.contains(version);
        }  else {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public List<String> getProvides() {
        return provides;
    }

    /**
     * {@inheritDoc}
     */
    public List<String> getRequirements() {
        return requirements;
    }

    /**
     * {@inheritDoc}
     */
    public ServiceProperties getServiceProperties() {
        return this.serviceProperties;
    }

    /**
     * {@inheritDoc}
     */
    public File getMetaDataFile() {
        return this.metaDataFile;
    }

    /**
     * {@inheritDoc}
     */
    public String getService() {
        return service;
    }

    /**
     * {@inheritDoc}
     */
    public String getImplementation() {
        return implementation;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isJOnASService() {
        return this.service != null;
    }

    /**
     * @param name The name of this addon to set
     */
    public void setName(final String name) {
        this.name = name;
    }

    /**
     * @param description the description to set
     */
    public void setDescription(final String description) {
        this.description = description;
    }

    /**
     * @param tenantId the tenant-id to set
     */
    public void setTenantId(final String tenantId) {
        this.tenantId = tenantId;
    }

    /**
     * @param instance the instance to set
     */
    public void setInstance(final String instance) {
        this.instance = instance;
    }

    /**
     * @param author The author to set
     */
    public void setAuthor(final String author) {
        this.author = author;
    }

    /**
     * @param licence The licence to set
     */
    public void setLicence(final String licence) {
        this.licence = licence;
    }

    /**
     * @param jonasVersions Range of jonas versions compatible with this addon  to set
     */
    public void setJonasVersions(final String jonasVersions) throws Exception {
        this.jonasVersions = parse(jonasVersions);
    }

    /*
     * @param autostart Autostart properties to set
     */
    public void setAutostart(final Boolean autostart) {
        this.autostart = autostart;
    }

    /**
     * @param jvmVersions A list of Jvm version to set
     */
    public void setJvmVersions(final String jvmVersions) throws Exception {
        this.jvmVersions = parse(jvmVersions);
    }

    /**
     * @param provides Provides properties to set
     */
    public void setProvides(final String provides) {
        this.provides = AddonUtil.getTokens(provides, PROVIDES_SEPARATOR);
    }

    /**
     * @param requirements Requirements properties to set
     */
    public void setRequirements(final String requirements) {
        this.requirements = AddonUtil.getTokens(requirements, REQUIREMENT_SEPARATOR);
    }

    /*
     * @param properties Properties of the addon to set
     */
    public void setServiceProperties(final Properties properties) {
        this.serviceProperties = new ServicePropertiesImpl(properties, this.service);
    }

    /**
     * @param metaDataFile The metadata file to set
     */
    public void setMetaDataFile(final File metaDataFile) {
        this.metaDataFile = metaDataFile;
    }

    /**
     * @param service the name of the JOnAS service associated to the addon to set
     */
    public void setService(final String service) {
        this.service = service;
    }

    /**
     * @param implementation the name of the implementation of the JOnAS service to set
     */
    public void setImplementation(final String implementation) {
        this.implementation = implementation;
    }

    /**
     * @param range A range as a String
     * @return the {@link VersionRange}
     * @throws Exception
     */
    private static VersionRange parse(final String range) throws Exception {
        if (range.indexOf(',') >= 0) {
            String s = range.substring(1, range.length() - 1);
            String vlo = s.substring(0, s.indexOf(',')).trim();
            String vhi = s.substring(s.indexOf(',') + 1, s.length()).trim();
            return new VersionRange (
                    (range.charAt(0) == '['),
                    convert(vlo),
                    convert(vhi),
                    (range.charAt(range.length() - 1) == ']'));
        } else {
            return new VersionRange(convert(range), true);
        }
    }

    /**
     * @param version An OSGi / Maven version as a String
     * @return the {@link org.osgi.framework.Version}
     * @throws Exception
     */
    private static Version convert(final String version) throws Exception {

        if (!version.isEmpty()) {

            String string = version.replaceAll("_", "-");

            try {
                return Version.parseVersion(string);
            } catch (Exception e) {
                //do nothing

                Integer major = null;
                Integer minor = null;
                Integer micro = null;
                String qualifier = null;

                StringTokenizer stringTokenizer = new StringTokenizer(string, ".");
                int nb = stringTokenizer.countTokens();

                String temp = stringTokenizer.nextToken();
                if (!isDigit(temp)) {
                    throw new NumberFormatException("Invalid major " + temp);
                } else {
                    major = Integer.parseInt(temp);
                }

                if (nb >= 1) {
                    temp = stringTokenizer.nextToken();
                    if (!isDigit(temp)) {
                        throw new NumberFormatException("Invalid minor " + temp);
                    } else {
                        minor = Integer.parseInt(temp);
                    }
                }

                StringBuilder qualifierBuilder = new StringBuilder();

                if (nb >= 2) {
                    temp = stringTokenizer.nextToken();
                    if (!isDigit(temp)) {
                        String replace = temp.replaceFirst("-", ".");
                        StringTokenizer tokenizer = new StringTokenizer(replace, ".");
                        temp = tokenizer.nextToken();
                        if (!isDigit(temp)) {
                            throw new NumberFormatException("Invalid micro " + temp);
                        } else {
                            micro = Integer.parseInt(temp);
                            qualifierBuilder.append(tokenizer.nextToken());
                        }
                    } else {
                        micro = Integer.parseInt(temp);
                    }
                }

                if (nb >= 3) {
                    int i = 3;
                    nb = nb - i;
                    boolean isInit = !qualifierBuilder.toString().isEmpty();

                    while (nb > 0) {

                        if (isInit) {
                            qualifierBuilder.append("-");
                            qualifierBuilder.append(stringTokenizer.nextToken());
                        } else {
                            qualifierBuilder.append(stringTokenizer.nextToken());
                            if (nb > 1) {
                                qualifierBuilder.append("-");
                            }
                        }

                        i++;
                        nb--;
                    }
                    qualifier = qualifierBuilder.toString();
                }

                StringBuilder versionBuilder = new StringBuilder();
                versionBuilder.append(major);
                if (minor != null) {
                    versionBuilder.append(".");
                    versionBuilder.append(minor);
                    if (micro != null) {
                        versionBuilder.append(".");
                        versionBuilder.append(micro);
                    }
                    if (qualifier != null && !qualifier.isEmpty()) {
                        versionBuilder.append(".");
                        versionBuilder.append(qualifier);
                    }
                }
                return Version.parseVersion(versionBuilder.toString());
            }
        }
        return null;
    }

    /**
     * @param string A string
     * @return true if the string is a number
     */
    private static boolean isDigit(final String string) {
        try {
            Integer.parseInt(string);
            return true;
        } catch(NumberFormatException e) {
            return false;
        }
    }
}
