/******************************************************************************
 * Copyright (c) 2006, 2010 VMware Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
 * is available at http://www.opensource.org/licenses/apache2.0.php.
 * You may elect to redistribute this code under either of these licenses. 
 * 
 * Contributors:
 *   VMware Inc.
 *****************************************************************************/

package org.eclipse.gemini.blueprint.extender.internal.support;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.logging.Log;
import org.osgi.framework.Bundle;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;

/**
 * Map-like class with dedicated support for lazy bundles. Allows execution of operations on active bundles, promoting
 * the lazy ones, if necessary, as fall back.
 * 
 * The class is thread-safe.
 * 
 * @author Costin Leau
 * @param <T> the entity type associated with active bundles
 */
class LazyBundleRegistry<T> {

	/** logger */
	private final Log log;

	/**
	 * A simple condition-like class.
	 * 
	 * @author Costin Leau
	 */
	interface Condition {

		/**
		 * Indicates if the given target bundle passes the condition or not.
		 * 
		 * @param bundle target bundle
		 * @return true if the bundle passes the condition, false otherwise
		 */
		boolean pass(Bundle bundle);
	}

	/**
	 * Activator action performed on lazy bundles upon promotion.
	 * 
	 * @author Costin Leau
	 * @param <V>
	 */
	interface Activator<V> {

		/**
		 * Activates the given target bundle.
		 * 
		 * @param <V> inherited return type
		 * @param bundle target bundle
		 * @return the bundle associated value
		 */
		V activate(Bundle bundle);
	}

	/**
	 * Operation performed all bundles - first active ones, followed by lazy ones (if nothing is found (null is
	 * returned)) by 'activating' them.
	 * 
	 * @author Costin Leau
	 * @param <T>
	 * @param <V>
	 */
	interface Operation<T, V> {

		/**
		 * Acts upon the bundle associated object (given as argument).
		 * 
		 * @param object associated object
		 * @return the result of the operation
		 */
		V operate(T t) throws Exception;
	}

	/** active, valid bundles */
	private final ConcurrentMap<Bundle, T> activeBundles = new ConcurrentHashMap<Bundle, T>(8);
	/** lazy bundles (potentially invalid) */
	private final ConcurrentMap<Bundle, Boolean> lazyBundles = new ConcurrentHashMap<Bundle, Boolean>(8);

	/**
	 * Queue of bundles that have been activated and validated and should be removed from the lazy map. This is needed
	 * so that promoted bundles do not go unseen by threads using the method at that point.
	 */
	private final List<Bundle> promotionQueue = new ArrayList<Bundle>(4);

	/** counter used for determining the promotion thread */
	private volatile AtomicInteger threadCounter = new AtomicInteger();

	private final Condition condition;
	private final Activator<T> activator;

	LazyBundleRegistry(Condition promotionCondition, Activator<T> activator, Log log) {
		this.condition = promotionCondition;
		this.activator = activator;
		this.log = log;
	}

	void add(Bundle bundle, boolean isLazy, boolean applyCondition) {
		if (isLazy) {
			lazyBundles.put(bundle, Boolean.valueOf(applyCondition));
		} else {
			activeBundles.put(bundle, activator.activate(bundle));
		}
	}

	boolean remove(Bundle bundle) {
		boolean value = false;

		value = (activeBundles.remove(bundle) != null);
		value |= (lazyBundles.remove(bundle) != null);

		synchronized (promotionQueue) {
			value |= promotionQueue.remove(bundle);
		}

		return value;
	}

	/**
	 * Applies an operation on all the bundles. To cope with concurrent environment, the class uses several queues so
	 * that lazy bundles that get activated, do not get ignored by threads hitting the method right at that point.
	 * 
	 * @param <V>
	 * @param action
	 * @return
	 */
	<V> V apply(Operation<T, V> action) throws Exception {
		boolean debug = log.isDebugEnabled();

		// count running thread
		threadCounter.incrementAndGet();

		try {
			// check active bundles
			for (Iterator<T> i = activeBundles.values().iterator(); i.hasNext();) {
				T result = i.next();
				V value = action.operate(result);
				if (value != null) {
					return value;
				}
			}

			// nothing found, look into lazy bundles
			for (Iterator<Map.Entry<Bundle, Boolean>> i = lazyBundles.entrySet().iterator(); i.hasNext();) {
				Entry<Bundle, Boolean> entry = i.next();
				Bundle bundle = entry.getKey();
				Boolean applyCondition = entry.getValue();

				if (Boolean.FALSE.equals(applyCondition)
						|| (Boolean.TRUE.equals(applyCondition) && condition.pass(bundle))) {
					// promote bundle
					T result = activeBundles.putIfAbsent(bundle, activator.activate(bundle));
					if (result == null) {
						result = activeBundles.get(bundle);
						synchronized (promotionQueue) {
							promotionQueue.add(bundle);
						}
					}

					if (result != null) {
						V value = action.operate(result);
						if (value != null) {
							return value;
						}
					}
				} else {
					// the bundle is not compatible, remove it asap
					lazyBundles.remove(bundle);
					if (debug)
						log.debug("Activated lazy bundle " + OsgiStringUtils.nullSafeNameAndSymName(bundle)
								+ " but found it unsuitable");
				}
			}

			// nothing found
			return null;
		}
		// cleanup the promotion queue
		finally {
			Bundle[] loggedBundles = null;

			synchronized (promotionQueue) {
				// the last thread accessing the method, does the cleanup
				// this way, we know other threads will pick the most up to date state
				// of the concurrent map
				if (threadCounter.decrementAndGet() == 0) {
					for (Bundle bundle : promotionQueue) {
						// remove bundle from the lazy map
						// but check that it wasn't removed in the meantime
						// by #remove(Bundle)
						if (lazyBundles.remove(bundle) == null) {
							activeBundles.remove(bundle);
						}
					}
				}
				if (debug && !promotionQueue.isEmpty()) {
					loggedBundles = (Bundle[]) promotionQueue.toArray(new Bundle[promotionQueue.size()]);
				}
				promotionQueue.clear();
			}

			if (loggedBundles != null) {
				StringBuilder builder = new StringBuilder("Activated (and validated) lazy bundles [ ");
				for (Bundle bundle : loggedBundles) {
					builder.append(OsgiStringUtils.nullSafeNameAndSymName(bundle));
					builder.append(" ");
				}
				builder.append("]");
				log.debug(builder);
			}
		}
	}

	public void clear() {
		promotionQueue.clear();
		lazyBundles.clear();
		activeBundles.clear();
	}
}