/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.aries.subsystem.core.internal;

import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;

import org.apache.aries.subsystem.AriesSubsystem;
import org.eclipse.equinox.region.Region;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.subsystem.Subsystem;
import org.osgi.service.subsystem.SubsystemConstants;

public class SubsystemServiceRegistrar {
	private final BundleContext context;
	private final Map<Subsystem, ServiceRegistration<?>> map = new HashMap<Subsystem, ServiceRegistration<?>>();
	
	public SubsystemServiceRegistrar(BundleContext context) {
		if (context == null)
			throw new NullPointerException("Missing required parameter: context");
		this.context = context;
	}
	
	public void addRegion(BasicSubsystem subsystem, Region region) {
		ServiceRegistration<?> registration;
		Dictionary<String, Object> properties;
		synchronized (this) {
			registration = map.get(subsystem);
			if (registration == null)
				throw new IllegalStateException("Subsystem '" + subsystem + "' is not registered");
			Collection<String> currentRegions = (Collection<String>)registration.getReference().getProperty(Constants.SubsystemServicePropertyRegions);
			String regionName = region.getName();
			if (currentRegions.contains(regionName))
				return;
			Collection<String> newRegions = new HashSet<String>(currentRegions.size() + 1);
			newRegions.addAll(currentRegions);
			newRegions.add(regionName);
			properties = properties(subsystem);
			properties.put(Constants.SubsystemServicePropertyRegions, Collections.unmodifiableCollection(newRegions));
		}
		registration.setProperties(properties);
	}
	
	public synchronized Subsystem getSubsystemService(BasicSubsystem subsystem) {
		ServiceRegistration<?> registration = map.get(subsystem);
		if (registration == null)
			return null;
		return (Subsystem)Activator.getInstance().getBundleContext().getService(registration.getReference());
	}
	
	public void register(BasicSubsystem child, BasicSubsystem parent) {
		Dictionary<String, Object> properties;
		synchronized (this) {
			if (map.containsKey(child))
				return;
			map.put(child, null);
			properties = properties(child, parent);
		}
		ServiceRegistration<?> registration = null;
		try {
			registration = context.registerService(
				new String[] {Subsystem.class.getName(), AriesSubsystem.class.getName()}, 
				child, properties);
		}
		finally {
			synchronized (this) {
				if (registration == null)
					map.remove(child);
				else
					map.put(child, registration);
			}
		}
	}
	
	public void removeRegion(BasicSubsystem subsystem, Region region) {
		ServiceRegistration<?> registration;
		Dictionary<String, Object> properties;
		synchronized (this) {
			registration = map.get(subsystem);
			if (registration == null)
				return;
			Collection<String> regions = (Collection<String>)registration.getReference().getProperty(Constants.SubsystemServicePropertyRegions);
			String regionName = region.getName();
			if (regions == null || !regions.contains(regionName))
				return;
			regions = new HashSet<String>(regions);
			regions.remove(regionName);
			properties = properties(subsystem);
			properties.put(Constants.SubsystemServicePropertyRegions, Collections.unmodifiableCollection(regions));
		}
		registration.setProperties(properties);
	}
	
	public void unregister(Subsystem subsystem) {
		ServiceRegistration<?> registration;
		synchronized (this) {
			registration = map.remove(subsystem);
			if (registration == null)
				throw new IllegalStateException("Subsystem '" + subsystem + "' is not registered");
		}
		registration.unregister();
	}
	
	public void update(BasicSubsystem subsystem) {
		ServiceRegistration<?> registration;
		Dictionary<String, Object> properties;
		synchronized (this) {
			registration = map.get(subsystem);
			if (registration == null)
				throw new IllegalStateException("Subsystem '" + subsystem + "' is not registered");
			properties = properties(subsystem, registration);
		}
		registration.setProperties(properties);
	}
	
	private Dictionary<String, Object> properties(BasicSubsystem subsystem) {
		Dictionary<String, Object> result = new Hashtable<String, Object>();
		result.put(SubsystemConstants.SUBSYSTEM_ID_PROPERTY, subsystem.getSubsystemId());
		result.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME_PROPERTY, subsystem.getSymbolicName());
		result.put(SubsystemConstants.SUBSYSTEM_VERSION_PROPERTY, subsystem.getVersion());
		result.put(SubsystemConstants.SUBSYSTEM_TYPE_PROPERTY, subsystem.getType());
		result.put(SubsystemConstants.SUBSYSTEM_STATE_PROPERTY, subsystem.getState());
		result.put(Constants.SubsystemServicePropertyRegions, Collections.singleton(subsystem.getRegionName()));
		return result;
	}
	
	private Dictionary<String, Object> properties(BasicSubsystem child, BasicSubsystem parent) {
		Dictionary<String, Object> result = properties(child);
		if (parent == null)
			return result;
		Collection<String> currentRegions = (Collection<String>)result.get(Constants.SubsystemServicePropertyRegions);
		Collection<String> newRegions = new HashSet<String>(currentRegions.size() + 1);
		newRegions.addAll(currentRegions);
		newRegions.add(parent.getRegion().getName());
		result.put(Constants.SubsystemServicePropertyRegions, Collections.unmodifiableCollection(newRegions));
		return result;
	}
	
	private Dictionary<String, Object> properties(BasicSubsystem subsystem, ServiceRegistration<?> registration) {
		Dictionary<String, Object> result = properties(subsystem);
		Collection<String> regions = (Collection<String>)registration.getReference().getProperty(Constants.SubsystemServicePropertyRegions);
		if (regions == null)
			return result;
		result.put(Constants.SubsystemServicePropertyRegions, regions);
		return result;
	}
}
