/*******************************************************************************
 * Copyright (c) 2010, 2015 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
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 474274
 ******************************************************************************/
package org.eclipse.e4.core.internal.tests.di.extensions;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import java.lang.reflect.InvocationTargetException;
import java.util.Dictionary;
import java.util.Hashtable;

import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.IInjector;
import org.eclipse.e4.core.di.InjectorFactory;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.di.extensions.EventTopic;
import org.eclipse.e4.core.di.internal.extensions.util.EventUtils;
import org.eclipse.e4.core.internal.tests.CoreTestsActivator;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;

// TBD add auto-conversion?
@SuppressWarnings("restriction")
public class InjectionEventTest {

	static protected boolean testFailed = false;

	// Class used to test receiving events
	static class InjectTarget {
		public int counter1 = 0;
		public int counter3 = 0;

		public String string1;
		public String string3;

		public boolean valid = true;

		public MyBinding myBinding;

		public void resetCounters() {
			counter1 = counter3 = 0;
		}

		@Inject @Optional
		public void receivedEvent1(@EventTopic("e4/test/event1") String string1) {
			if (!valid) {
				testFailed = true;
			}
			counter1++;
			this.string1 = string1;
		}

		@Inject
		public void receivedOptionalEvent(MyBinding myBinding, @Optional @EventTopic("e4/test/event3") String string3) {
			if (!valid) {
				testFailed = true;
			}
			counter3++;
			this.myBinding = myBinding;
			this.string3 = string3;
		}
	}

	// Class used to test receiving events
	static class InjectTargetEvent {
		public int counter1 = 0;
		public Event event;

		@Inject @Optional
		public void receivedEvent1(@EventTopic("e4/test/eventInjection") Event event) {
			counter1++;
			this.event = event;
		}

	}

	// Class used to test receiving events using wildcard
	static class InjectStarEvent {
		public int counter1 = 0;
		public Event event;

		@Inject @Optional
		public void receivedEvent1(@EventTopic("e4/test/*") Event event) {
			counter1++;
			this.event = event;
		}
	}

	// This tests and demos sending events
	static public class EventAdminHelper {
		@Inject
		public EventAdmin eventAdmin;

		public void sendEvent(String topic, Object data) {
			EventUtils.send(eventAdmin, topic, data);
		}

		public void sendEvent(Event event) {
			eventAdmin.sendEvent(event);
		}
	}

	// Tests mixed injection modes
	@Singleton
	static class MyBinding {
		// static binding for injector
	}

	private EventAdminHelper helper;

	@Before
	public void setUp() throws Exception {
		ensureEventAdminStarted();
		BundleContext bundleContext = CoreTestsActivator.getDefault().getBundleContext();
		IEclipseContext localContext = EclipseContextFactory.getServiceContext(bundleContext);
		helper = ContextInjectionFactory.make(EventAdminHelper.class, localContext);
	}

	@Test
	public void testEventInjection() throws InvocationTargetException, InstantiationException {

		IInjector injector = InjectorFactory.getDefault();
		injector.addBinding(MyBinding.class);

		IEclipseContext context = EclipseContextFactory.create();
		InjectTarget target = ContextInjectionFactory.make(InjectTarget.class, context);

		// initial state
		assertEquals(0, target.counter1);
		assertNull(target.string1);
		assertEquals(1, target.counter3);
		assertNull(target.string3);
		assertNotNull(target.myBinding);

		// send event1
		helper.sendEvent("e4/test/event1", "event1data");

		assertEquals(1, target.counter1);
		assertEquals("event1data", target.string1);
		assertEquals(1, target.counter3);
		assertNull(target.string3);
		assertNotNull(target.myBinding);

		// send event2
		helper.sendEvent("e4/test/event2", "event2data");

		assertEquals(1, target.counter1);
		assertEquals("event1data", target.string1);
		assertEquals(1, target.counter3);
		assertNull(target.string3);
		assertNotNull(target.myBinding);

		// send event3
		helper.sendEvent("e4/test/event3", "event3data");

		assertEquals(1, target.counter1);
		assertEquals("event1data", target.string1);
		assertEquals(2, target.counter3);
		assertEquals("event3data", target.string3);
		assertNotNull(target.myBinding);

		// send event1 again
		helper.sendEvent("e4/test/event1", "abc");

		assertEquals(2, target.counter1);
		assertEquals("abc", target.string1);
		assertEquals(2, target.counter3);
		assertEquals("event3data", target.string3);
		assertNotNull(target.myBinding);
	}

	@Test
	public void testInjectType() {
		IEclipseContext context = EclipseContextFactory.create();
		InjectTargetEvent target = ContextInjectionFactory.make(InjectTargetEvent.class, context);

		// initial state
		assertEquals(0, target.counter1);
		assertNull(target.event);

		// send event
		String eventTopic = "e4/test/eventInjection";
		Dictionary<String, Object> d = new Hashtable<>();
		d.put(EventConstants.EVENT_TOPIC, eventTopic);
		d.put("data1", Integer.valueOf(5));
		d.put("data2", "sample");
		Event event = new Event(eventTopic, d);
		helper.sendEvent(event);

		assertEquals(1, target.counter1);
		assertEquals(event, target.event);
		assertEquals(Integer.valueOf(5), target.event.getProperty("data1"));
		assertEquals("sample", target.event.getProperty("data2"));
	}

	// NOTE: this test relies on GC being actually done on the test object.
	// Java does not guarantee that to happen, so, if this test starts to fail
	// intermittently, feel free to comment it
	@Test
	public void testEventInjectionUnsubscribe() throws InvocationTargetException, InstantiationException {
		IInjector injector = InjectorFactory.getDefault();
		injector.addBinding(MyBinding.class);

		wrapSetup(); // do it in a separate method to ease GC
		System.gc();
		System.runFinalization();
		System.gc();
		helper.sendEvent("e4/test/event1", "wrong");
		assertFalse(testFailed); // target would have asserted if it is still subscribed
	}

	@Test
	public void testInjectWildCard() {
		IEclipseContext context = EclipseContextFactory.create();
		InjectStarEvent target = ContextInjectionFactory.make(InjectStarEvent.class, context);

		// initial state
		assertEquals(0, target.counter1);
		assertNull(target.event);

		// send event
		String eventTopic = "e4/test/eventInjection";
		Dictionary<String, Object> d = new Hashtable<>();
		d.put(EventConstants.EVENT_TOPIC, eventTopic);
		d.put("data1", Integer.valueOf(5));
		d.put("data2", "sample");
		Event event = new Event(eventTopic, d);
		helper.sendEvent(event);

		assertEquals(1, target.counter1);
		assertEquals(event, target.event);
		assertEquals(Integer.valueOf(5), target.event.getProperty("data1"));
		assertEquals("sample", target.event.getProperty("data2"));
	}

	private void wrapSetup() throws InvocationTargetException, InstantiationException {
		IEclipseContext context = EclipseContextFactory.create();
		InjectTarget target = ContextInjectionFactory.make(InjectTarget.class, context);
		// send event
		helper.sendEvent("e4/test/event1", "event1data");
		assertEquals(1, target.counter1);
		assertEquals("event1data", target.string1);
		target.valid = false;

	}

	static void ensureEventAdminStarted() {
		if (CoreTestsActivator.getDefault().getEventAdmin() == null) {
			Bundle[] bundles = CoreTestsActivator.getDefault().getBundleContext().getBundles();
			for (Bundle bundle : bundles) {
				if (!"org.eclipse.equinox.event".equals(bundle.getSymbolicName())) {
					continue;
				}
				try {
					bundle.start(Bundle.START_TRANSIENT);
				} catch (BundleException e) {
					e.printStackTrace();
				}
				break;
			}
		}
	}
}
