package org.osgi.service.indexer.impl;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.osgi.framework.Version;
import org.osgi.service.indexer.Builder;
import org.osgi.service.indexer.Capability;
import org.osgi.service.indexer.Requirement;
import org.osgi.service.indexer.Resource;
import org.osgi.service.indexer.ResourceAnalyzer;
import org.osgi.service.indexer.impl.types.SymbolicName;
import org.osgi.service.indexer.impl.types.VersionRange;
import org.osgi.service.indexer.impl.util.OSGiHeader;

public class KnownBundleAnalyzer implements ResourceAnalyzer {

	private final Properties	defaultProperties;
	private Properties			extraProperties	= null;

	public KnownBundleAnalyzer() {
		defaultProperties = new Properties();
		try (InputStream stream = KnownBundleAnalyzer.class.getResourceAsStream("known-bundles.properties")) {
			if (stream != null) {
				defaultProperties.load(stream);
			}
		} catch (IOException e) {
			// ignore
		}
	}

	public KnownBundleAnalyzer(Properties properties) {
		this.defaultProperties = properties;
	}

	public void setKnownBundlesExtra(Properties extras) {
		this.extraProperties = extras;
	}

	public void analyzeResource(Resource resource, List<Capability> caps, List<Requirement> reqs) throws Exception {
		SymbolicName resourceName;
		try {
			resourceName = Util.getSymbolicName(resource);
		} catch (IllegalArgumentException e) {
			// not a bundle, so return without analyzing
			return;
		}

		for (Enumeration< ? > names = defaultProperties.propertyNames(); names.hasMoreElements();) {
			String propName = (String) names.nextElement();
			processPropertyName(resource, caps, reqs, resourceName, propName, defaultProperties);
		}

		if (extraProperties != null)
			for (Enumeration< ? > names = extraProperties.propertyNames(); names.hasMoreElements();) {
				String propName = (String) names.nextElement();
				processPropertyName(resource, caps, reqs, resourceName, propName, extraProperties, defaultProperties);
			}
	}

	private static void processPropertyName(Resource resource, List<Capability> caps, List<Requirement> reqs,
			SymbolicName resourceName, String name, Properties... propertiesList) throws IOException {
		String[] bundleRef = name.split(";");
		String bsn = bundleRef[0];

		if (resourceName.getName().equals(bsn)) {
			VersionRange versionRange = null;
			if (bundleRef.length > 1)
				versionRange = new VersionRange(bundleRef[1]);

			Version version = Util.getVersion(resource);
			if (versionRange == null || versionRange.match(version)) {
				processClause(name, Util.readProcessedProperty(name, propertiesList), caps, reqs);
			}
		}
	}

	private static enum IndicatorType {
		Capability("cap="), Requirement("req=");

		String prefix;

		IndicatorType(String prefix) {
			this.prefix = prefix;
		}

		public String getPrefix() {
			return prefix;
		}
	}

	private static void processClause(String bundleRef, String clauseStr, List<Capability> caps,
			List<Requirement> reqs) {
		Map<String,Map<String,String>> header = OSGiHeader.parseHeader(clauseStr);

		for (Entry<String,Map<String,String>> entry : header.entrySet()) {
			String indicator = OSGiHeader.removeDuplicateMarker(entry.getKey());
			IndicatorType type;

			String namespace;
			if (indicator.startsWith(IndicatorType.Capability.getPrefix())) {
				type = IndicatorType.Capability;
				namespace = indicator.substring(IndicatorType.Capability.getPrefix().length());
			} else if (indicator.startsWith(IndicatorType.Requirement.getPrefix())) {
				type = IndicatorType.Requirement;
				namespace = indicator.substring(IndicatorType.Requirement.getPrefix().length());
			} else {
				throw new IllegalArgumentException(MessageFormat.format(
						"Invalid indicator format in known-bundle parsing for bundle  \"{0}\", expected cap=namespace or req=namespace, found \"{1}\".",
						bundleRef, indicator));
			}

			Builder builder = new Builder().setNamespace(namespace);

			Map<String,String> attribs = entry.getValue();
			Util.copyAttribsToBuilder(builder, attribs);

			if (type == IndicatorType.Capability)
				caps.add(builder.buildCapability());
			else if (type == IndicatorType.Requirement)
				reqs.add(builder.buildRequirement());
		}
	}

}
