package aQute.bnd.component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.osgi.service.component.annotations.ReferenceCardinality;

import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Clazz;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Descriptors;
import aQute.bnd.osgi.Instruction;
import aQute.bnd.osgi.Instructions;
import aQute.bnd.osgi.Processor;
import aQute.bnd.service.AnalyzerPlugin;
import aQute.bnd.version.Version;
import aQute.bnd.xmlattribute.XMLAttributeFinder;
import aQute.lib.strings.Strings;

/**
 * Analyze the class space for any classes that have an OSGi annotation for DS.
 */
public class DSAnnotations implements AnalyzerPlugin {

	public enum Options {
		inherit, felixExtensions, extender, nocapabilities, norequirements,

		version {
			@Override
			void process(DSAnnotations anno, Attrs attrs) {
				String v = attrs.get("minimum");
				if (v != null && v.length() > 0) {
					anno.minVersion = new Version(v);
				}
			}

			@Override
			void reset(DSAnnotations anno) {
				anno.minVersion = AnnotationReader.V1_3;
			}
		};

		void process(DSAnnotations anno, Attrs attrs) {

		}

		void reset(DSAnnotations anno) {

		}

		static void parseOption(Map.Entry<String,Attrs> entry, EnumSet<Options> options, DSAnnotations state) {
			String s = entry.getKey();
			boolean negation = false;
			if (s.startsWith("!")) {
				negation = true;
				s = s.substring(1);
			}
			Options option = Options.valueOf(s);
			if (negation) {
				options.remove(option);
				option.reset(state);
			} else {
				options.add(option);
				Attrs attrs;
				if ((attrs = entry.getValue()) != null) {
					option.process(state, attrs);
				}
			}

		}
	};

	Version minVersion;

	public boolean analyzeJar(Analyzer analyzer) throws Exception {
		Parameters header = OSGiHeader.parseHeader(analyzer.getProperty(Constants.DSANNOTATIONS, "*"));
		if (header.size() == 0)
			return false;

		minVersion = AnnotationReader.V1_3;
		Parameters optionsHeader = OSGiHeader.parseHeader(analyzer.mergeProperties(Constants.DSANNOTATIONS_OPTIONS));
		EnumSet<Options> options = EnumSet.noneOf(Options.class);
		for (Map.Entry<String,Attrs> entry : optionsHeader.entrySet()) {
			try {
				Options.parseOption(entry, options, this);
			} catch (IllegalArgumentException e) {
				analyzer.error("Unrecognized %s value %s with attributes %s, expected values are %s",
						Constants.DSANNOTATIONS_OPTIONS, entry.getKey(), entry.getValue(),
						EnumSet.allOf(Options.class));
			}
		}
		// obsolete but backwards compatible, use the options instead
		if (Processor.isTrue(analyzer.getProperty("-dsannotations-inherit")))
			options.add(Options.inherit);
		if (Processor.isTrue(analyzer.getProperty("-ds-felix-extensions")))
			options.add(Options.felixExtensions);

		Instructions instructions = new Instructions(header);
		Collection<Clazz> list = analyzer.getClassspace().values();
		String sc = analyzer.getProperty(Constants.SERVICE_COMPONENT);
		List<String> names = new ArrayList<String>();
		if (sc != null && sc.trim().length() > 0)
			names.add(sc);

		TreeSet<String> provides = new TreeSet<String>();
		TreeSet<String> requires = new TreeSet<String>();
		Version maxVersion = AnnotationReader.V1_0;

		XMLAttributeFinder finder = new XMLAttributeFinder(analyzer);
		boolean componentProcessed = false;
		for (Clazz c : list) {
			for (Instruction instruction : instructions.keySet()) {

				if (instruction.matches(c.getFQN())) {
					if (instruction.isNegated())
						break;
					ComponentDef definition = AnnotationReader.getDefinition(c, analyzer, options, finder, minVersion);
					if (definition != null) {
						componentProcessed = true;
						definition.sortReferences();
						definition.prepare(analyzer);

						String name = "OSGI-INF/"
								+ analyzer.validResourcePath(definition.name, "Invalid component name") + ".xml";
						names.add(name);
						analyzer.getJar().putResource(name, new TagResource(definition.getTag()));

						if (definition.service != null && !options.contains(Options.nocapabilities)) {
							String[] objectClass = new String[definition.service.length];

							for (int i = 0; i < definition.service.length; i++) {
								Descriptors.TypeRef tr = definition.service[i];
								objectClass[i] = tr.getFQN();
							}
							Arrays.sort(objectClass);
							addServiceCapability(objectClass, provides);
						}

						if (!options.contains(Options.norequirements)) {
							MergedRequirement serviceReqMerge = new MergedRequirement("osgi.service");
							for (ReferenceDef ref : definition.references.values()) {
								addServiceRequirement(ref, serviceReqMerge);
							}
							requires.addAll(serviceReqMerge.toStringList());
						}
						maxVersion = ComponentDef.max(maxVersion, definition.version);
					}
				}
			}
		}
		if (componentProcessed
				&& (options.contains(Options.extender) || (maxVersion.compareTo(AnnotationReader.V1_3) >= 0))) {
			maxVersion = ComponentDef.max(maxVersion, AnnotationReader.V1_3);
			addExtenderRequirement(requires, maxVersion);
		}
		sc = Processor.append(names.toArray(new String[0]));
		analyzer.setProperty(Constants.SERVICE_COMPONENT, sc);
		updateHeader(analyzer, Constants.REQUIRE_CAPABILITY, requires);
		updateHeader(analyzer, Constants.PROVIDE_CAPABILITY, provides);
		return false;
	}

	private void addServiceCapability(String[] objectClass, Set<String> provides) {
		if (objectClass.length > 0) {
			Parameters p = new Parameters();
			Attrs a = new Attrs();
			StringBuilder sb = new StringBuilder();
			String sep = "";
			for (String oc : objectClass) {
				sb.append(sep).append(oc);
				sep = ",";
			}
			a.put("objectClass:List<String>", sb.toString());
			p.put("osgi.service", a);
			String s = p.toString();
			provides.add(s);
		}
	}

	private void addServiceRequirement(ReferenceDef ref, MergedRequirement requires) {
		String objectClass = ref.service;
		ReferenceCardinality cardinality = ref.cardinality;
		boolean optional = cardinality == ReferenceCardinality.OPTIONAL || cardinality == ReferenceCardinality.MULTIPLE;
		boolean multiple = cardinality == ReferenceCardinality.MULTIPLE
				|| cardinality == ReferenceCardinality.AT_LEAST_ONE;

		String filter = "(objectClass=" + objectClass + ")";
		requires.put(filter, "active", optional, multiple);
	}

	private void addExtenderRequirement(Set<String> requires, Version version) {
		Version next = new Version(version.getMajor() + 1);
		Parameters p = new Parameters();
		Attrs a = new Attrs();
		a.put(Constants.FILTER_DIRECTIVE,
				"\"(&(osgi.extender=osgi.component)(version>=" + version + ")(!(version>=" + next + ")))\"");
		p.put("osgi.extender", a);
		String s = p.toString();
		requires.add(s);
	}

	/**
	 * Updates specified header, sorting and removing duplicates. Destroys
	 * contents of set parameter.
	 * 
	 * @param analyzer
	 * @param name header name
	 * @param set values to add to header; contents are not preserved.
	 */
	private void updateHeader(Analyzer analyzer, String name, TreeSet<String> set) {
		if (!set.isEmpty()) {
			String value = analyzer.getProperty(name);
			if (value != null) {
				Parameters p = OSGiHeader.parseHeader(value);
				for (Map.Entry<String,Attrs> entry : p.entrySet()) {
					StringBuilder sb = new StringBuilder(entry.getKey());
					if (entry.getValue() != null) {
						sb.append(";");
						entry.getValue().append(sb);
					}
					set.add(sb.toString());
				}
			}
			String header = Strings.join(set);
			analyzer.setProperty(name, header);
		}
	}

	@Override
	public String toString() {
		return "DSAnnotations";
	}
}
