/*******************************************************************************
 * Copyright (c) 2017 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.internal.log.stream;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogListener;
import org.osgi.service.log.LogReaderService;
import org.osgi.service.log.stream.LogStreamProvider;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

/* LogStreamManager is used to start and stop the bundle and keeps the track of the logs using the 
 * ServiceTrackerCustomizer<LogReaderService, AtomicReference<LogReaderService>> which listens to 
 * the incoming logs using the LogListener. It is also responsible to provide service tracker 
 * and each log entry to the LogStreamProviderFactory.
 * 
 */
public class LogStreamManager implements BundleActivator, ServiceTrackerCustomizer<LogReaderService, AtomicReference<LogReaderService>>, LogListener {
	private ServiceRegistration<LogStreamProvider> logStreamServiceRegistration;
	private LogStreamProviderFactory logStreamProviderFactory;
	private ServiceTracker<LogReaderService, AtomicReference<LogReaderService>> logReaderService;
	private BundleContext context;
	private final ReentrantLock eventProducerLock = new ReentrantLock();

	/*
	 * (non-Javadoc)
	 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
	 */
	@Override
	public void start(BundleContext bc) throws Exception {
		this.context = bc;
		logReaderService = new ServiceTracker<>(context, LogReaderService.class, this);
		logReaderService.open();
		logStreamProviderFactory = new LogStreamProviderFactory(logReaderService);
		logStreamServiceRegistration = context.registerService(LogStreamProvider.class, logStreamProviderFactory, null);
	}

	/*
	 * (non-Javadoc)
	 * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
	 */
	@Override
	public void stop(BundleContext bundleContext) throws Exception {
		logReaderService.close();
		logStreamServiceRegistration.unregister();
		logStreamServiceRegistration = null;
		logStreamProviderFactory.shutdownExecutor();
	}

	/*
	 * (non-Javadoc)
	 * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
	 */

	@Override
	public AtomicReference<LogReaderService> addingService(ServiceReference<LogReaderService> reference) {
		AtomicReference<LogReaderService> tracked = new AtomicReference<>();
		modifiedService(reference, tracked);
		return tracked;
	}

	/*
	 * (non-Javadoc)
	 * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference, java.lang.Object)
	 */
	@Override
	public void modifiedService(ServiceReference<LogReaderService> modifiedServiceRef, AtomicReference<LogReaderService> modifiedTracked) {
		eventProducerLock.lock();
		try {
			// Check if the currently used reader service is lower ranked that the modified serviceRef
			ServiceReference<LogReaderService> currentServiceRef = logReaderService.getServiceReference();
			if (currentServiceRef == null || modifiedServiceRef.compareTo(currentServiceRef) > 0) {
				// The modified service reference is higher ranked than the currently used one;
				// Use the modified service reference instead.
				LogReaderService readerService = context.getService(modifiedServiceRef);
				if (readerService != null) {
					if (modifiedTracked.get() == null) {
						// update our tracked object for the reference with the real service
						modifiedTracked.set(readerService);
					}
					// remove our listener from the currently used service
					if (currentServiceRef != null) {
						AtomicReference<LogReaderService> currentTracked = logReaderService.getService(currentServiceRef);
						if (currentTracked != null) {
							LogReaderService currentLogReader = currentTracked.get();
							if (currentLogReader != null) {
								// we were really using this service;
								// remove our listener and unget the service
								currentLogReader.removeLogListener(this);
								context.ungetService(currentServiceRef);
								// finally null out our tracked reference
								currentTracked.set(null);
							}
						}
					}

					readerService.addLogListener(this);
				}
			}
		} finally {
			eventProducerLock.unlock();
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference, java.lang.Object)
	 */
	@Override
	public void removedService(ServiceReference<LogReaderService> removedRef, AtomicReference<LogReaderService> removedTracked) {
		eventProducerLock.lock();
		try {
			LogReaderService removedLogReader = removedTracked.get();
			if (removedLogReader != null) {
				// remove the listener
				removedLogReader.removeLogListener(this);
				context.ungetService(removedRef);
				removedTracked.set(null);
			}
			ServiceReference<LogReaderService> currentRef = logReaderService.getServiceReference();
			if (currentRef != null) {
				AtomicReference<LogReaderService> currentTracked = logReaderService.getService(currentRef);
				if (currentTracked != null) {
					LogReaderService currentLogReader = currentTracked.get();
					if (currentLogReader == null) {
						currentLogReader = context.getService(currentRef);
						currentTracked.set(currentLogReader);
					}
					if (currentLogReader != null) {
						currentLogReader.addLogListener(this);
					}
				}
			}
		} finally {
			eventProducerLock.unlock();
		}
	}

	/* It is used to post each log entry to the LogStreamProviderFactory
	 * (non-Javadoc)
	 * @see org.osgi.service.log.LogListener#logged(org.osgi.service.log.LogEntry)
	 */

	@Override
	public void logged(LogEntry entry) {
		logStreamProviderFactory.postLogEntry(entry);
	}

}
