/*******************************************************************************
 * Copyright (c) 2004, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.osgi.internal.module;

import java.util.ArrayList;
import java.util.HashMap;
import org.osgi.framework.Constants;

public class GroupingChecker {
	HashMap constraints = new HashMap();

	void addConstraint(ResolverExport constrained, ResolverExport constraint) {
		ArrayList list = (ArrayList) constraints.get(constrained);
		if (list == null) {
			list = new ArrayList();
			constraints.put(constrained, list);
		}
		if (!list.contains(constraint))
			list.add(constraint);
	}

	private void addConstraints(ResolverExport constrained, ArrayList newConstraints) {
		ArrayList list = (ArrayList) constraints.get(constrained);
		if (list == null) {
			constraints.put(constrained, newConstraints);
		} else {
			for (int i = 0; i < newConstraints.size(); i++) {
				ResolverExport exp = (ResolverExport) newConstraints.get(i);
				if (!list.contains(exp))
					list.add(newConstraints.get(i));
			}
		}
	}

	void removeConstraint(ResolverExport constrained, ResolverExport constraint) {
		ArrayList list = (ArrayList) constraints.get(constrained);
		if (list == null)
			return;
		list.remove(constraint);
	}

	void removeAllConstraints(ResolverExport constrained) {
		constraints.put(constrained, null);
	}

	ResolverExport isConsistent(ResolverImport imp, ResolverExport exp) {
		// Check imports are consistent
		ResolverImport[] imports = imp.getBundle().getImportPackages();
		BundleConstraint[] requires = imp.getBundle().getRequires();
		for (int i = 0; i < imports.length; i++) {
			// Check new wiring against constraints from previous wirings
			ResolverExport wire = imports[i].getMatchingExport();
			if (wire == null)
				continue;
			ArrayList list = (ArrayList) constraints.get(wire);
			if (list != null) {
				for (int j = 0; j < list.size(); j++) {
					ResolverExport re = (ResolverExport) list.get(j);
					if (re.isDropped())
						continue;
					if (re.getExporter().isResolvable() && exp.getName().equals(re.getName()) && !imp.isOnRootPath(re.getExporter()) && !imp.isOnRootPathSplit(imp.getMatchingExport().getExporter(), re.getExporter()))
						return wire;
				}
			}
			// Check previous wirings against constraints from new wiring
			list = (ArrayList) constraints.get(exp);
			if (list != null) {
				for (int j = 0; j < list.size(); j++) {
					ResolverExport re = (ResolverExport) list.get(j);
					if (re.isDropped())
						continue;
					if (re.getExporter().isResolvable() && wire.getName().equals(re.getName()) && !imports[i].isOnRootPath(re.getExporter()) && !imp.isOnRootPathSplit(imp.getMatchingExport().getExporter(), re.getExporter()))
						return wire;
					// Check against requires
					for (int k = 0; k < requires.length; k++) {
						if (requires[k].getMatchingBundle() == null)
							continue;
						ResolverExport[] exports = requires[k].getMatchingBundle().getExportPackages();
						for (int m = 0; m < exports.length; m++) {
							if (re.getExporter().isResolvable() && exports[m].getName().equals(re.getName()) && !exports[m].isOnRootPath(re.getExporter()))
								return re;
						}
					}
				}
			}
		}
		// Check imports against requires
		for (int i = 0; i < requires.length; i++) {
			if (requires[i].getMatchingBundle() == null)
				continue;
			ResolverExport[] exports = requires[i].getMatchingBundle().getExportPackages();
			for (int j = 0; j < exports.length; j++) {
				ArrayList list = (ArrayList) constraints.get(exports[j]);
				if (list != null) {
					for (int k = 0; k < list.size(); k++) {
						ResolverExport re = (ResolverExport) list.get(k);
						if (re.getExporter().isResolvable() && exp.getName().equals(re.getName()) && !imp.isOnRootPath(re.getExporter()))
							return re;
					}
				}
			}
		}
		// No clash, so return null
		return null;
	}

	boolean checkRequiresConstraints(ResolverBundle bundle) {
		BundleConstraint[] requires = bundle.getRequires();
		if (requires == null)
			return true;
		for (int i = 0; i < requires.length; i++) {
			ResolverBundle matchingBundle = requires[i].getMatchingBundle();
			if (matchingBundle == null)
				continue;
			ResolverExport[] exports = matchingBundle.getExportPackages();
			for (int j = 0; j < exports.length; j++) {
				ArrayList list = (ArrayList) constraints.get(exports[j]);
				if (list == null)
					continue;
				for (int k = 0; k < list.size(); k++) {
					ResolverExport constraint = (ResolverExport) list.get(k);
					boolean foundPotential = false;
					boolean found = false;
					for (int m = 0; m < requires.length; m++) {
						if (requires[m].getMatchingBundle() == null)
							continue;
						ResolverExport[] exps = requires[m].getMatchingBundle().getExportPackages();
						for (int n = 0; n < exps.length; n++) {
							if (constraint.getExporter().isResolvable() && constraint.getName().equals(exps[n].getName())) {
								foundPotential = true;
								if (exps[n] == constraint || exps[n].isOnRootPath(constraint.getExporter())) {
									found = true;
									break;
								}
							}
						}
						if (found)
							break;
					}
					if (foundPotential && !found) {
						return false;
					}
				}
			}
		}
		return true;
	}

	// Add basic 'uses' constraints
	void addInitialGroupingConstraints(ResolverBundle[] bundles) {
		for (int i = 0; i < bundles.length; i++) {
			ResolverExport[] exports = bundles[i].getExportPackages();
			for (int j = 0; j < exports.length; j++) {
				String[] uses = (String[]) exports[j].getExportPackageDescription().getDirective(Constants.USES_DIRECTIVE);
				if (uses == null)
					continue;
				for (int k = 0; k < uses.length; k++) {
					ResolverExport exp = bundles[i].getExport(uses[k]);
					if (exp == null)
						continue;
					addConstraint(exports[j], exp);
					addTransitiveGroupingConstraints(exports[j], exp);
				}
			}
		}
	}

	// Add constraints from other exports (due to 'uses' being transitive)
	private void addTransitiveGroupingConstraints(ResolverExport export, ResolverExport constraint) {
		if (export == constraint)
			return;
		String[] uses = (String[]) constraint.getExportPackageDescription().getDirective(Constants.USES_DIRECTIVE);
		if (uses == null)
			return;
		for (int i = 0; i < uses.length; i++) {
			ResolverExport exp = export.getExporter().getExport(uses[i]);
			if (exp == null || exp == constraint)
				continue;
			// Check if the constraint has already been added so
			// we don't recurse infinitely
			ArrayList list = (ArrayList) constraints.get(export);
			if (list.contains(exp))
				continue;
			addConstraint(export, exp);
			addTransitiveGroupingConstraints(export, exp);
		}
	}

	// Add root constraints to re-exports
	void addReExportConstraints(ResolverBundle bundle) {
		ResolverExport[] exports = bundle.getExportPackages();
		for (int i = 0; i < exports.length; i++) {
			if (exports[i].getExportPackageDescription().isRoot())
				continue;
			ResolverExport root = exports[i].getRoot();
			if (root == null)
				continue;
			ArrayList list = (ArrayList) constraints.get(root);
			if (list == null)
				continue;
			addConstraints(exports[i], list);
		}
	}

	// Add constraints to exports that 'use' imports
	void addImportConstraints(ResolverBundle bundle) {
		ResolverExport[] exports = bundle.getExportPackages();
		for (int i = 0; i < exports.length; i++) {
			String[] uses = (String[]) exports[i].getExportPackageDescription().getDirective(Constants.USES_DIRECTIVE);
			if (uses == null)
				continue;
			for (int j = 0; j < uses.length; j++) {
				ResolverImport imp = bundle.getImport(uses[j]);
				if (imp == null)
					continue;
				ResolverExport root = imp.getRoot();
				if (root == null)
					continue;
				addConstraint(exports[i], root);
			}
		}
	}

	// Add constraints to re-rexports (via a require)
	void addRequireConstraints(ResolverExport[] exports, ResolverBundle bundle) {
		BundleConstraint[] requires = bundle.getRequires();
		for (int i = 0; i < exports.length; i++) {
			if (exports[i].getExportPackageDescription().isRoot())
				continue;
			for (int j = 0; j < requires.length; j++) {
				if (requires[j].getMatchingBundle() == null || exports[i].getExporter() == requires[j].getMatchingBundle())
					continue;
				ResolverExport[] requireExports = requires[j].getMatchingBundle().getExportPackages();
				for (int k = 0; k < requireExports.length; k++) {
					if (!exports[i].getName().equals(requireExports[k].getName()))
						continue;
					ArrayList list = (ArrayList) constraints.get(requireExports[k]);
					if (list == null)
						continue;
					for (int m = 0; m < list.size(); m++) {
						addConstraint(exports[i], (ResolverExport) list.get(m));
					}
				}
			}
		}
	}

	// Add constraints for reprovided exports
	void addReprovideConstraints(ResolverExport re) {
		BundleConstraint[] requires = re.getExporter().getRequires();
		for (int i = 0; i < requires.length; i++) {
			if (requires[i].getMatchingBundle() == null)
				return;
			ResolverExport[] requireExports = requires[i].getMatchingBundle().getExportPackages();
			for (int j = 0; j < requireExports.length; j++) {
				if (!re.getName().equals(requireExports[j].getName()))
					continue;
				ArrayList list = (ArrayList) constraints.get(requireExports[j]);
				if (list == null)
					continue;
				for (int k = 0; k < list.size(); k++) {
					addConstraint(re, (ResolverExport) list.get(k));
				}
			}
		}
	}
}
