/*******************************************************************************
 * Copyright (c) 2011, 2012 VMware Inc.
 *
 * 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:
 *   VMware Inc. - initial contribution
 *******************************************************************************/

package org.eclipse.equinox.internal.region.management;

import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import javax.management.*;
import org.eclipse.equinox.internal.region.RegionLifecycleListener;
import org.eclipse.equinox.region.Region;
import org.eclipse.equinox.region.RegionDigraph;
import org.eclipse.equinox.region.management.ManageableRegion;
import org.eclipse.equinox.region.management.ManageableRegionDigraph;
import org.osgi.framework.*;

/**
 * {@link StandardManageableRegionDigraph} is a {@link ManageableRegionDigraph} that delegates to the
 * {@link RegionDigraph}.
 * <p />
 * 
 * <strong>Concurrent Semantics</strong><br />
 * Thread safe.
 */
public final class StandardManageableRegionDigraph implements ManageableRegionDigraph {

	private final RegionDigraph regionDigraph;

	private final String domain;

	private final RegionObjectNameCreator regionObjectNameCreator;

	private final Map<String, ManageableRegion> manageableRegions = new ConcurrentHashMap<>();

	private final BundleContext bundleContext;

	private final Object monitor = new Object();

	private final MBeanServer mbeanServer;

	private final ObjectName mbeanName;

	private ServiceRegistration<RegionLifecycleListener> listenerRegistration;

	private final String frameworkUUID;

	private final RegionLifecycleListener regionLifecycleListener = new RegionLifecycleListener() {

		@Override
		public void regionAdded(Region region) {
			addRegion(region);
		}

		@Override
		public void regionRemoving(Region region) {
			removeRegion(region);
		}

	};

	public StandardManageableRegionDigraph(RegionDigraph regionDigraph, String domain, BundleContext bundleContext) {
		this.regionDigraph = regionDigraph;
		this.domain = domain;
		this.bundleContext = bundleContext;
		this.mbeanServer = ManagementFactory.getPlatformMBeanServer();
		this.frameworkUUID = bundleContext.getProperty(Constants.FRAMEWORK_UUID);
		this.regionObjectNameCreator = new RegionObjectNameCreator(domain, this.frameworkUUID);
		String name = this.domain + ":type=RegionDigraph"; //$NON-NLS-1$
		if (frameworkUUID != null)
			name += ",frameworkUUID=" + frameworkUUID; //$NON-NLS-1$
		try {
			mbeanName = new ObjectName(name);
		} catch (MalformedObjectNameException e) {
			e.printStackTrace();
			throw new IllegalArgumentException("Invalid domain name '" + domain + "' resulting in an invalid object name '" + name + "'", e); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
		}

	}

	private void registerRegionLifecycleListener() {
		synchronized (this.monitor) {
			listenerRegistration = this.bundleContext.registerService(RegionLifecycleListener.class, regionLifecycleListener, null);
		}
	}

	private void unregisterRegionLifecycleListener() {
		synchronized (this.monitor) {
			listenerRegistration.unregister();
		}
	}

	public void registerMBean() {
		registerRegionLifecycleListener();
		synchronized (this.monitor) {
			// The following alien call is unavoidable to ensure consistency.
			Set<Region> regions = this.regionDigraph.getRegions();
			for (Region region : regions) {
				addRegion(region);
			}
		}

		safelyRegisterMBean(this, mbeanName);
	}

	public void unregisterMbean() {
		unregisterRegionLifecycleListener();
		Collection<String> currentRegionNames;
		synchronized (this.monitor) {
			currentRegionNames = new ArrayList<>(this.manageableRegions.keySet());
		}
		for (String regionName : currentRegionNames) {
			removeRegion(regionName);
		}
		try {
			this.mbeanServer.unregisterMBean(this.mbeanName);
		} catch (MBeanRegistrationException e) {
			e.printStackTrace();
			throw new RuntimeException("Problem unregistering mbean", e); //$NON-NLS-1$
		} catch (InstanceNotFoundException e) {
			// Something else unregistered the bean
		}
	}

	private void safelyRegisterMBean(Object mbean, ObjectName name) {
		try {
			try {
				this.mbeanServer.registerMBean(mbean, name);
			} catch (InstanceAlreadyExistsException e) {
				// Recover as this happens when a JVM is reused.
				this.mbeanServer.unregisterMBean(name);
				this.mbeanServer.registerMBean(mbean, name);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("MBean registration failed", e); //$NON-NLS-1$
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ManageableRegion[] getRegions() {
		List<ManageableRegion> regions = new ArrayList<>();
		synchronized (this.monitor) {
			for (ManageableRegion manageableRegion : this.manageableRegions.values()) {
				regions.add(manageableRegion);
			}
		}
		return regions.toArray(new ManageableRegion[regions.size()]);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ManageableRegion getRegion(String regionName) {
		return this.manageableRegions.get(regionName);
	}

	void addRegion(Region region) {
		StandardManageableRegion manageableRegion = new StandardManageableRegion(region, this, this.regionDigraph);
		safelyRegisterMBean(manageableRegion, this.regionObjectNameCreator.getRegionObjectName(region.getName()));
		synchronized (this.monitor) {
			this.manageableRegions.put(region.getName(), manageableRegion);
		}
	}

	void removeRegion(Region region) {
		removeRegion(region.getName());
	}

	private void removeRegion(String regionName) {
		synchronized (this.monitor) {
			this.manageableRegions.remove(regionName);
		}
		try {
			this.mbeanServer.unregisterMBean(this.regionObjectNameCreator.getRegionObjectName(regionName));
		} catch (MBeanRegistrationException e) {
			e.printStackTrace();
			throw new RuntimeException("Problem unregistering mbean", e); //$NON-NLS-1$
		} catch (InstanceNotFoundException e) {
			// Something else unregistered the bean
		}
	}

}
