/*******************************************************************************
 * Copyright (c) 2000, 2009 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.device;

import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

/**
 * DeviceManager bundle. This bundle implements the OSGi Device Access 1.1
 * specification.
 *
 * This implementation does not include the optimizations in section
 * 8.7.4 of the OSGi SP R2 spec.
 *
 */
public class Activator implements BundleActivator, ServiceTrackerCustomizer, FrameworkListener, Runnable {
	protected final static boolean DEBUG = false;

	/** DeviceManager BundleContext */
	protected BundleContext context;

	/** LogTracker object */
	protected LogTracker log;

	/** if false the thread must terminate */
	protected volatile boolean running;

	/** DeviceManager thread */
	protected Thread thread;

	/** DriverTracker for Driver services. */
	protected DriverTracker drivers;

	/** Tracker for DriverLocator services */
	protected DriverLocatorTracker locators;

	/** Tracker for DriverSelector services */
	protected DriverSelectorTracker selectors;

	/** ServiceTracker object for device services */
	protected ServiceTracker devices;

	/** filter for Device services */
	protected Filter deviceFilter;

	/** filter for Driver services */
	protected Filter driverFilter;

	/**
	 * Linked List item
	 */
	static class DeviceService {
		/** object for this item */
		final DeviceTracker device;
		/** next item in event queue */
		DeviceService next;

		/**
		 * Constructor for work queue item
		 *
		 * @param o Object for this event
		 */
		DeviceService(DeviceTracker device) {
			this.device = device;
			next = null;
		}
	}

	/** item at the head of the event queue */
	private DeviceService head;
	/** item at the tail of the event queue */
	private DeviceService tail;

	/** number of milliseconds to wait before refining idle Device services */
	protected long updatewait;

	/** set to true by DriverTracker when a Driver Service is registered */
	protected volatile boolean driverServiceRegistered;

	/**
	 * Create a DeviceManager object.
	 *
	 */

	public Activator() {
		super();
	}

	/**
	 * Start the Device Manager.
	 *
	 * @param contxt The device manager's bundle context
	 */

	public void start(BundleContext contxt) throws Exception {
		this.context = contxt;
		running = false;

		log = new LogTracker(context, System.err);

		try {
			deviceFilter = context.createFilter("(|(" + org.osgi.framework.Constants.OBJECTCLASS + "=" + DeviceTracker.clazz + ////-1$ ////-2$ //$NON-NLS-1$ //$NON-NLS-2$
					")(" + org.osgi.service.device.Constants.DEVICE_CATEGORY + "=*))"); //$NON-NLS-1$ //$NON-NLS-2$

			driverFilter = context.createFilter("(" + org.osgi.framework.Constants.OBJECTCLASS + "=" + DriverTracker.clazz + ")"); ////-1$ ////-2$ ////-3$ //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		} catch (InvalidSyntaxException e) {
			log.log(LogService.LOG_ERROR, NLS.bind(DeviceMsg.Unable_to_create_Filter_for_DeviceManager, e)); ////-1$
			throw e;
		}

		updatewait = 5 * 1000L;

		String prop = context.getProperty("org.eclipse.equinox.device.updatewait"); //$NON-NLS-1$

		if (prop != null) {
			try {
				updatewait = Long.parseLong(prop) * 1000L;
			} catch (NumberFormatException e) {
				//do nothing
			}
		}

		Bundle systemBundle = context.getBundle(0);

		if ((systemBundle != null) && ((systemBundle.getState() & Bundle.STARTING) != 0)) { /* if the system bundle is starting */
			context.addFrameworkListener(this);
		} else {
			startDeviceManager();
		}

		log.log(LogService.LOG_INFO, DeviceMsg.DeviceManager_started);
	}

	/**
	 * Receive notification of a general framework event.
	 *
	 * @param event The FrameworkEvent.
	 */
	public void frameworkEvent(FrameworkEvent event) {
		switch (event.getType()) {
			case FrameworkEvent.STARTED : {
				context.removeFrameworkListener(this);

				try {
					startDeviceManager();
				} catch (Throwable t) {
					log.log(LogService.LOG_ERROR, NLS.bind(DeviceMsg.DeviceManager_has_thrown_an_error, t)); ////-1$
				}

				break;
			}
		}
	}

	/**
	 * Start the DeviceManager thread.
	 *
	 */
	public void startDeviceManager() {
		if (!running) {
			head = null;
			tail = null;

			locators = new DriverLocatorTracker(this);

			selectors = new DriverSelectorTracker(this);

			drivers = new DriverTracker(this);

			devices = new ServiceTracker(context, deviceFilter, this);
			devices.open();

			running = true;
			driverServiceRegistered = false;

			thread = (new SecureAction()).createThread(this, "DeviceManager"); //$NON-NLS-1$
			thread.start(); /* Start DeviceManager thread */
		}
	}

	/**
	 * Stop the Device Manager bundle.
	 *
	 * @param contxt The device manager's bundle context
	 */

	public void stop(BundleContext contxt) throws Exception {
		context.removeFrameworkListener(this);

		if (running) {
			Thread t = thread;

			running = false; /* request thread to stop */

			if (t != null) {
				t.interrupt();

				synchronized (t) {
					while (t.isAlive()) /* wait for thread to complete */
					{
						try {
							t.wait(0);
						} catch (InterruptedException e) {
							//	do nothing
						}
					}
				}
			}
		}

		if (drivers != null) {
			drivers.close();
			drivers = null;
		}

		if (devices != null) {
			devices.close();
			devices = null;
		}

		if (locators != null) {
			locators.close();
			locators = null;
		}

		if (selectors != null) {
			selectors.close();
			selectors = null;
		}

		if (log != null) {
			log.close();
			log = null;
		}

		this.context = null;
	}

	/**
	 * A service is being added to the ServiceTracker.
	 *
	 * <p>This method is called before a service which matched
	 * the search parameters of the ServiceTracker is
	 * added to the ServiceTracker. This method should return the
	 * service object to be tracked for this ServiceReference.
	 * The returned service object is stored in the ServiceTracker
	 * and is available from the getService and getServices
	 * methods.
	 *
	 * @param reference Reference to service being added to the ServiceTracker.
	 * @return The service object to be tracked for the
	 * ServiceReference or <tt>null</tt> if the ServiceReference should not
	 * be tracked.
	 */
	public Object addingService(ServiceReference reference) {
		if (Activator.DEBUG) {
			this.log.log(reference, LogService.LOG_DEBUG, "DeviceManager device service registered"); //$NON-NLS-1$
		}

		enqueue(new DeviceTracker(this, reference));

		return (reference);
	}

	/**
	 * A service tracked by the ServiceTracker has been modified.
	 *
	 * <p>This method is called when a service being tracked
	 * by the ServiceTracker has had it properties modified.
	 *
	 * @param reference Reference to service that has been modified.
	 * @param service The service object for the modified service.
	 */
	public void modifiedService(ServiceReference reference, Object service) {
		// do nothing
	}

	/**
	 * A service tracked by the ServiceTracker is being removed.
	 *
	 * <p>This method is called after a service is no longer being tracked
	 * by the ServiceTracker.
	 *
	 * @param reference Reference to service that has been removed.
	 * @param object The service object for the removed service.
	 */
	public void removedService(ServiceReference reference, Object object) {
		if (Activator.DEBUG) {
			log.log(reference, LogService.LOG_DEBUG, "DeviceManager device service unregistered"); //$NON-NLS-1$
		}

		/* We do not implement optional driver reclamation.
		 * Thus we take no specific action upon Device service unregistration .
		 */
	}

	public void refineIdleDevices() {
		if (Activator.DEBUG) {
			log.log(LogService.LOG_DEBUG, "DeviceManager refining idle device services"); //$NON-NLS-1$
		}

		ServiceReference[] references = devices.getServiceReferences();

		if (references != null) {
			int size = references.length;

			for (int i = 0; i < size; i++) {
				ServiceReference device = references[i];

				enqueue(new DeviceTracker(this, device));
			}
		}
	}

	/**
	 * Main thread for DeviceManager.
	 *
	 * Attempt to refine all Device services that are not in use
	 * by a driver bundle.
	 */
	public void run() {
		while (running) {
			DeviceTracker device;

			try {
				device = dequeue();
			} catch (InterruptedException e) {
				continue;
			}

			try {
				device.refine();
			} catch (Throwable t) {
				log.log(LogService.LOG_ERROR, NLS.bind(DeviceMsg.DeviceManager_has_thrown_an_error, t)); ////-1$
			}
		}
	}

	/**
	 * Queue the object to be processed on the work thread.
	 * The thread is notified.
	 *
	 * @param device Work item.
	 */
	public synchronized void enqueue(DeviceTracker device) {
		if (device != null) {
			if (Activator.DEBUG) {
				log.log(LogService.LOG_DEBUG, "DeviceManager queuing DeviceTracker"); //$NON-NLS-1$
			}

			DeviceService item = new DeviceService(device);

			if (head == null) /* if the queue was empty */
			{
				head = item;
				tail = item;
			} else /* else add to end of queue */
			{
				tail.next = item;
				tail = item;
			}
		}

		notify();
	}

	/**
	 * Dequeue an object from the work thread.
	 * If the queue is empty, this method blocks.
	 *
	 * @return Dequeue object from the work thread.
	 * @throws InterruptedException If the queue has been stopped.
	 */
	private synchronized DeviceTracker dequeue() throws InterruptedException {
		while (running && (head == null)) {
			// TODO need to determine if this code is needed (bug 261197)
			/* This should be included per Section 8.7.7 of the OSGi SP R2
			 * spec, but it causes the OSGi SP R2 Test Suite to fail.
			 * We should turn this on for R3.
			
			 if (driverServiceRegistered)
			 */
			//			if (false) {
			//				driverServiceRegistered = false;
			//
			//				refineIdleDevices();
			//			} else {
			locators.uninstallDriverBundles();

			try {
				if (Activator.DEBUG) {
					log.log(LogService.LOG_DEBUG, "DeviceManager waiting on queue"); //$NON-NLS-1$
				}

				wait();
			} catch (InterruptedException e) {
				// do nothing
			}
			//			}
		}

		if (!running) /* if we are stopping */
		{
			throw new InterruptedException(); /* throw an exception */
		}

		DeviceService item = head;
		head = item.next;
		if (head == null) {
			tail = null;
		}

		return (item.device);
	}
}
