/*******************************************************************************
 * Copyright (c) 2006, 2015 Wind River Systems 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:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.tests.dsf.service;

import java.lang.reflect.Constructor;
import java.util.concurrent.ExecutionException;

import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.service.IDsfService;
import org.eclipse.cdt.tests.dsf.DsfTestPlugin;
import org.eclipse.cdt.tests.dsf.TestDsfExecutor;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;

public class ServiceTests {
	TestDsfExecutor fExecutor;

	@Before
	public void startExecutor() throws ExecutionException, InterruptedException {
		fExecutor = new TestDsfExecutor();
	}

	@After
	public void shutdownExecutor() throws ExecutionException, InterruptedException {
		fExecutor.submit(new DsfRunnable() {
			@Override
			public void run() {
				fExecutor.shutdown();
			}
		}).get();
		if (fExecutor.exceptionsCaught()) {
			Throwable[] exceptions = fExecutor.getExceptions();
			throw new ExecutionException(exceptions[0]);
		}
		fExecutor = null;
	}

	private class CreateSessionStep extends Sequence.Step {
		private DsfSession fSession;

		@Override
		public void execute(RequestMonitor requestMonitor) {
			fSession = DsfSession.startSession(fExecutor, "org.eclipse.cdt.dsf.tests"); //$NON-NLS-1$
			requestMonitor.done();
		}

		DsfSession getSession() {
			return fSession;
		}
	}

	private class ShutdownSessionStep extends Sequence.Step {
		private CreateSessionStep fCreateSessionStep;

		ShutdownSessionStep(CreateSessionStep createSessionStep) {
			fCreateSessionStep = createSessionStep;
		}

		@Override
		public void execute(RequestMonitor requestMonitor) {
			DsfSession.endSession(fCreateSessionStep.getSession());
			requestMonitor.done();
		}
	}

	private class InitializeServiceStep extends Sequence.Step {
		CreateSessionStep fCreateSessionStep;
		Class<? extends IDsfService> fServiceClass;
		IDsfService fService;

		InitializeServiceStep(CreateSessionStep createSessionStep, Class<? extends IDsfService> serviceClass) {
			fCreateSessionStep = createSessionStep;
			fServiceClass = serviceClass;
		}

		IDsfService getService() {
			return fService;
		}

		@Override
		public void execute(RequestMonitor requestMonitor) {
			try {
				Constructor<? extends IDsfService> c = fServiceClass.getConstructor(new Class[] { DsfSession.class });
				fService = c.newInstance(new Object[] { fCreateSessionStep.getSession() });
			} catch (Exception e) {
				Assert.fail("Unexpected exception"); //$NON-NLS-1$
			}
			fService.initialize(requestMonitor);
		}
	}

	private class InitializeMultiInstanceServiceStep extends InitializeServiceStep {
		String fServiceId;

		InitializeMultiInstanceServiceStep(CreateSessionStep createSessionStep,
				Class<? extends IDsfService> serviceClass, String serviceId) {
			super(createSessionStep, serviceClass);
			fServiceId = serviceId;
		}

		@Override
		IDsfService getService() {
			return fService;
		}

		@Override
		public void execute(RequestMonitor requestMonitor) {
			try {
				Constructor<? extends IDsfService> c = fServiceClass
						.getConstructor(new Class[] { DsfSession.class, String.class });
				fService = c.newInstance(new Object[] { fCreateSessionStep.getSession(), fServiceId });
			} catch (Exception e) {
				Assert.fail("Unexpected exception"); //$NON-NLS-1$
			}
			fService.initialize(requestMonitor);
		}
	}

	private class ShutdownServiceStep extends Sequence.Step {
		InitializeServiceStep fInitializeServiceStep;

		ShutdownServiceStep(InitializeServiceStep initStep) {
			fInitializeServiceStep = initStep;
		}

		@Override
		public void execute(RequestMonitor requestMonitor) {
			fInitializeServiceStep.getService().shutdown(requestMonitor);
		}
	}

	abstract private class TestRetrievingReferenceStep extends Sequence.Step {
		String fClass;
		boolean fShouldSucceed;

		TestRetrievingReferenceStep(Class<?> clazz, boolean shouldSucceed) {
			fClass = clazz.getName();
			fShouldSucceed = shouldSucceed;
		}

		abstract String getFilter();

		@Override
		public void execute(RequestMonitor requestMonitor) {
			ServiceReference[] refs = null;
			try {
				refs = DsfTestPlugin.getBundleContext().getServiceReferences(fClass, getFilter());
			} catch (InvalidSyntaxException e) {
				Assert.fail("Unexpected exception"); //$NON-NLS-1$
			}
			if (fShouldSucceed) {
				Assert.assertTrue(refs != null);
				Assert.assertTrue(refs.length == 1);
				IDsfService service = (IDsfService) DsfTestPlugin.getBundleContext().getService(refs[0]);
				Assert.assertTrue(service != null);
				DsfTestPlugin.getBundleContext().ungetService(refs[0]);
			} else {
				Assert.assertTrue(refs == null);
			}
			requestMonitor.done();
		}
	}

	private class TestRetrievingSimpleServiceReferenceStep extends TestRetrievingReferenceStep {
		CreateSessionStep fCreateSessionStep;

		TestRetrievingSimpleServiceReferenceStep(Class<?> clazz, boolean shouldSucceed,
				CreateSessionStep createSessionStep) {
			super(clazz, shouldSucceed);
			fCreateSessionStep = createSessionStep;
		}

		@Override
		String getFilter() {
			return "(" + IDsfService.PROP_SESSION_ID + "=" + fCreateSessionStep.getSession().getId() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
	}

	private class TestRetrievingMultiSessionServiceReferenceStep extends TestRetrievingSimpleServiceReferenceStep {
		String fServiceId;

		TestRetrievingMultiSessionServiceReferenceStep(Class<?> clazz, boolean shouldSucceed,
				CreateSessionStep createSessionStep, String serviceId) {
			super(clazz, shouldSucceed, createSessionStep);
			fServiceId = serviceId;
		}

		@Override
		String getFilter() {
			return "(&" + //$NON-NLS-1$
					"(" + IDsfService.PROP_SESSION_ID + "=" + fCreateSessionStep.getSession().getId() + ")" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					"(" + MultiInstanceTestService.PROP_INSTANCE_ID + "=" + fServiceId + ")" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					")"; //$NON-NLS-1$
		}
	}

	@Test
	public void singleServiceTest() throws InterruptedException, ExecutionException {
		Sequence seq = new Sequence(fExecutor) {
			CreateSessionStep fSessionStep;
			InitializeServiceStep fServiceStep;

			@Override
			public Step[] getSteps() {
				return fSteps;
			}

			final private Step[] fSteps = new Step[] { fSessionStep = new CreateSessionStep(),
					fServiceStep = new InitializeServiceStep(fSessionStep, SimpleTestService.class),
					new TestRetrievingSimpleServiceReferenceStep(SimpleTestService.class, true, fSessionStep),
					new ShutdownServiceStep(fServiceStep),
					new TestRetrievingSimpleServiceReferenceStep(SimpleTestService.class, false, fSessionStep),
					new ShutdownSessionStep(fSessionStep) };
		};
		fExecutor.execute(seq);
		seq.get();
	}

	/**
	 * Creates two sessions and starts a single service within each session.
	 * Then it tests retrieving the reference to the service.
	 */
	@Test
	public void singleServiceMultiSessionTest() throws InterruptedException, ExecutionException {
		Sequence seq = new Sequence(fExecutor) {
			CreateSessionStep fSession1Step;
			CreateSessionStep fSession2Step;
			InitializeServiceStep fSession1ServiceStep;
			InitializeServiceStep fSession2ServiceStep;

			@Override
			public Step[] getSteps() {
				return fSteps;
			}

			final private Step[] fSteps = new Step[] { fSession1Step = new CreateSessionStep(),
					fSession2Step = new CreateSessionStep(),
					fSession1ServiceStep = new InitializeServiceStep(fSession1Step, SimpleTestService.class),
					fSession2ServiceStep = new InitializeServiceStep(fSession2Step, SimpleTestService.class),
					new TestRetrievingSimpleServiceReferenceStep(SimpleTestService.class, true, fSession1Step),
					new TestRetrievingSimpleServiceReferenceStep(SimpleTestService.class, true, fSession2Step),
					new ShutdownServiceStep(fSession1ServiceStep), new ShutdownServiceStep(fSession2ServiceStep),
					new TestRetrievingSimpleServiceReferenceStep(SimpleTestService.class, false, fSession1Step),
					new TestRetrievingSimpleServiceReferenceStep(SimpleTestService.class, false, fSession2Step),
					new ShutdownSessionStep(fSession1Step), new ShutdownSessionStep(fSession2Step) };
		};
		fExecutor.execute(seq);
		seq.get();
	}

	@Test
	public void multiServiceServiceTest() throws InterruptedException, ExecutionException {
		Sequence seq = new Sequence(fExecutor) {
			CreateSessionStep fSessionStep;
			InitializeServiceStep fService1Step;
			InitializeServiceStep fService2Step;

			@Override
			public Step[] getSteps() {
				return fSteps;
			}

			final private Step[] fSteps = new Step[] { fSessionStep = new CreateSessionStep(),
					fService1Step = new InitializeMultiInstanceServiceStep(fSessionStep, MultiInstanceTestService.class,
							"1"), //$NON-NLS-1$
					fService2Step = new InitializeMultiInstanceServiceStep(fSessionStep, MultiInstanceTestService.class,
							"2"), //$NON-NLS-1$
					new TestRetrievingMultiSessionServiceReferenceStep(MultiInstanceTestService.class, true,
							fSessionStep, "1"), //$NON-NLS-1$
					new TestRetrievingMultiSessionServiceReferenceStep(MultiInstanceTestService.class, true,
							fSessionStep, "2"), //$NON-NLS-1$
					new ShutdownServiceStep(fService1Step), new ShutdownServiceStep(fService2Step),
					new TestRetrievingMultiSessionServiceReferenceStep(MultiInstanceTestService.class, false,
							fSessionStep, "1"), //$NON-NLS-1$
					new TestRetrievingMultiSessionServiceReferenceStep(MultiInstanceTestService.class, false,
							fSessionStep, "2"), //$NON-NLS-1$
					new ShutdownSessionStep(fSessionStep) };
		};
		fExecutor.execute(seq);
		seq.get();
	}

}
