/*
 * 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.Map;

import org.apache.aries.subsystem.core.archive.DeploymentManifest;
import org.apache.aries.subsystem.core.archive.ProvisionResourceHeader;
import org.apache.aries.subsystem.core.archive.SubsystemContentHeader;
import org.apache.aries.subsystem.core.archive.SubsystemManifest;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.resource.Namespace;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.service.coordinator.Coordination;
import org.osgi.service.coordinator.CoordinationException;
import org.osgi.service.subsystem.Subsystem;
import org.osgi.service.subsystem.Subsystem.State;
import org.osgi.service.subsystem.SubsystemConstants;
import org.osgi.service.subsystem.SubsystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Utils {
	private static final Logger logger = LoggerFactory.getLogger(Utils.class);
	
	public static String computeCoordinationName(Subsystem subsystem) {
		return subsystem.getSymbolicName() + '-' + subsystem.getSubsystemId();
	}
	
	public static Coordination createCoordination() {
		return Activator.getInstance().getCoordinator().begin(BasicSubsystem.ROOT_SYMBOLIC_NAME + "-0", 0);
	}
	
	public static Coordination createCoordination(BasicSubsystem subsystem) {
		return Activator.getInstance().getCoordinator().begin(computeCoordinationName(subsystem), 0);
	}
	
	public static BasicSubsystem findFirstSubsystemAcceptingDependenciesStartingFrom(BasicSubsystem subsystem) {
		// The following loop is guaranteed to end once the root subsystem has
		// been reached.
		while (!isAcceptDependencies(subsystem))
			subsystem = (BasicSubsystem)subsystem.getParents().iterator().next();
		return subsystem;
	}
	
	public static BasicSubsystem findFirstScopedAncestorWithSharingPolicy(SubsystemResource descendant) {
		Collection<BasicSubsystem> parents = descendant.getParents();
		if (parents == null || parents.isEmpty()) {
			return null;
		}
		BasicSubsystem result = (BasicSubsystem) parents.iterator().next();
		// The result is defined as the first scoped ancestor whose sharing 
		// policy has already been set. This covers the case of multiple 
		// subsystems from the same archive being installed whose regions will 
		// form a tree of depth N.
		while (	// We only want scoped subsystems because they control 
				// the region.
				!result.isScoped()
				// If the state is INSTALLING then the sharing policy 
				// has not yet been set. This means we cannot use the
				// region in order to test visibility and must proceed
				// to the next parent.
				|| result.getState().equals(State.INSTALLING)) {
			result = result.getResource().getParents().iterator().next();
		}
		return result;
	}
	
	public static BasicSubsystem findScopedSubsystemInRegion(BasicSubsystem subsystem) {
		while (!subsystem.isScoped())
			subsystem = (BasicSubsystem)subsystem.getParents().iterator().next();
		return subsystem;
	}

	public static int getActiveUseCount(Resource resource) {
		int result = 0;
		for (BasicSubsystem subsystem : Activator.getInstance().getSubsystems().getSubsystemsReferencing(resource)) {
			if (	// ACTIVE subsystem referencing the resource.
					Subsystem.State.ACTIVE.equals(subsystem.getState())
					// Ensure unmanaged bundle constituents of the root subsystem are not stopped or uninstalled.
					|| (subsystem.isRoot() && isBundle(resource))) { 
				result++;
			}
		}
		return result;
	}
	
	public static long getId(Resource resource) {
		if (resource instanceof BasicSubsystem)
			return ((BasicSubsystem)resource).getSubsystemId();
		if (resource instanceof BundleRevision)
			return ((BundleRevision)resource).getBundle().getBundleId();
		return -1;
	}
	
	public static void handleTrowable(Throwable t) {
		if (t instanceof SubsystemException) {
			throw (SubsystemException)t;
		}
		if (t instanceof SecurityException) {
			throw (SecurityException)t;
		}
		throw new SubsystemException(t);
	}
	
	public static void installResource(Resource resource, BasicSubsystem subsystem) {
		Coordination coordination = Utils.createCoordination(subsystem);
		try {
			ResourceInstaller.newInstance(coordination, resource, subsystem).install();
		}
		catch (Throwable t) {
			coordination.fail(t);
		}
		finally {
			try {
				coordination.end();
			}
			catch (CoordinationException e) {
				logger.error("Resource could not be installed", e);
			}
		}
	}
	
	public static boolean isAcceptDependencies(BasicSubsystem subsystem) {
		return subsystem.getSubsystemManifest().getSubsystemTypeHeader().getProvisionPolicyDirective().isAcceptDependencies();
	}
	
	public static boolean isBundle(Resource resource) {
		String type = ResourceHelper.getTypeAttribute(resource);
		return IdentityNamespace.TYPE_BUNDLE.equals(type) ||
				IdentityNamespace.TYPE_FRAGMENT.equals(type);
	}
	
	public static boolean isFragment(Resource resource) {
		String type = ResourceHelper.getTypeAttribute(resource);
		return IdentityNamespace.TYPE_FRAGMENT.equals(type);
	}
	
	public static boolean isEffectiveResolve(Requirement requirement) {
		Map<String, String> directives = requirement.getDirectives();
		String value = directives.get(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
		return value == null || Namespace.EFFECTIVE_RESOLVE.equals(value);
	}
	
	public static boolean isMandatory(Requirement requirement) {
		Map<String, String> directives = requirement.getDirectives();
		String value = directives.get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
		return value == null || Namespace.RESOLUTION_MANDATORY.equals(value);
	}
	
	/*
	 * The Deployed-Content header in the deployment manifest is used to store
	 * information about explicitly installed resources and provisioned
	 * dependencies in addition to content for persistence purposes. This method
	 * returns true only if the resource is "true" content of the subsystem and,
	 * therefore, uses the Subsystem-Content header from the subsystem manifest.
	 */
	public static boolean isContent(BasicSubsystem subsystem, Resource resource) {
		SubsystemManifest subsystemManifest = subsystem.getSubsystemManifest();
		if (subsystemManifest == null)
			return false;
		SubsystemContentHeader subsystemContentHeader = subsystemManifest.getSubsystemContentHeader();
		if (subsystemContentHeader == null)
			return false;
		return subsystemContentHeader.contains(resource);
	}
	
	public static boolean isDependency(BasicSubsystem subsystem, Resource resource) {
		DeploymentManifest manifest = subsystem.getDeploymentManifest();
		if (manifest == null)
			return false;
		ProvisionResourceHeader header = manifest.getProvisionResourceHeader();
		if (header == null)
			return false;
		return header.contains(resource);
	}
	
	public static boolean isInstallableResource(Resource resource) {
		return !isSharedResource(resource);
	}
	
	public static boolean isRegionContextBundle(Resource resource) {
		return ResourceHelper.getSymbolicNameAttribute(resource).startsWith(
				RegionContextBundleHelper.SYMBOLICNAME_PREFIX);
	}
	
	public static boolean isSharedResource(Resource resource) {
		return resource instanceof BasicSubsystem || resource instanceof BundleRevision || resource instanceof BundleRevisionResource;
	}
	
	public static boolean isSubsystem(Resource resource) {
		String type = ResourceHelper.getTypeAttribute(resource);
		return SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION.equals(type) ||
				SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE.equals(type) ||
				SubsystemConstants.SUBSYSTEM_TYPE_FEATURE.equals(type);
	}
	
	public static boolean isProvisionDependenciesInstall(BasicSubsystem subsystem) {
		return subsystem.getSubsystemManifest().getSubsystemTypeHeader().getAriesProvisionDependenciesDirective().isInstall();
	}
}
