/*******************************************************************************
 * Copyright (c) 2010, 2011 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.osgi.tests.bundles;

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.core.internal.runtime.RuntimeLog;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.ILogListener;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.log.ExtendedLogEntry;
import org.eclipse.equinox.log.ExtendedLogReaderService;
import org.eclipse.equinox.log.ExtendedLogService;
import org.eclipse.equinox.log.LogFilter;
import org.eclipse.equinox.log.Logger;
import org.eclipse.osgi.framework.log.FrameworkLog;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogListener;
import org.osgi.service.log.LogService;

public class LoggingTests extends AbstractBundleTests {
	static final String EQUINOX_LOGGER = "org.eclipse.equinox.logger";

	public static Test suite() {
		return new TestSuite(LoggingTests.class);
	}

	class LogServiceReference {
		private final ServiceReference logRef;
		final ExtendedLogService logService;
		private final ServiceReference readerRef;
		final ExtendedLogReaderService readerService;
		private final ServiceReference fwkLogRef;
		final FrameworkLog fwkLog;

		public LogServiceReference(ServiceReference logRef, ExtendedLogService logService, ServiceReference readerRef, ExtendedLogReaderService readerService, ServiceReference fwkLogRef, FrameworkLog fwkLog) {

			this.logRef = logRef;
			this.logService = logService;
			this.readerRef = readerRef;
			this.readerService = readerService;
			this.fwkLogRef = fwkLogRef;
			this.fwkLog = fwkLog;
		}

		public void unget(BundleContext bc) {
			bc.ungetService(logRef);
			bc.ungetService(readerRef);
			bc.ungetService(fwkLogRef);
		}
	}

	class ILogEntry {
		final IStatus status;
		final String plugin;

		ILogEntry(IStatus status, String plugin) {
			this.status = status;
			this.plugin = plugin;
		}
	}

	class TestListener {
		final List context;
		boolean found = false;

		TestListener(List context) {
			this.context = context;
		}

		synchronized boolean waitforContext() {
			if (context.size() > 0)
				try {
					wait(5000);
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			return context.size() == 0;

		}

		synchronized void checkContext(IStatus check) {
			if (check == null)
				return;
			if (context.size() > 0) {
				IStatus expected = (IStatus) context.get(0);
				try {
					assertEquals(expected.getSeverity(), check.getSeverity());
					assertEquals(expected.getCode(), check.getCode());
					assertEquals(expected.getMessage(), check.getMessage());
					assertEquals(expected.getPlugin(), check.getPlugin());
				} catch (AssertionFailedError e) {
					return;
				}
				context.remove(0);
				if (context.size() == 0)
					notifyAll();
			}
		}

		synchronized void addContext(List moreContext) {
			context.addAll(moreContext);
		}
	}

	class TestILogListener extends TestListener implements ILogListener {
		TestILogListener(List context) {
			super(context);
		}

		public void logging(IStatus status, String plugin) {
			checkContext(status);
		}

	}

	class TestLogListener extends TestListener implements LogListener, LogFilter {
		TestLogListener(List context) {
			super(context);
		}

		List entries = new ArrayList();

		public void logged(LogEntry entry) {
			entries.add(entry);
			Object check = ((ExtendedLogEntry) entry).getContext();
			if (check instanceof FrameworkLogEntry)
				check = ((FrameworkLogEntry) check).getContext();
			if (check instanceof IStatus)
				checkContext((IStatus) check);
		}

		List getEntries() {
			return entries;
		}

		public boolean isLoggable(Bundle bundle, String loggerName, int logLevel) {
			return EQUINOX_LOGGER.equals(loggerName);
		}
	}

	private LogServiceReference getLogService(BundleContext bc) {
		ServiceReference logRef = bc.getServiceReference(ExtendedLogService.class.getName());
		assertNotNull("log service ref is null", logRef);
		ServiceReference readerRef = bc.getServiceReference(ExtendedLogReaderService.class.getName());
		assertNotNull("log reader ref is null", readerRef);
		ServiceReference fwkLogRef = bc.getServiceReference(FrameworkLog.class.getName());
		assertNotNull("framework log ref is null", fwkLogRef);

		ExtendedLogService logService = (ExtendedLogService) bc.getService(logRef);
		assertNotNull("log service is null", logService);
		ExtendedLogReaderService readerService = (ExtendedLogReaderService) bc.getService(readerRef);
		assertNotNull("reader service is null", readerService);
		FrameworkLog fwkLog = (FrameworkLog) bc.getService(fwkLogRef);
		assertNotNull("framework log is null", fwkLog);
		return new LogServiceReference(logRef, logService, readerRef, readerService, fwkLogRef, fwkLog);
	}

	public void testLogService() throws BundleException {
		// Tests listeners at all levels when logged with the LogService
		Bundle testBundle = installer.installBundle("test.logging.a"); //$NON-NLS-1$
		testBundle.start();
		BundleContext bc = testBundle.getBundleContext();
		LogServiceReference logRef = getLogService(bc);
		IStatus a = new Status(IStatus.ERROR, testBundle.getSymbolicName(), getName());
		List context = new ArrayList();
		context.add(a);
		TestILogListener iLogListener1 = new TestILogListener(new ArrayList(context));
		TestILogListener iLogListener2 = new TestILogListener(new ArrayList(context));
		TestLogListener logListener = new TestLogListener(new ArrayList(context));

		ILog testLog = Platform.getLog(testBundle);
		testLog.addLogListener(iLogListener1);
		Platform.addLogListener(iLogListener2);
		logRef.readerService.addLogListener(logListener, logListener);
		try {
			ExtendedLogService logService = logRef.logService;
			Logger logger = logService.getLogger(testBundle, EQUINOX_LOGGER);
			logger.log(a, LogService.LOG_ERROR, a.getMessage());
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			assertTrue("Missing context", logListener.waitforContext());

			iLogListener1.addContext(new ArrayList(context));
			iLogListener2.addContext(new ArrayList(context));
			logListener.addContext(new ArrayList(context));

			logger.log(new FrameworkLogEntry(a, testBundle.getSymbolicName(), a.getSeverity(), a.getCode(), a.getMessage(), 0, null, null), LogService.LOG_ERROR, a.getMessage());
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			assertTrue("Missing context", logListener.waitforContext());

			iLogListener1.addContext(new ArrayList(context));
			iLogListener2.addContext(new ArrayList(context));
			logListener.addContext(new ArrayList(context));

			logListener.entries.clear();
			logger.log(LogService.LOG_ERROR, a.getMessage());
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			// there is no status here
			assertFalse("Missing context", logListener.waitforContext());
			// but we should have received one log entry
			assertEquals("Wrong number of entries", 1, logListener.entries.size());
		} finally {
			testLog.removeLogListener(iLogListener1);
			Platform.removeLogListener(iLogListener2);
			logRef.readerService.removeLogListener(logListener);
			logRef.unget(bc);
		}
	}

	public void testPluginILog() throws BundleException {
		// Tests listeners at all levels when logged with a plugin ILog
		Bundle testBundle = installer.installBundle("test.logging.a"); //$NON-NLS-1$
		testBundle.start();
		BundleContext bc = testBundle.getBundleContext();
		LogServiceReference logRef = getLogService(bc);
		IStatus a = new Status(IStatus.ERROR, testBundle.getSymbolicName(), getName());
		List context = new ArrayList();
		context.add(a);
		TestILogListener iLogListener1 = new TestILogListener(new ArrayList(context));
		TestILogListener iLogListener2 = new TestILogListener(new ArrayList(context));
		TestLogListener logListener = new TestLogListener(new ArrayList(context));

		ILog testLog = Platform.getLog(testBundle);
		testLog.addLogListener(iLogListener1);
		Platform.addLogListener(iLogListener2);
		logRef.readerService.addLogListener(logListener, logListener);
		try {
			testLog.log(a);
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			assertTrue("Missing context", logListener.waitforContext());

			iLogListener1.addContext(new ArrayList(context));
			iLogListener2.addContext(new ArrayList(context));
			logListener.addContext(new ArrayList(context));

			testLog.log(a);
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			assertTrue("Missing context", logListener.waitforContext());
		} finally {
			testLog.removeLogListener(iLogListener1);
			Platform.removeLogListener(iLogListener2);
			logRef.readerService.removeLogListener(logListener);
			logRef.unget(bc);
		}
	}

	public void testRuntimeLog() throws BundleException {
		// Tests listeners at all levels when logged with the RuntimeLog
		Bundle testBundle = installer.installBundle("test.logging.a"); //$NON-NLS-1$
		testBundle.start();
		BundleContext bc = testBundle.getBundleContext();
		LogServiceReference logRef = getLogService(bc);
		IStatus a = new Status(IStatus.ERROR, testBundle.getSymbolicName(), getName());
		List context = new ArrayList();
		context.add(a);
		TestILogListener iLogListener1 = new TestILogListener(new ArrayList(context));
		TestILogListener iLogListener2 = new TestILogListener(new ArrayList(context));
		TestLogListener logListener = new TestLogListener(new ArrayList(context));

		ILog testLog = Platform.getLog(testBundle);
		testLog.addLogListener(iLogListener1);
		Platform.addLogListener(iLogListener2);
		logRef.readerService.addLogListener(logListener, logListener);
		try {
			RuntimeLog.log(a);
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			assertTrue("Missing context", logListener.waitforContext());

			iLogListener1.addContext(new ArrayList(context));
			iLogListener2.addContext(new ArrayList(context));
			logListener.addContext(new ArrayList(context));

			RuntimeLog.log(a);
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			assertTrue("Missing context", logListener.waitforContext());
		} finally {
			testLog.removeLogListener(iLogListener1);
			Platform.removeLogListener(iLogListener2);
			logRef.readerService.removeLogListener(logListener);
			logRef.unget(bc);
		}
	}

	public void testFrameworkLog() throws BundleException {
		// Tests listeners at all levels when logged with the FrameworkLog
		Bundle testBundle = installer.installBundle("test.logging.a"); //$NON-NLS-1$
		testBundle.start();
		BundleContext bc = testBundle.getBundleContext();
		LogServiceReference logRef = getLogService(bc);
		IStatus a = new Status(IStatus.ERROR, testBundle.getSymbolicName(), getName());
		List context = new ArrayList();
		context.add(a);
		TestILogListener iLogListener1 = new TestILogListener(new ArrayList(context));
		TestILogListener iLogListener2 = new TestILogListener(new ArrayList(context));
		TestLogListener logListener = new TestLogListener(new ArrayList(context));

		ILog testLog = Platform.getLog(testBundle);
		testLog.addLogListener(iLogListener1);
		Platform.addLogListener(iLogListener2);
		logRef.readerService.addLogListener(logListener, logListener);
		try {
			FrameworkLogEntry fwkLogEntry = new FrameworkLogEntry(a, testBundle.getSymbolicName(), a.getSeverity(), a.getCode(), a.getMessage(), 0, null, null);
			logRef.fwkLog.log(fwkLogEntry);
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			assertTrue("Missing context", logListener.waitforContext());

			iLogListener1.addContext(new ArrayList(context));
			iLogListener2.addContext(new ArrayList(context));
			logListener.addContext(new ArrayList(context));

			logRef.fwkLog.log(fwkLogEntry);
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			assertTrue("Missing context", logListener.waitforContext());

			iLogListener1.addContext(new ArrayList(context));
			iLogListener2.addContext(new ArrayList(context));
			logListener.addContext(new ArrayList(context));

			logListener.entries.clear();
			logRef.fwkLog.log(new FrameworkLogEntry(testBundle.getSymbolicName(), a.getSeverity(), a.getCode(), a.getMessage(), 0, null, null));
			assertTrue("Missing context", iLogListener1.waitforContext());
			assertTrue("Missing context", iLogListener2.waitforContext());
			// there is no status here
			assertFalse("Missing context", logListener.waitforContext());
			// but we should have received one log entry
			assertEquals("Wrong number of entries", 1, logListener.entries.size());
		} finally {
			testLog.removeLogListener(iLogListener1);
			Platform.removeLogListener(iLogListener2);
			logRef.readerService.removeLogListener(logListener);
			logRef.unget(bc);
		}
	}

	public void testEventAdminAdapter1() {
		ServiceReference eventRef = getContext().getServiceReference(EventAdmin.class.getName());
		assertNotNull("No eventAdmin.", eventRef);
		LogServiceReference logRef = getLogService(getContext());

		final List events = new ArrayList();
		EventHandler testHandler = new EventHandler() {

			public void handleEvent(Event event) {
				synchronized (events) {
					events.add(event);
					events.notifyAll();
				}
			}
		};

		Dictionary props = new Hashtable();
		props.put(EventConstants.EVENT_TOPIC, "org/osgi/service/log/LogEntry/*");
		ServiceRegistration handlerReg = getContext().registerService(EventHandler.class.getName(), testHandler, props);
		try {
			logRef.logService.log(LogService.LOG_ERROR, getName());
			Event testEvent = null;
			synchronized (events) {
				if (events.size() == 0)
					try {
						events.wait(5000);
					} catch (InterruptedException e) {
						Thread.currentThread().interrupt();
					}
				if (events.size() > 0)
					testEvent = (Event) events.get(0);
			}
			assertNotNull("No event fired", testEvent);
			assertEquals("Wrong message", getName(), testEvent.getProperty("message"));
		} finally {
			handlerReg.unregister();
			logRef.unget(getContext());
		}
	}

	public void testEventAdminAdapter2() {
		ServiceReference eventRef = getContext().getServiceReference(EventAdmin.class.getName());
		assertNotNull("No eventAdmin.", eventRef);
		LogServiceReference logRef = getLogService(getContext());

		final List events = new ArrayList();
		EventHandler testHandler = new EventHandler() {

			public void handleEvent(Event event) {
				synchronized (events) {
					events.add(event);
					events.notifyAll();
				}
				throw new NullPointerException();
			}
		};

		Dictionary props = new Hashtable();
		props.put(EventConstants.EVENT_TOPIC, "org/osgi/service/log/LogEntry/*");
		ServiceRegistration handlerReg = getContext().registerService(EventHandler.class.getName(), testHandler, props);
		try {
			logRef.logService.log(LogService.LOG_ERROR, getName());
			int size = 0;
			synchronized (events) {
				for (int i = 0; i < 3 && events.size() < 3; i++)
					try {
						events.wait(5000);
					} catch (InterruptedException e) {
						Thread.currentThread().interrupt();
					}
				size = events.size();
			}
			assertEquals("Should only get one event from bad handler", 2, size);
		} finally {
			handlerReg.unregister();
			logRef.unget(getContext());
		}
	}

	public void testBug347183() throws BundleException {
		// test recursive logging
		final Bundle testBundle = installer.installBundle("test.logging.a"); //$NON-NLS-1$
		testBundle.start();
		LogServiceReference logRef = getLogService(getContext());
		ILogListener recurseLog = new ILogListener() {

			public void logging(IStatus status, String plugin) {
				Platform.getLog(testBundle).log(status);
			}
		};
		Platform.addLogListener(recurseLog);
		try {
			logRef.fwkLog.log(new FrameworkLogEntry(getContext().getBundle().getSymbolicName(), FrameworkLogEntry.ERROR, 0, "Test message", 0, null, null));

			// prime the log so we don't have to create it in the logging call
			Platform.getLog(testBundle);

			logRef.fwkLog.log(new FrameworkLogEntry(getContext().getBundle().getSymbolicName(), FrameworkLogEntry.ERROR, 0, "Test message", 0, null, null));
		} finally {
			Platform.removeLogListener(recurseLog);
		}
	}
}
