/*
 * 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.HashSet;
import java.util.Map;
import java.util.Map.Entry;

import org.osgi.service.subsystem.Subsystem;
import org.osgi.service.subsystem.SubsystemException;

public class SubsystemGraph {
	private static class SubsystemWrapper {
		private final Subsystem s;
		
		public SubsystemWrapper(Subsystem subsystem) {
			s = subsystem;
		}
		
		@Override
		public boolean equals(Object o) {
			if (o == this)
				return true;
			if (!(o instanceof SubsystemWrapper))
				return false;
			SubsystemWrapper that = (SubsystemWrapper)o;
			return s.getLocation().equals(that.s.getLocation());
		}
		
		public Subsystem getSubsystem() {
			return s;
		}
		
		@Override
		public int hashCode() {
			int result = 17;
			result = 31 * result + s.getLocation().hashCode();
			return result;
		}
		
		@Override
		public String toString() {
			return new StringBuilder("location=").append(s.getLocation())
					.append(", symbolicName=").append(s.getSymbolicName())
					.append(", version=").append(s.getVersion())
					.append(", type=").append(s.getType()).toString();
		}
	}
	private final Map<SubsystemWrapper, Collection<SubsystemWrapper>> adjacencyList = new HashMap<SubsystemWrapper, Collection<SubsystemWrapper>>();
	
	public SubsystemGraph(BasicSubsystem root) {
		adjacencyList.put(new SubsystemWrapper(root), new HashSet<SubsystemWrapper>());
	}
	
	public synchronized void add(BasicSubsystem parent, BasicSubsystem child) {
		SubsystemWrapper parentWrap = new SubsystemWrapper(parent);
		SubsystemWrapper childWrap = new SubsystemWrapper(child);
		if (containsAncestor(childWrap, parentWrap))
			throw new SubsystemException("Cycle detected between '" + parentWrap + "' and '" + childWrap + "'");
		Collection<SubsystemWrapper> subsystems = adjacencyList.get(childWrap);
		if (subsystems == null) {
			subsystems = new HashSet<SubsystemWrapper>();
			adjacencyList.put(childWrap, subsystems);
		}
		subsystems = adjacencyList.get(parentWrap);
		if (subsystems == null) {
			subsystems = new HashSet<SubsystemWrapper>();
			adjacencyList.put(parentWrap, subsystems);
		}
		subsystems.add(childWrap);
	}
	
	public synchronized Collection<Subsystem> getChildren(BasicSubsystem parent) {
		Collection<SubsystemWrapper> children = adjacencyList.get(new SubsystemWrapper(parent));
		if (children == null || children.isEmpty())
			return Collections.emptySet();
		Collection<Subsystem> result = new ArrayList<Subsystem>(children.size());
		for (SubsystemWrapper child : children)
			result.add(child.getSubsystem());
 		return Collections.unmodifiableCollection(result);
	}
	
	public synchronized Collection<Subsystem> getParents(BasicSubsystem child) {
		Collection<SubsystemWrapper> parents = getParents(new SubsystemWrapper(child));
		Collection<Subsystem> result = new ArrayList<Subsystem>(parents.size());
		for (SubsystemWrapper parent : parents) {
			result.add(parent.getSubsystem());
		}
		return Collections.unmodifiableCollection(result);
	}
	
	public synchronized void remove(BasicSubsystem child) {
		SubsystemWrapper subsystemWrap = new SubsystemWrapper(child);
		Collection<SubsystemWrapper> parents = getParents(subsystemWrap);
		for (SubsystemWrapper parent : parents)
			adjacencyList.get(parent).remove(subsystemWrap);
		adjacencyList.remove(subsystemWrap);
	}
	
	public synchronized void remove(BasicSubsystem parent, BasicSubsystem child) {
		SubsystemWrapper parentWrap = new SubsystemWrapper(parent);
		SubsystemWrapper childWrap = new SubsystemWrapper(child);
		adjacencyList.get(parentWrap).remove(childWrap);
	}
	
	private boolean containsAncestor(SubsystemWrapper subsystem, SubsystemWrapper ancestor) {
		Collection<SubsystemWrapper> subsystems = adjacencyList.get(subsystem);
		if (subsystems == null)
			return false;
		if (subsystems.contains(ancestor))
			return true;
		for (SubsystemWrapper s : subsystems) {
			return containsAncestor(s, ancestor);
		}
		return false;
	}
	
	private Collection<SubsystemWrapper> getParents(SubsystemWrapper child) {
		ArrayList<SubsystemWrapper> result = new ArrayList<SubsystemWrapper>();
		for (Entry<SubsystemWrapper, Collection<SubsystemWrapper>> entry : adjacencyList.entrySet())
			if (entry.getValue().contains(child))
				result.add(entry.getKey());
		result.trimToSize();
		return result;
	}
}
