//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 IBM Corporation and others.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// Contributors:
// IBM Corporation - initial implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.common.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;

/**
 * Implements a utility class for managing URLs and URIs.
 * 
 * @author Kelvin Low
 * @since 1.0
 */
public class NetUtil {

	/**
	 * FILE scheme.
	 */
	public final static String FILE_SCHEME = "file"; //$NON-NLS-1$

	/**
	 * File URI prefix.
	 */
	public final static String FILE_URI_PREFIX = FILE_SCHEME + ":/"; //$NON-NLS-1$

	/**
	 * File URI prefix size.
	 */
	public final static int FILE_URI_PREFIX_LENGTH = FILE_URI_PREFIX.length();

	/**
	 * HTTP scheme.
	 */
	public final static String HTTP_SCHEME = "http"; //$NON-NLS-1$

	/**
	 * HTTP URI prefix.
	 */
	public final static String HTTP_URI_PREFIX = HTTP_SCHEME + "://"; //$NON-NLS-1$

	/**
	 * HTTP URI prefix size.
	 */
	public final static int HTTP_URI_PREFIX_LENGTH = HTTP_URI_PREFIX.length();

	/**
	 * A table of hex values.
	 */
	private final static String[] HEX_VALUES = {
			"%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%28", "%29", "%2A", "%2B", "%2C", "%2D", "%2E", "%2F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%38", "%39", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%48", "%49", "%4A", "%4B", "%4C", "%4D", "%4E", "%4F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%58", "%59", "%5A", "%5B", "%5C", "%5D", "%5E", "%5F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%68", "%69", "%6A", "%6B", "%6C", "%6D", "%6E", "%6F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%78", "%79", "%7A", "%7B", "%7C", "%7D", "%7E", "%7F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
			"%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
	};

	/**
	 * Private constructor to prevent this class from being instantiated. All
	 * methods in this class should be static.
	 */
	private NetUtil() {
	}

	/**
	 * Returns the URI for the given file.
	 * 
	 * @param file
	 *            The input file.
	 * @return The URI for the given file.
	 * @throws MalformedURLException
	 *             if an error occur while constructing the URI for the given
	 *             file.
	 */
	public static String getUri(File file) throws MalformedURLException {
		String url = file.toURL().toExternalForm();
		StringBuffer strBuf = new StringBuffer();
		int urlLength = url.length();
		for (int i = 0; i < urlLength; i++) {
			char ch = url.charAt(i);
			switch (ch) {
			case ' ':
				strBuf.append("%20"); //$NON-NLS-1$
				break;
			default:
				strBuf.append(ch);
				break;
			}
		}
		return strBuf.toString();
	}

	/**
	 * Resolves the given URI using the given the base URI.
	 * 
	 * @param uri
	 *            The URI to resolve.
	 * @param baseUri
	 *            The base URI.
	 * @return A fully formed URI.
	 */
	public static String resolveUri(String uri, String baseUri) {
		if (uri == null) {
			return null;
		}

		if (uri.startsWith("../")) { //$NON-NLS-1$
			if (baseUri.endsWith("/")) { //$NON-NLS-1$
				baseUri = baseUri.substring(0, baseUri.length() - 1);
			}
			while (uri.startsWith("../")) { //$NON-NLS-1$
				uri = uri.substring(3);
				int index = baseUri.lastIndexOf('/');
				if (index > 0) {
					baseUri = baseUri.substring(0, index);
				}
			}
			uri = "/" + uri; //$NON-NLS-1$
		}

		if (uri.startsWith("/")) { //$NON-NLS-1$
			return baseUri.endsWith("/") //$NON-NLS-1$
					? baseUri + uri.substring(1) : baseUri + uri;
		}

		if (uri.startsWith(FILE_URI_PREFIX) || uri.startsWith(HTTP_URI_PREFIX)) {
			return uri;
		}

		return baseUri.endsWith("/") ? baseUri + uri : baseUri + '/' + uri; //$NON-NLS-1$
	}

	/**
	 * Returns the input stream for the given URI.
	 * 
	 * @param uri
	 *            The source URI.
	 * @return The input stream for the given URI.
	 * @throw MalformedURLException if a given XML document URI is invalid.
	 * @throw IOException if an I/O error occur while accessing the URI.
	 */
	public static InputStream getInputStream(String uri)
			throws MalformedURLException, IOException {
		if (uri == null) {
			return null;
		}

		if (uri.startsWith(HTTP_URI_PREFIX)) {
			URL url = new URL(uri);
			return url.openStream();
		} else if (uri.startsWith(FILE_URI_PREFIX)) {
			uri = uri.substring(FILE_URI_PREFIX_LENGTH);
		}

		return new FileInputStream(NetUtil.decodeUrl(uri, null));
	}

	/**
	 * Returns the Java string represention (encoded in UTF-16) of the given URL
	 * (encoded in the given encoding and ASCII-escaped).
	 * 
	 * @param url
	 *            The URL to decode.
	 * @param encoding
	 *            The encoding of the URL.
	 * @return The Java UTF-16 string respresentation.
	 * @throws IllegalArgumentException
	 *             if the given URL contain improperly escaped characters.
	 */
	public static String decodeUrl(String url, String encoding) {
		if (url == null) {
			return url;
		}
		
		int len = url.length();
		if (len == 0) {
			return url;
		}

		// Unescape the url.
		StringBuffer strBuf = new StringBuffer();
		for (int i = 0; i < len; i++) {
			char ch = url.charAt(i);
			switch (ch) {
			case '+':
				strBuf.append(' ');
				break;
			case '%':
				try {
					strBuf.append((char) Integer.parseInt(url.substring(i + 1,
							i + 3), 16));
				} catch (NumberFormatException e) {
					throw new IllegalArgumentException();
				}
				i += 2;
				break;
			default:
				strBuf.append(ch);
				break;
			}
		}

		// Convert the un-escaped byte values to Java UTF-16 string.
		String result = strBuf.toString();
		if (encoding != null) {
			try {
				byte[] bytes = result.getBytes("8859_1"); //$NON-NLS-1$
				result = new String(bytes, encoding);
			} catch (UnsupportedEncodingException e) {
			}
		}

		return result;
	}

	/**
	 * Returns the ASCII-escaped representation (encoded in the specified
	 * encoding) of the given URL (encoded in UTF-16).
	 * 
	 * @param url
	 *            The URL to encode.
	 * @param encoding
	 *            The encoding of the URL.
	 * @return the ASCII-escaped respresentation.
	 * @throws IllegalArgumentException
	 *             if the given URL contain improperly escaped characters.
	 * @throws UnsupportedEncodingException
	 *             if the given coding is unsupport.
	 */
	public static String encodeUrl(String url, String encoding)
			throws UnsupportedEncodingException {
		if (url == null) {
			return url;
		}		
		int len = url.length();
		if (len == 0) {
			return url;
		}

		StringBuffer result = new StringBuffer();

		byte[] bytes = url.getBytes(encoding);
		for (int i = 0; i < bytes.length; i++) {
			char ch = (char) bytes[i];

			if (ch >= 'a' && ch <= 'z') {
				result.append(ch);
			} else if (ch >= 'A' && ch <= 'Z') {
				result.append(ch);
			} else if (ch >= '0' && ch <= '9') {
				result.append(ch);
			} else {
				switch (ch) {
				case '-':
				case '_':
				case '.':
				case '!':
				case '~':
				case '*':
				case '\'':
				case '(':
				case ')':
					result.append(ch);
					break;
				default:
					result.append(HEX_VALUES[ch & 0xFF]);
					break;
				}
			}
		}

		return result.toString();
	}

	/**
	 * Returns the ASCII-escaped representation of the given file URL.
	 * 
	 * @param fileURL
	 *            The file URL to encode.
	 * @return the ASCII-escaped respresentation.
	 */
	public static String encodeFileURL(String fileURL) {
		String url = fileURL;
		StringBuffer strBuf = new StringBuffer();
		int urlLength = url.length();
		for (int i = 0; i < urlLength; i++) {
			char ch = url.charAt(i);
			switch (ch) {
			case ' ':
				strBuf.append("%20"); //$NON-NLS-1$
				break;
			default:
				strBuf.append(ch);
				break;
			}
		}
		return strBuf.toString();
	}

	/**
	 * Returns the ASCII-escaped representation of the given file URL.
	 * 
	 * @param fileURL
	 *            The file URL to encode.
	 * @return the ASCII-escaped respresentation.
	 */
	public static String decodedFileUrl(String fileURL) {
		String url = fileURL;
		StringBuffer strBuf = new StringBuffer();
		int urlLength = url.length();
		for (int i = 0; i < urlLength; i++) {
			char ch = url.charAt(i);
			switch (ch) {
			case '%':
				try {
					strBuf.append((char) Integer.parseInt(url.substring(i + 1,
							i + 3), 16));
				} catch (NumberFormatException e) {
					throw new IllegalArgumentException();
				}
				i += 2;
				break;
			default:
				strBuf.append(ch);
				break;
			}
		}
		return strBuf.toString();
	}
	
	public static String decodeURL (String url) throws UnsupportedEncodingException {
//		url = url.replaceAll("\\+", "%2B"); //$NON-NLS-1$
		url = URLDecoder.decode(url, "UTF-8"); //$NON-NLS-1$
		return url;
	}

}