/*
 * 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 org.apache.aries.subsystem.ContentHandler;
import org.apache.aries.subsystem.core.archive.ProvisionResourceHeader;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.resource.Resource;
import org.osgi.service.subsystem.SubsystemConstants;
import org.osgi.service.subsystem.SubsystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class ResourceUninstaller {
	private static final Logger logger = LoggerFactory.getLogger(ResourceUninstaller.class);

	public static ResourceUninstaller newInstance(Resource resource, BasicSubsystem subsystem) {
		String type = ResourceHelper.getTypeAttribute(resource);
		if (SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION.equals(type)
				|| SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE.equals(type)
				|| SubsystemConstants.SUBSYSTEM_TYPE_FEATURE.equals(type)) {
			return new SubsystemResourceUninstaller(resource, subsystem);
		} else if (IdentityNamespace.TYPE_BUNDLE.equals(type) || IdentityNamespace.TYPE_FRAGMENT.equals(type)) {
			return new BundleResourceUninstaller(resource, subsystem);
		} else {
		    ServiceReference<ContentHandler> handlerRef = CustomResources.getCustomContentHandler(subsystem, type);
		    if (handlerRef != null) {
		        return new CustomResourceUninstaller(resource, type, subsystem, handlerRef);
		    } else {
		        throw new SubsystemException("No uninstaller exists for resource type: " + type);
		    }
		}
	}

	protected static void removeConstituent(BasicSubsystem subsystem, Resource resource) {
		Activator.getInstance().getSubsystems().removeConstituent(subsystem, resource);
	}

	protected static void removeReference(BasicSubsystem subsystem, Resource resource) {
		Activator.getInstance().getSubsystems().removeReference(subsystem, resource);
	}

	protected final BasicSubsystem provisionTo;
	protected final Resource resource;
	protected final BasicSubsystem subsystem;

	public ResourceUninstaller(Resource resource, BasicSubsystem subsystem) {
		if (resource == null)
			throw new NullPointerException("Missing required parameter: resource");
		if (subsystem == null)
			throw new NullPointerException("Missing required parameter: subsystem");
		this.resource = resource;
		this.subsystem = subsystem;
		if (isTransitive())
			provisionTo = Utils.findFirstSubsystemAcceptingDependenciesStartingFrom(subsystem);
		else
			provisionTo = subsystem;
	}

	public abstract void uninstall();

	protected boolean isExplicit() {
		// The operation is explicit if it was requested by a user, in which
		// case the resource and subsystem are the same.
		if (resource.equals(subsystem))
			return true;
		// The operation is explicit if it was requested by a scoped subsystem
		// on a resource within the same region.
		if (subsystem.isScoped()) {
			if (Utils.isBundle(resource))
				return subsystem.getRegion().contains(((BundleRevision)resource).getBundle());
			// TODO This is insufficient. The unscoped subsystem could be a
			// dependency in another region, which would make it implicit.
			return !((BasicSubsystem)resource).isScoped();
		}
		return false;
	}

	protected boolean isTransitive() {
		ProvisionResourceHeader header = subsystem.getDeploymentManifest().getProvisionResourceHeader();
		if (header == null)
			return false;
		return header.contains(resource);
	}

	protected boolean isResourceUninstallable() {
		int referenceCount = Activator.getInstance().getSubsystems().getSubsystemsReferencing(resource).size();
		if (referenceCount == 0)
			return true;
		if (isExplicit()) {
			logger.error("Explicitly uninstalling resource still has dependencies: {}", resource);
			return true;
		}
		return false;
	}

	protected void removeConstituent() {
		removeConstituent(subsystem, resource);
	}

	protected void removeReference() {
		removeReference(subsystem, resource);
	}
}
