/*******************************************************************************
 * Copyright (c) 2011 VMware Inc.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    SpringSource, a division of VMware - initial API and implementation and/or initial documentation
 *******************************************************************************/

package org.eclipse.equinox.internal.region.hook;

import java.util.*;
import org.eclipse.equinox.region.*;

/**
 * {@link RegionDigraphVisitorBase} is an abstract base class for {@link RegionDigraphVisitor} implementations in the
 * framework hooks.
 * <p />
 * 
 * <strong>Concurrent Semantics</strong><br />
 * This class is thread safe.
 */
abstract class RegionDigraphVisitorBase<C> implements RegionDigraphVisitor {

	private final Collection<C> allCandidates;

	private final Stack<Set<C>> allowedStack = new Stack<>();
	private final Stack<Collection<C>> filteredStack = new Stack<>();

	private final Object monitor;

	private Set<C> allowed;

	protected RegionDigraphVisitorBase(Collection<C> candidates) {
		this.allCandidates = candidates;
		this.allowed = new HashSet<>();
		this.monitor = new Object();
	}

	Collection<C> getAllowed() {
		synchronized (this.monitor) {
			return this.allowed;
		}
	}

	private void allow(C candidate) {
		synchronized (this.monitor) {
			this.allowed.add(candidate);
		}
	}

	private void allow(Collection<C> candidates) {
		synchronized (this.monitor) {
			this.allowed.addAll(candidates);
		}
	}

	private void pushAllowed() {
		synchronized (this.monitor) {
			this.allowedStack.push(this.allowed);
			this.allowed = new HashSet<>();
		}
	}

	private Collection<C> popAllowed() {
		synchronized (this.monitor) {
			Collection<C> a = this.allowed;
			this.allowed = this.allowedStack.pop();
			return a;
		}
	}

	private void pushFiltered(Collection<C> filtered) {
		synchronized (this.monitor) {
			this.filteredStack.push(filtered);
		}
	}

	private Collection<C> popFiltered() {
		synchronized (this.monitor) {
			return this.filteredStack.isEmpty() ? allCandidates : this.filteredStack.pop();
		}
	}

	private Collection<C> peekFiltered() {
		synchronized (this.monitor) {
			return this.filteredStack.isEmpty() ? allCandidates : this.filteredStack.peek();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean visit(Region region) {
		Collection<C> candidates = peekFiltered();
		for (C candidate : candidates) {
			if (contains(region, candidate)) {
				allow(candidate);
			}
		}
		if (allowed.containsAll(candidates)) {
			// there is no need to traverse edges of this region,
			// it contains all the remaining filtered candidates
			return false;
		}
		return true;
	}

	/**
	 * Determines whether the given region contains the given candidate.
	 * 
	 * @param region the {@link Region}
	 * @param candidate the candidate
	 * @return <code>true</code> if and only if the given region contains the given candidate
	 */
	protected abstract boolean contains(Region region, C candidate);

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean preEdgeTraverse(RegionFilter regionFilter) {
		// Find the candidates filtered by the previous edge
		Collection<C> candidates = new ArrayList<>(peekFiltered());
		// remove any candidates contained in the current region
		candidates.removeAll(allowed);
		// apply the filter across remaining candidates
		filter(candidates, regionFilter);
		if (candidates.isEmpty())
			return false; // this filter does not apply; avoid traversing this edge
		// push the filtered candidates for the next region
		pushFiltered(candidates);
		// push the allowed
		pushAllowed();
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void postEdgeTraverse(RegionFilter regionFilter) {
		popFiltered();
		Collection<C> candidates = popAllowed();
		allow(candidates);
	}

	private void filter(Collection<C> candidates, RegionFilter filter) {
		Iterator<C> i = candidates.iterator();
		while (i.hasNext()) {
			C candidate = i.next();
			if (!isAllowed(candidate, filter)) {
				i.remove();
			}
		}
	}

	/**
	 * Determines whether the given candidate is allowed by the given {@link RegionFilter}.
	 * 
	 * @param candidate the candidate
	 * @param filter the filter
	 * @return <code>true</code> if and only if the given candidate is allowed by the given filter
	 */
	protected abstract boolean isAllowed(C candidate, RegionFilter filter);
}