/*
 * Copyright 2015 Martin Smock <martin.smock@bluewin.ch>
 *
 * 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 li.strolch.model.activity;

import li.strolch.exception.StrolchElementNotFoundException;
import li.strolch.exception.StrolchException;
import li.strolch.exception.StrolchModelException;
import li.strolch.exception.StrolchPolicyException;
import li.strolch.model.*;
import li.strolch.model.Locator.LocatorBuilder;
import li.strolch.model.parameter.Parameter;
import li.strolch.model.policy.PolicyDef;
import li.strolch.model.policy.PolicyDefs;
import li.strolch.model.visitor.StrolchElementVisitor;
import li.strolch.model.xml.StrolchXmlHelper;
import li.strolch.utils.dbc.DBC;

import java.text.MessageFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;
import static li.strolch.model.StrolchModelConstants.BAG_PARAMETERS;
import static li.strolch.model.StrolchModelConstants.BAG_RELATIONS;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.BAG_OBJECTIVES;
import static li.strolch.utils.collections.CollectionsHelper.singletonCollector;

/**
 * Parameterized object grouping a collection of {@link Activity} and {@link Action} objects defining the process to be
 * scheduled
 *
 * @author Martin Smock <martin.smock@bluewin.ch>
 */
public class Activity extends AbstractStrolchRootElement
		implements IActivityElement, StrolchRootElement, Comparable<Activity> {

	protected Locator locator;
	protected Version version;

	protected Activity parent;
	protected TimeOrdering timeOrdering;
	protected Map<String, IActivityElement> elements;
	protected PolicyDefs policyDefs;

	/**
	 * Empty constructor - for marshalling only!
	 */
	public Activity() {
		super();
	}

	/**
	 * Default constructor
	 *
	 * @param id   the id
	 * @param name the name
	 * @param type the type
	 */
	public Activity(String id, String name, String type, TimeOrdering timeOrdering) {
		super(id, name, type);
		this.timeOrdering = timeOrdering;
	}

	@Override
	public void setId(String id) {
		this.locator = null;
		super.setId(id);
	}

	@Override
	public String getObjectType() {
		return Tags.ACTIVITY;
	}

	public TimeOrdering getTimeOrdering() {
		return this.timeOrdering;
	}

	public void setTimeOrdering(TimeOrdering timeOrdering) {
		assertNotReadonly();
		this.timeOrdering = timeOrdering;
	}

	@Override
	public boolean hasVersion() {
		return this.version != null;
	}

	@Override
	public Version getVersion() {
		return this.version;
	}

	@Override
	public void setVersion(Version version) throws IllegalArgumentException, IllegalStateException {
		if (!isRootElement())
			throw new IllegalStateException("Can't set the version on non root of " + getLocator());

		if (version != null && !getLocator().equals(version.getLocator())) {
			String msg = "Illegal version as locator is not same: Element: {0} Version: {1}";
			throw new IllegalArgumentException(MessageFormat.format(msg, getLocator(), version));
		}

		this.version = version;
	}

	private void initElements() {
		if (this.elements == null) {
			// use a LinkedHashMap since we will iterate elements in the order
			// added and lookup elements by ID
			this.elements = new LinkedHashMap<>();
		}
	}

	@Override
	public boolean isAction() {
		return false;
	}

	@Override
	public boolean isActivity() {
		return true;
	}

	/**
	 * Returns true if this {@link Activity} contains any children i.e. any of {@link Action} or {@link Activity}
	 *
	 * @return true if this {@link Activity} contains any children i.e. any of {@link Action} or {@link Activity}
	 */
	public boolean hasElements() {
		return this.elements != null && !this.elements.isEmpty();
	}

	/**
	 * Returns true if this {@link Activity} contains a child with the given id. The element instance type is ignored,
	 * i.e. {@link Action} or {@link Activity}
	 *
	 * @param id the id of the element to check for
	 *
	 * @return true if this {@link Activity} contains a child with the given id. The element instance type is ignored,
	 * i.e. {@link Action} or {@link Activity}
	 */
	public boolean hasElement(String id) {
		return this.elements != null && this.elements.containsKey(id);
	}

	/**
	 * add an activity element to the {@code LinkedHashMap} of {@code IActivityElements}
	 *
	 * @param activityElement the element to add
	 */
	public void addElement(IActivityElement activityElement) {
		assertCanAdd(activityElement);
		activityElement.setParent(this);
		this.elements.put(activityElement.getId(), activityElement);
	}

	/**
	 * add an activity element to the {@code LinkedHashMap} of {@code IActivityElements} before the given element
	 *
	 * @param elementId    the id of the element before which to add the other element
	 * @param elementToAdd the element to add
	 */
	public void addElementBefore(String elementId, IActivityElement elementToAdd) {
		IActivityElement element = getElement(elementId);
		addElementBefore(element, elementToAdd);
	}

	/**
	 * add an activity element to the {@code LinkedHashMap} of {@code IActivityElements} before the given element
	 *
	 * @param element      the element before which to add the other element
	 * @param elementToAdd the element to add
	 */
	public void addElementBefore(IActivityElement element, IActivityElement elementToAdd) {
		assertCanAdd(elementToAdd);

		Iterator<Entry<String, IActivityElement>> iterator = this.elements.entrySet().iterator();
		LinkedHashMap<String, IActivityElement> elements = new LinkedHashMap<>();
		boolean added = false;
		while (iterator.hasNext()) {
			Entry<String, IActivityElement> next = iterator.next();

			if (!added && next.getValue().equals(element)) {
				elements.put(elementToAdd.getId(), elementToAdd);
				elements.put(next.getKey(), next.getValue());
				added = true;
			}

			elements.put(next.getKey(), next.getValue());
		}
		if (!added)
			throw new IllegalStateException("Element " + element.getId() + " was not found, couldn't add before!");

		elementToAdd.setParent(this);
		this.elements = elements;
	}

	/**
	 * add an activity element to the {@code LinkedHashMap} of {@code IActivityElements} after the given element
	 *
	 * @param elementId    the id of the element after which to add the other element
	 * @param elementToAdd the element to add
	 */
	public void addElementAfter(String elementId, IActivityElement elementToAdd) {
		IActivityElement element = getElement(elementId);
		addElementAfter(element, elementToAdd);
	}

	/**
	 * add an activity element to the {@code LinkedHashMap} of {@code IActivityElements} after the given element
	 *
	 * @param element      the element after which to add the other element
	 * @param elementToAdd the element to add
	 */
	public void addElementAfter(IActivityElement element, IActivityElement elementToAdd) {
		assertCanAdd(elementToAdd);

		Iterator<Entry<String, IActivityElement>> iterator = this.elements.entrySet().iterator();
		LinkedHashMap<String, IActivityElement> elements = new LinkedHashMap<>();
		boolean added = false;
		while (iterator.hasNext()) {
			Entry<String, IActivityElement> next = iterator.next();
			elements.put(next.getKey(), next.getValue());

			if (!added && next.getValue().equals(element)) {
				elements.put(elementToAdd.getId(), elementToAdd);
				added = true;
			}
		}
		if (!added)
			throw new IllegalStateException("Element " + element.getId() + " was not found, couldn't add after!");

		elementToAdd.setParent(this);
		this.elements = elements;
	}

	private void assertCanAdd(IActivityElement elementToAdd) {
		assertNotReadonly();
		DBC.PRE.assertNotEquals("Can't add element to itself!", this, elementToAdd);
		DBC.PRE.assertNull("Parent can't already be set!", elementToAdd.getParent());

		// TODO make sure we can't create a circular dependency

		initElements();
		String id = elementToAdd.getId();
		if (id == null)
			throw new StrolchException("Cannot add IActivityElement without id.");
		if (this.elements.containsKey(id))
			throw new StrolchException(
					"Activity " + getLocator() + " already contains an activity element with id = " + id);
	}

	/**
	 * Removes the element with the given id and returns it, if it exists
	 *
	 * @param id the id of the element to remove
	 *
	 * @return the removed element, or null if it does not exist
	 */
	@SuppressWarnings("unchecked")
	public <T extends IActivityElement> T remove(String id) {
		assertNotReadonly();
		IActivityElement element = this.elements.remove(id);
		if (element != null)
			element.setParent(null);
		return (T) element;
	}

	/**
	 * Returns the {@link Action} with the given ID which is a direct child of this {@link Activity}
	 *
	 * @param id the id of the {@link Action} to return
	 *
	 * @return the {@link Action} with the given ID
	 */
	public Action getAction(String id) {
		return getElement(id);
	}

	/**
	 * Returns the {@link Activity} with the given ID which is a direct child of this {@link Activity}
	 *
	 * @param id the id of the {@link Activity} to return
	 *
	 * @return the {@link Activity} with the given ID
	 */
	public Activity getActivity(String id) {
		return getElement(id);
	}

	/**
	 * get {@code IActivityElement} by id
	 *
	 * @param id the id of the {@code IActivityElement}
	 *
	 * @return IActivityElement
	 */
	public <T extends IActivityElement> T getElement(String id) {
		if (this.elements == null)
			throw new IllegalArgumentException("Element " + id + " does not exist on " + getLocator());
		@SuppressWarnings("unchecked") T t = (T) this.elements.get(id);
		if (t == null)
			throw new IllegalArgumentException("Element " + id + " does not exist on " + getLocator());
		return t;
	}

	public Optional<IActivityElement> getPreviousElement(IActivityElement element) {
		if (!hasElements())
			return Optional.empty();

		IActivityElement previous = null;

		Iterator<Entry<String, IActivityElement>> iter = elementIterator();
		while (iter.hasNext()) {
			IActivityElement elem = iter.next().getValue();
			if (elem == element)
				return Optional.ofNullable(previous);
			else
				previous = elem;
		}

		return Optional.empty();
	}

	public Optional<IActivityElement> getPreviousElementByType(IActivityElement element, String type) {
		if (!hasElements())
			return Optional.empty();

		List<IActivityElement> reversed = new ArrayList<>(this.elements.values());
		Collections.reverse(reversed);

		boolean foundElem = false;
		Iterator<IActivityElement> iter = reversed.iterator();
		IActivityElement elem;
		while (iter.hasNext()) {
			elem = iter.next();

			if (foundElem && elem.getType().equals(type))
				return Optional.of(elem);
			else if (elem == element) {
				foundElem = true;
			}
		}

		return Optional.empty();
	}

	public Optional<IActivityElement> getNextElement(IActivityElement element) {
		if (!hasElements())
			return Optional.empty();

		Iterator<Entry<String, IActivityElement>> iter = elementIterator();
		IActivityElement previous = iter.next().getValue();

		while (iter.hasNext()) {
			IActivityElement elem = iter.next().getValue();
			if (previous == element)
				return Optional.ofNullable(elem);
			else
				previous = elem;
		}

		return Optional.empty();
	}

	public Optional<IActivityElement> getNextElementByType(IActivityElement element, String type) {
		if (!hasElements())
			return Optional.empty();

		Iterator<Entry<String, IActivityElement>> iter = elementIterator();

		boolean foundElem = false;
		IActivityElement elem;
		while (iter.hasNext()) {
			elem = iter.next().getValue();

			if (foundElem && elem.getType().equals(type))
				return Optional.of(elem);
			else if (elem == element) {
				foundElem = true;
			}
		}

		return Optional.empty();
	}

	public List<IActivityElement> getElementsByType(String type) {
		List<IActivityElement> elements = new ArrayList<>();
		Iterator<Entry<String, IActivityElement>> iter = elementIterator();
		while (iter.hasNext()) {
			IActivityElement element = iter.next().getValue();
			if (element.getType().equals(type))
				elements.add(element);
		}
		return elements;
	}

	/**
	 * @return get the {@code LinkedHashMap} of {@code IActivityElements}
	 */
	public Map<String, IActivityElement> getElements() {
		if (this.elements == null)
			return Collections.emptyMap();
		return this.elements;
	}

	/**
	 * @return the iterator for entries, which include the id as key and the {@link IActivityElement} as value
	 */
	public Iterator<Entry<String, IActivityElement>> elementIterator() {
		if (this.elements == null)
			return Collections.emptyIterator();
		return this.elements.entrySet().iterator();
	}

	/**
	 * @return the stream for entries, which include the id as key and the {@link IActivityElement} as value
	 */
	public Stream<Entry<String, IActivityElement>> elementStream() {
		if (this.elements == null)
			return Stream.empty();
		return this.elements.entrySet().stream();
	}

	public Stream<IActivityElement> streamElements() {
		if (this.elements == null)
			return Stream.empty();
		return this.elements.values().stream();
	}

	public Stream<IActivityElement> streamElementsByType(String type) {
		return streamElements().filter(e -> e.getType().equals(type));
	}

	public Stream<IActivityElement> streamElements(Predicate<IActivityElement> predicate) {
		return streamElements().filter(predicate);
	}

	public Stream<Activity> streamActivities() {
		return streamElements() //
				.filter(IActivityElement::isActivity) //
				.map(IActivityElement::asActivity);
	}

	public Stream<Activity> streamActivities(Predicate<Activity> predicate) {
		return streamActivities().filter(predicate);
	}

	public Stream<Activity> streamActivitiesByType(String type) {
		return streamActivities(a -> a.getType().equals(type));
	}

	public Stream<Action> streamActions() {
		return streamElements() //
				.filter(IActivityElement::isAction) //
				.map(IActivityElement::asAction);
	}

	public Stream<Action> streamActions(Predicate<Action> predicate) {
		return streamActions().filter(predicate);
	}

	public Stream<Action> streamActionsByType(String type) {
		return streamActions(a -> a.getType().equals(type));
	}

	public Stream<Action> streamActionsDeep() {
		return streamElements().flatMap(e -> {
			if (e.isAction())
				return Stream.of(e);
			return e.asActivity().streamActionsDeep();
		}).map(IActivityElement::asAction);
	}

	public Stream<Action> streamActionsDeep(Predicate<Action> predicate) {
		return streamActionsDeep().filter(predicate);
	}

	public <T extends IActivityElement> T findElement(Predicate<IActivityElement> predicate,
			Supplier<String> msgSupplier) {
		@SuppressWarnings("unchecked") T t = (T) streamElements()
				.filter(predicate)
				.collect(singletonCollector(msgSupplier));
		return t;
	}

	public List<IActivityElement> findElementsByType(String type) {
		return findElements(e -> e.getType().equals(type));
	}

	public List<IActivityElement> findElements(Predicate<IActivityElement> predicate) {
		return streamElements().filter(predicate).collect(toList());
	}

	public List<Action> findActions(Predicate<Action> predicate) {
		return streamActions(predicate).collect(toList());
	}

	public List<Action> findActionsDeep(Predicate<Action> predicate) {
		return streamActionsDeep().filter(predicate).collect(toList());
	}

	/**
	 * Returns all the actions as a flat list
	 *
	 * @return the list of actions
	 */
	public List<Action> getActionsAsFlatList() {
		return streamActionsDeep().toList();
	}

	/**
	 * Returns all the actions in the entire hierarchy with the given state
	 *
	 * @param state the state of the action to return
	 *
	 * @return the list of actions with the given state
	 */
	public List<Action> getActionsWithState(State state) {
		return streamActionsDeep(action -> action.getState() == state).toList();
	}

	/**
	 * Returns all the actions in the entire hierarchy with the given type
	 *
	 * @param type the type of action to return
	 *
	 * @return the list of actions with the given type
	 */
	public List<Action> getActionsByType(String type) {
		return streamActionsDeep(action -> action.getType().equals(type)).toList();
	}

	public <T extends IActivityElement> T getElementByLocator(Locator locator) {
		DBC.PRE.assertEquals("Locator is not for this activity!", getLocator(), locator.trim(3));
		DBC.PRE.assertTrue("Locator must have at least 5 parts", locator.getSize() >= 4);

		IActivityElement element = this;
		for (int i = 3; i < locator.getSize(); i++) {
			String next = locator.get(i);

			if (!(element instanceof Activity)) {
				String msg = "Invalid locator {0} with part {1} as not an Activity but deeper element specified";
				throw new StrolchModelException(MessageFormat.format(msg, locator, next));
			}

			element = ((Activity) element).getElement(next);
			if (element == null)
				throw new StrolchElementNotFoundException(locator + " does not exist!");
		}

		@SuppressWarnings("unchecked") T t = (T) element;
		return t;
	}

	@Override
	public Activity asActivity() {
		return this;
	}

	@Override
	public Long getStart() {
		long start = Long.MAX_VALUE;
		if (this.elements == null)
			return start;
		Iterator<Entry<String, IActivityElement>> elementIterator = elementIterator();
		while (elementIterator.hasNext()) {
			IActivityElement element = elementIterator.next().getValue();
			start = Math.min(start, element.getStart());
		}
		return start;
	}

	@Override
	public Long getEnd() {
		long end = 0L;
		if (this.elements == null)
			return end;
		Iterator<Entry<String, IActivityElement>> elementIterator = elementIterator();
		while (elementIterator.hasNext()) {
			IActivityElement element = elementIterator.next().getValue();
			end = Math.max(end, element.getEnd());
		}
		return end;
	}

	@Override
	public State getState() {
		if (this.elements == null || this.elements.isEmpty())
			return State.CREATED;
		return State.getState(this);
	}

	@Override
	public PolicyDefs getPolicyDefs() {
		if (this.policyDefs == null)
			throw new StrolchPolicyException(getLocator() + " has no Policies defined!");
		return this.policyDefs;
	}

	@Override
	public PolicyDef getPolicyDef(Class<?> clazz) {
		return getPolicyDefs().getPolicyDef(clazz);
	}

	@Override
	public PolicyDef getPolicyDef(String type) {
		return getPolicyDefs().getPolicyDef(type);
	}

	@Override
	public PolicyDef getPolicyDef(Class<?> clazz, PolicyDef defaultDef) {
		if (!hasPolicyDefs())
			return defaultDef;
		return getPolicyDefs().getPolicyDef(clazz, defaultDef);
	}

	@Override
	public PolicyDef getPolicyDef(String type, PolicyDef defaultDef) {
		if (!hasPolicyDefs())
			return defaultDef;
		return getPolicyDefs().getPolicyDef(type, defaultDef);
	}

	@Override
	public boolean hasPolicyDefs() {
		return this.policyDefs != null;
	}

	@Override
	public boolean hasPolicyDef(String type) {
		return this.policyDefs != null && this.policyDefs.hasPolicyDef(type);
	}

	@Override
	public boolean hasPolicyDef(Class<?> clazz) {
		return this.policyDefs != null && this.policyDefs.hasPolicyDef(clazz);
	}

	@Override
	public void setPolicyDefs(PolicyDefs policyDefs) {
		assertNotReadonly();
		this.policyDefs = policyDefs;
		this.policyDefs.setParent(this);
	}

	@Override
	public void addOrUpdate(PolicyDef policyDef) {
		assertNotReadonly();
		DBC.PRE.assertNotNull("policyDef", policyDef);
		if (this.policyDefs == null) {
			this.policyDefs = new PolicyDefs();
			this.policyDefs.setParent(this);
		}
		this.policyDefs.addOrUpdate(policyDef);
	}

	@Override
	public Locator getLocator() {
		if (this.locator == null) {
			LocatorBuilder lb = new LocatorBuilder();
			fillLocator(lb);
			this.locator = lb.build();
		}
		return this.locator;
	}

	@Override
	protected void fillLocator(LocatorBuilder locatorBuilder) {
		if (this.parent != null)
			this.parent.fillLocator(locatorBuilder);
		else
			locatorBuilder.append(Tags.ACTIVITY).append(getType());
		locatorBuilder.append(getId());
	}

	@Override
	public Activity getParent() {
		return this.parent;
	}

	@Override
	public Activity getRootElement() {
		return (this.parent == null) ? this : this.parent.getRootElement();
	}

	@Override
	public boolean isRootElement() {
		return (this.parent == null);
	}

	@Override
	public Activity getClone() {
		return getClone(false);
	}

	@Override
	public Activity getClone(boolean withVersion) {
		Activity clone = new Activity();
		super.fillClone(clone);
		clone.timeOrdering = this.timeOrdering;
		clone.locator = this.locator;

		if (this.elements != null) {
			for (IActivityElement element : this.elements.values()) {
				clone.addElement(element.getClone());
			}
		}

		if (this.policyDefs != null)
			clone.policyDefs = this.policyDefs.getClone();
		if (withVersion)
			clone.version = this.version;
		return clone;
	}

	@Override
	public void setReadOnly() {
		if (this.policyDefs != null)
			this.policyDefs.setReadOnly();
		elementStream().forEach(e -> e.getValue().setReadOnly());
		super.setReadOnly();
	}

	@Override
	public Activity ensureModifiable() {
		if (!this.isRootElement())
			throw new IllegalStateException("Only call this method on the root element!");
		if (isReadOnly())
			return getClone(true);
		return this;
	}

	@Override
	public Activity ensureReadOnly() {
		if (isReadOnly())
			return this;
		return getClone(true).readOnly();
	}

	@Override
	public Activity readOnly() {
		setReadOnly();
		return this;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null || getClass() != obj.getClass())
			return false;
		Activity a = (Activity) obj;
		return this.parent == a.parent && this.type.equals(a.type) && this.id.equals(a.id);
	}

	@Override
	public int hashCode() {
		return Objects.hash(type, id);
	}

	@Override
	public String toString() {
		final StringBuilder builder = new StringBuilder();
		builder.append(getLocator());
		builder.append(", state=");
		builder.append(getState());
		builder.append(", start=");
		builder.append(getStart());
		builder.append(", end=");
		builder.append(getEnd());
		if (isRootElement() && this.version != null) {
			builder.append(", version=");
			builder.append(this.version.getVersion());
		}
		return builder.toString();
	}

	@Override
	public int compareTo(Activity o) {
		return getId().compareTo(o.getId());
	}

	@Override
	public <T> T accept(StrolchElementVisitor<T> visitor) {
		return visitor.visitActivity(this);
	}

	@Override
	public <U, T extends Parameter<U>> T findObjectivesParam(String paramKey) {
		return findParameter(BAG_OBJECTIVES, paramKey);
	}

	@Override
	public <U, T extends Parameter<U>> T findObjectivesParam(String paramKey, boolean assertExists) {
		return findParameter(BAG_OBJECTIVES, paramKey, assertExists);
	}

	@Override
	public <U, T extends Parameter<U>> T findRelationParam(String paramKey) {
		return findParameter(BAG_RELATIONS, paramKey);
	}

	@Override
	public <U, T extends Parameter<U>> T findRelationParam(String paramKey, boolean assertExists) {
		return findParameter(BAG_RELATIONS, paramKey, assertExists);
	}

	@Override
	public <U, T extends Parameter<U>> T findParameter(String paramKey) {
		return findParameter(BAG_PARAMETERS, paramKey);
	}

	@Override
	public <U, T extends Parameter<U>> T findParameter(String paramKey, boolean assertExists) {
		return findParameter(BAG_PARAMETERS, paramKey, assertExists);
	}

	public <U, T extends Parameter<U>> T findParameter(String bagKey, String paramKey) {

		T parameter = getParameter(bagKey, paramKey);
		if (parameter != null)
			return parameter;

		if (this.parent != null)
			return this.parent.findParameter(bagKey, paramKey);

		return null;
	}

	@Override
	public <U, T extends Parameter<U>> T findParameter(String bagKey, String paramKey, boolean assertExists)
			throws StrolchModelException {

		T parameter = getParameter(bagKey, paramKey);
		if (parameter != null)
			return parameter;

		parameter = this.parent == null ? null : this.parent.findParameter(bagKey, paramKey);
		if (assertExists && parameter == null) {
			String msg = "The Parameter {0} does not exist";
			throw new StrolchModelException(MessageFormat.format(msg, getLocator().append(Tags.BAG, bagKey, paramKey)));
		}

		return parameter;
	}

	@Override
	public PolicyDef findPolicy(Class<?> clazz, PolicyDef defaultDef) throws StrolchModelException {
		return findPolicy(clazz.getSimpleName(), defaultDef);
	}

	@Override
	public PolicyDef findPolicy(String className, PolicyDef defaultDef) throws StrolchModelException {
		if (hasPolicyDef(className))
			return getPolicyDef(className);

		if (this.parent == null) {
			if (defaultDef != null)
				return defaultDef;

			String msg = "The PolicyDef {0} does not exist";
			throw new StrolchModelException(MessageFormat.format(msg, className));
		}

		return this.parent.findPolicy(className, defaultDef);
	}

	@Override
	public void setParent(Activity activity) {
		assertNotReadonly();
		this.parent = activity;
		this.locator = null;
	}

	/**
	 * Creates a {@link Locator} for activities of the given type and id
	 *
	 * @param type the type of activity
	 * @param id   the id of the activity
	 *
	 * @return the locator
	 */
	public static Locator locatorFor(String type, String id) {
		return Locator.valueOf(Tags.ACTIVITY, type, id);
	}

	/**
	 * Parses the given XML and returns the activity with the given ID
	 *
	 * @param xml the xml to parse
	 * @param id  the id of the activity to return from the parsed elements
	 *
	 * @return the activity, or null if it does not exist
	 */
	public static Resource parse(String xml, String id) {
		return StrolchXmlHelper.parseAndReturnResource(xml, id);
	}
}
