package org.eclipse.equinox.internal.p2.core.helpers;

/*******************************************************************************
 * Copyright (c) 2003, 2007 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 API and implementation
 *******************************************************************************/

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.BundleException;

/**
 * Headers classes. This class implements a Dictionary that has
 * the following behaviour:
 * <ul>
 * <li>put and remove clear throw UnsupportedOperationException.
 * The Dictionary is thus read-only to others.
 * <li>The String keys in the Dictionary are case-preserved,
 * but the get operation is case-insensitive.
 * </ul>
 * TODO: This class is copied from org.eclipse.osgi due to access restrictions. We
 * can't make this API so we need to find a place for it.
 * @since 3.1
 */
public class Headers extends Dictionary implements Map {
	private boolean readOnly = false;
	private Object[] headers;
	private Object[] values;
	private int size = 0;

	/**
	 * Create an empty Headers dictionary.
	 *
	 * @param initialCapacity The initial capacity of this Headers object.
	 */
	public Headers(int initialCapacity) {
		super();
		headers = new Object[initialCapacity];
		values = new Object[initialCapacity];
	}

	/**
	 * Create a Headers dictionary from a Dictionary.
	 *
	 * @param values The initial dictionary for this Headers object.
	 * @exception IllegalArgumentException If a case-variant of the key is
	 * in the dictionary parameter.
	 */
	public Headers(Dictionary values) {
		this(values.size());
		/* initialize the headers and values */
		Enumeration keys = values.keys();
		while (keys.hasMoreElements()) {
			Object key = keys.nextElement();
			set(key, values.get(key));
		}
	}

	/**
	 * Case-preserved keys.
	 */
	public synchronized Enumeration keys() {
		return new ArrayEnumeration(headers, size);
	}

	/**
	 * Values.
	 */
	public synchronized Enumeration elements() {
		return new ArrayEnumeration(values, size);
	}

	private int getIndex(Object key) {
		boolean stringKey = key instanceof String;
		for (int i = 0; i < size; i++) {
			if (stringKey && (headers[i] instanceof String)) {
				if (((String) headers[i]).equalsIgnoreCase((String) key))
					return i;
			} else {
				if (headers[i].equals(key))
					return i;
			}
		}
		return -1;
	}

	private Object remove(int remove) {
		Object removed = values[remove];
		for (int i = remove; i < size; i++) {
			if (i == headers.length - 1) {
				headers[i] = null;
				values[i] = null;
			} else {
				headers[i] = headers[i + 1];
				values[i] = values[i + 1];
			}
		}
		if (remove < size)
			size--;
		return removed;
	}

	private void add(Object header, Object value) {
		if (size == headers.length) {
			// grow the arrays
			Object[] newHeaders = new Object[headers.length + 10];
			Object[] newValues = new Object[values.length + 10];
			System.arraycopy(headers, 0, newHeaders, 0, headers.length);
			System.arraycopy(values, 0, newValues, 0, values.length);
			headers = newHeaders;
			values = newValues;
		}
		headers[size] = header;
		values[size] = value;
		size++;
	}

	/**
	 * Support case-insensitivity for keys.
	 *
	 * @param key name.
	 */
	public synchronized Object get(Object key) {
		int i = -1;
		if ((i = getIndex(key)) != -1)
			return values[i];
		return null;
	}

	/**
	 * Set a header value or optionally replace it if it already exists.
	 *
	 * @param key Key name.
	 * @param value Value of the key or null to remove key.
	 * @param replace A value of true will allow a previous
	 * value of the key to be replaced.  A value of false 
	 * will cause an IllegalArgumentException to be thrown 
	 * if a previous value of the key exists.
	 * @return the previous value to which the key was mapped,
	 * or null if the key did not have a previous mapping.
	 *
	 * @exception IllegalArgumentException If a case-variant of the key is
	 * already present.
	 * @since 3.2
	 */
	public synchronized Object set(Object key, Object value, boolean replace) {
		if (readOnly)
			throw new UnsupportedOperationException();
		if (key instanceof String)
			key = ((String) key).intern();
		int i = getIndex(key);
		if (value == null) { /* remove */
			if (i != -1)
				return remove(i);
		} else { /* put */
			if (i != -1) { /* duplicate key */
				if (!replace)
					throw new IllegalArgumentException("HEADER_DUPLICATE_KEY_EXCEPTION: " + key); //$NON-NLS-1$
				Object oldVal = values[i];
				values[i] = value;
				return oldVal;
			}
			add(key, value);
		}
		return null;
	}

	/**
	 * Set a header value.
	 *
	 * @param key Key name.
	 * @param value Value of the key or null to remove key.
	 * @return the previous value to which the key was mapped,
	 * or null if the key did not have a previous mapping.
	 *
	 * @exception IllegalArgumentException If a case-variant of the key is
	 * already present.
	 */
	public synchronized Object set(Object key, Object value) {
		return set(key, value, false);
	}

	public synchronized void setReadOnly() {
		readOnly = true;
	}

	/**
	 * Returns the number of entries (distinct keys) in this dictionary.
	 *
	 * @return  the number of keys in this dictionary.
	 */
	public synchronized int size() {
		return size;
	}

	/**
	 * Tests if this dictionary maps no keys to value. The general contract
	 * for the <tt>isEmpty</tt> method is that the result is true if and only
	 * if this dictionary contains no entries.
	 *
	 * @return  <code>true</code> if this dictionary maps no keys to values;
	 *          <code>false</code> otherwise.
	 */
	public synchronized boolean isEmpty() {
		return size == 0;
	}

	/**
	 * Always throws UnsupportedOperationException.
	 *
	 * @param key header name.
	 * @param value header value.
	 * @throws UnsupportedOperationException
	 */
	public synchronized Object put(Object key, Object value) {
		if (readOnly)
			throw new UnsupportedOperationException();
		return set(key, value, true);
	}

	/**
	 * Always throws UnsupportedOperationException.
	 *
	 * @param key header name.
	 * @throws UnsupportedOperationException
	 */
	public Object remove(Object key) {
		throw new UnsupportedOperationException();
	}

	public String toString() {
		return (values.toString());
	}

	public static Headers parseManifest(InputStream in) throws BundleException {
		Headers headers = new Headers(10);
		try {
			ManifestElement.parseBundleManifest(in, headers);
		} catch (IOException e) {
			throw new BundleException("Error reading bundle manifest", e);
		}
		headers.setReadOnly();
		return headers;
	}

	class ArrayEnumeration implements Enumeration {
		private Object[] array;
		int cur = 0;

		public ArrayEnumeration(Object[] array, int size) {
			this.array = new Object[size];
			System.arraycopy(array, 0, this.array, 0, this.array.length);
		}

		public boolean hasMoreElements() {
			return cur < array.length;
		}

		public Object nextElement() {
			return array[cur++];
		}
	}

	public synchronized void clear() {
		if (readOnly)
			throw new UnsupportedOperationException();
	}

	public synchronized boolean containsKey(Object key) {
		return getIndex(key) >= 0;
	}

	public boolean containsValue(Object var0) {
		throw new UnsupportedOperationException();
	}

	public Set entrySet() {
		throw new UnsupportedOperationException();
	}

	public Set keySet() {
		throw new UnsupportedOperationException();
	}

	public void putAll(Map var0) {
		throw new UnsupportedOperationException();
	}

	public Collection values() {
		throw new UnsupportedOperationException();
	}
}
