/*
 * 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.aries.subsystem.core.capabilityset.CapabilitySetRepository;
import org.osgi.framework.Constants;
import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.namespace.NativeNamespace;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.resource.Wire;
import org.osgi.resource.Wiring;
import org.osgi.service.resolver.HostedCapability;
import org.osgi.service.resolver.ResolutionException;
import org.osgi.service.resolver.Resolver;

public class DependencyCalculator {
	private static class ResolveContext extends
			org.osgi.service.resolver.ResolveContext {
	    private final CapabilitySetRepository repository = new CapabilitySetRepository();
		private final Collection<Resource> resources;

		public ResolveContext(Collection<Resource> resources) {
		    this.resources = resources;
			for (Resource resource : resources) {
			    repository.addResource(resource);
			}
		}

		@Override
		public List<Capability> findProviders(Requirement requirement) {
		    String namespace = requirement.getNamespace();
		    // never check local resources for osgi.ee or osgi.native capabilities
		    if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(namespace) 
                    || NativeNamespace.NATIVE_NAMESPACE.equals(namespace)) {
		        return Collections.<Capability>singletonList(new MissingCapability(requirement));
		    }
			Map<Requirement, Collection<Capability>> map = repository.findProviders(Collections.singleton(requirement));
			Collection<Capability> capabilities = map.get(requirement);
			if (!capabilities.isEmpty()) {
			    return new ArrayList<Capability>(capabilities);
			}
			return Collections.<Capability>singletonList(new MissingCapability(requirement));
		}

		@Override
		public boolean isEffective(Requirement requirement) {
			return true;
		}
		
		@Override
		public Collection<Resource> getMandatoryResources() {
			return resources;
		}

		@Override
		public Map<Resource, Wiring> getWirings() {
			return Collections.emptyMap();
		}

		@Override
		public int insertHostedCapability(List<Capability> capabilities,
				HostedCapability hostedCapability) {
			int sz = capabilities.size();
			capabilities.add(sz, hostedCapability);
	        return sz;
		}
	}

	static class MissingCapability extends AbstractCapability {
		private static class Resource implements org.osgi.resource.Resource {
			public static final Resource INSTANCE = new Resource();
			
			private final Capability identity;
			
			public Resource() {
				Map<String, Object> attributes = new HashMap<String, Object>();
				attributes.put(IdentityNamespace.IDENTITY_NAMESPACE, org.apache.aries.subsystem.core.internal.Constants.ResourceTypeSynthesized);
				attributes.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, org.apache.aries.subsystem.core.internal.Constants.ResourceTypeSynthesized);
				identity = new BasicCapability(IdentityNamespace.IDENTITY_NAMESPACE, attributes, null, this);
			}
			
			@Override
			public List<Capability> getCapabilities(String namespace) {
				return Collections.singletonList(identity);
			}

			@Override
			public List<Requirement> getRequirements(String namespace) {
				return Collections.emptyList();
			}
		}
		
		private final Map<String, Object> attributes = new HashMap<String, Object>();
		private final Requirement requirement;

		public MissingCapability(Requirement requirement) {
			this.requirement = requirement;
			initializeAttributes();
		}

		@Override
		public Map<String, Object> getAttributes() {
			return Collections.unmodifiableMap(attributes);
		}

		@Override
		public Map<String, String> getDirectives() {
			return Collections.emptyMap();
		}

		@Override
		public String getNamespace() {
			return requirement.getNamespace();
		}

		@Override
		public Resource getResource() {
			return Resource.INSTANCE;
		}
		
		private void initializeAttributes() {
			String filter = requirement.getDirectives().get(Constants.FILTER_DIRECTIVE);
			if (filter == null)
				return;
			Pattern pattern = Pattern.compile("\\(([^<>~(=]+)(?:=|<=|>=|~=)([^)]+)\\)");
			Matcher matcher = pattern.matcher(filter);
			while (matcher.find())
				attributes.put(matcher.group(1), matcher.group(2));
		}
	}

	private final ResolveContext context;

	public DependencyCalculator(Collection<Resource> resources) {
		context = new ResolveContext(resources);
	}

	public List<Requirement> calculateDependencies() throws ResolutionException {
		ArrayList<Requirement> result = new ArrayList<Requirement>();
		Resolver resolver = Activator.getInstance().getResolver();
		Map<Resource, List<Wire>> resolution = resolver.resolve(context);
		for (List<Wire> wires : resolution.values())
			for (Wire wire : wires)
				if (wire.getCapability() instanceof MissingCapability)
					result.add(wire.getRequirement());
		result.trimToSize();
		return result;
	}
}
