/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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
 *
 *      https://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.apache.ivy.core.module.id;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ivy.core.IvyPatternHelper;

/**
 * Identifies a module, without revision information
 *
 * @see <a href="package-summary.html">org.apache.ivy.core.module.id</a>
 */
public class ModuleId implements Comparable<ModuleId> {

    static final String ENCODE_SEPARATOR = ":#@#:";

    private static final Map<ModuleId, WeakReference<ModuleId>> CACHE = new WeakHashMap<>();

    /**
     * Returns a ModuleId for the given organization and module name.
     *
     * @param org
     *            the module's organization, can be <code>null</code>
     * @param name
     *            the module's name, must not be <code>null</code>
     * @return a ModuleId instance
     */
    public static ModuleId newInstance(String org, String name) {
        return intern(new ModuleId(org, name));
    }

    /**
     * Returns an intern instance of a ModuleId equals to the given ModuleId if any, or the given
     * ModuleId.
     * <p>
     * This is useful to reduce the number of instances of ModuleId kept in memory, and thus reduce
     * memory footprint.
     * </p>
     *
     * @param moduleId
     *            the module id to return
     * @return a unit instance of the given module id.
     */
    public static ModuleId intern(ModuleId moduleId) {
        ModuleId r = null;

        synchronized (CACHE) {
            WeakReference<ModuleId> ref = CACHE.get(moduleId);
            if (ref != null) {
                r = ref.get();
            }
            if (r == null) {
                r = moduleId;
                CACHE.put(r, new WeakReference<>(r));
            }
        }

        return r;
    }

    private String organisation;

    private String name;

    private int hash;

    private Map<String, String> attributes = new HashMap<>();

    /**
     * Constructor.
     *
     * @param organisation
     *            The organisation which creates the module.
     * @param name
     *            The name of the module.
     */
    public ModuleId(String organisation, String name) {
        if (name == null) {
            throw new IllegalArgumentException("null name not allowed");
        }
        this.organisation = organisation;
        this.name = name;
        attributes.put(IvyPatternHelper.ORGANISATION_KEY, organisation);
        attributes.put(IvyPatternHelper.MODULE_KEY, name);
    }

    /**
     * Returns the name of the module.
     *
     * @return The name of the module.
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the name of the organisation.
     *
     * @return The name of the organisation.
     */
    public String getOrganisation() {
        return organisation;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof ModuleId)) {
            return false;
        }
        ModuleId other = (ModuleId) obj;
        return (other.organisation == null)
                ? organisation == null && other.name.equals(name)
                : other.organisation.equals(organisation) && other.name.equals(name);
    }

    @Override
    public int hashCode() {
        if (hash == 0) {
            // CheckStyle:MagicNumber| OFF
            hash = 31;
            hash = hash * 13 + (organisation == null ? 0 : organisation.hashCode());
            hash = hash * 13 + name.hashCode();
            // CheckStyle:MagicNumber| ON
        }
        return hash;
    }

    @Override
    public String toString() {
        return organisation + "#" + name;
    }

    public int compareTo(ModuleId that) {
        int result = organisation.compareTo(that.organisation);
        if (result == 0) {
            result = name.compareTo(that.name);
        }
        return result;
    }

    /**
     * Returns the encoded String representing this ModuleId.
     *
     * @return The ModuleId encoded as String.
     */
    public String encodeToString() {
        return getOrganisation() + ENCODE_SEPARATOR + getName();
    }

    /**
     * Returns a Map of all attributes of this module id. The Map keys are attribute names as
     * Strings, and values are corresponding attribute values (as String too).
     *
     * @return A Map instance containing all the attributes and their values.
     */
    public Map<String, String> getAttributes() {
        return attributes;
    }

    /**
     * Returns a ModuleId
     *
     * @param encoded String
     * @return The new ModuleId.
     * @throws IllegalArgumentException
     *             If the given String could not be decoded.
     */
    public static ModuleId decode(String encoded) {
        String[] parts = encoded.split(ENCODE_SEPARATOR);
        if (parts.length != 2) {
            throw new IllegalArgumentException("badly encoded module id: '" + encoded + "'");
        }
        return new ModuleId(parts[0], parts[1]);
    }

    /**
     * Pattern to use to matched mid text representation.
     *
     * @see #parse(String)
     */
    public static final Pattern MID_PATTERN = Pattern.compile("("
            + ModuleRevisionId.STRICT_CHARS_PATTERN + "*)" + "#("
            + ModuleRevisionId.STRICT_CHARS_PATTERN + "+)");

    /**
     * Parses the module id text representation and returns it as a {@link ModuleId} instance.
     *
     * @param mid
     *            the module id text representation to parse
     * @return the ModuleId instance corresponding to the representation
     * @throws IllegalArgumentException
     *             if the given text representation cannot be parsed
     */
    public static ModuleId parse(String mid) {
        Matcher m = MID_PATTERN.matcher(mid);
        if (!m.matches()) {
            throw new IllegalArgumentException(
                    "module text representation do not match expected pattern." + " given mid='"
                            + mid + "' expected form=" + MID_PATTERN.pattern());
        }

        // CheckStyle:MagicNumber| OFF
        return newInstance(m.group(1), m.group(2));
        // CheckStyle:MagicNumber| ON
    }
}
