/*******************************************************************************
 * Copyright (c) 2008, 2017 Genuitec, LLC and others. 
 *
 * This
 * program and the accompanying materials are made available under the terms of
 * the Eclipse Public License 2.0 which accompanies this distribution, and is
 * available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors: 
 * 		Genuitec, LLC - initial API and implementation
 * 		IBM Corporation - ongoing development
 *      EclipseSource - ongoing development
 ******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;

import java.math.BigInteger;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.eclipse.equinox.p2.metadata.ILicense;

/**
 * The <code>License</code> class represents a software license.  A license has required body text
 * which may be the full text or an annotation.  An optional URL field can be specified
 * which links to full text.  Licenses can be easily compared using their digests.
 */
public class License implements ILicense {
	/**
	 * The <code>body</code> contains the descriptive text for the license. This may
	 * be a summary for a full license specified in a URL.
	 */
	private final String body;

	/**
	 * The <code>location</code> is the URL of the license.
	 */
	private URI location;

	/**
	 * The <code>digest</code> is the cached message digest of the normalized body
	 */
	private String digest;

	/**
	 * Creates a new license object which is identified by users using the <code>body</code> field.
	 * The body should contain either the full text of the license or an summary for a license
	 * fully specified in the given location.
	 * 
	 * @param location the location of a document containing the full license, or <code>null</code>
	 * @param body the license body, cannot be <code>null</code>
	 * @throws IllegalArgumentException when the <code>body</code> is <code>null</code>
	 */
	public License(URI location, String body, String uuid) {
		if (body == null)
			throw new IllegalArgumentException("body cannot be null"); //$NON-NLS-1$
		this.body = body;
		this.location = location;
		this.digest = uuid;
	}

	/**
	 * Returns the location of a document containing the full license.
	 * 
	 * @return the location of the license document, or <code>null</code>
	 */
	@Override
	public URI getLocation() {
		return location;
	}

	/**
	 * Returns the license body.
	 * @return the license body, never <code>null</code>
	 */
	@Override
	public String getBody() {
		return body;
	}

	/**
	 * Returns the message digest of the license body.  The digest is calculated on a normalized
	 * version of the license where all whitespace has been reduced to one space.
	 * @return the message digest as a <code>BigInteger</code>, never <code>null</code>
	 */
	@Override
	public synchronized String getUUID() {
		if (digest == null)
			digest = calculateLicenseDigest().toString(16);

		return digest;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == this)
			return true;
		if (obj == null)
			return false;
		if (obj instanceof ILicense) {
			ILicense other = (ILicense) obj;
			if (other.getUUID().equals(getUUID()))
				return true;
		}
		return false;
	}

	@Override
	public int hashCode() {
		return getUUID().hashCode();
	}

	private BigInteger calculateLicenseDigest() {
		String message = normalize(getBody());
		try {
			MessageDigest algorithm = MessageDigest.getInstance("MD5"); //$NON-NLS-1$
			algorithm.reset();
			algorithm.update(message.getBytes(StandardCharsets.UTF_8));
			byte[] digestBytes = algorithm.digest();
			return new BigInteger(1, digestBytes);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Replace all sequences of whitespace with a single whitespace character.
	 */
	private String normalize(String license) {
		String text = license.trim();
		StringBuilder result = new StringBuilder();
		int length = text.length();
		for (int i = 0; i < length; i++) {
			char c = text.charAt(i);
			boolean foundWhitespace = false;
			while (Character.isWhitespace(c) && i < length) {
				foundWhitespace = true;
				c = text.charAt(++i);
			}
			if (foundWhitespace)
				result.append(' ');
			if (i < length)
				result.append(c);
		}
		return result.toString();
	}
}
