package models.dataFlowModel;

import java.util.AbstractMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.InvalidMessage;
import models.algebra.Parameter;
import models.algebra.ParameterizedIdentifierIsFutureWork;
import models.algebra.Position;
import models.algebra.Term;
import models.algebra.UnificationFailed;
import models.algebra.ValueUndefined;
import models.algebra.Variable;
import models.dataConstraintModel.*;

public class DataTransferChannel extends Channel {
	protected Set<ChannelMember> inputChannelMembers = null;
	protected Set<ChannelMember> outputChannelMembers = null;
	protected Set<ChannelMember> referenceChannelMembers = null;
	
	public DataTransferChannel(String channelName) {
		super(channelName);
		inputChannelMembers = new HashSet<>();
		outputChannelMembers = new HashSet<>();
		referenceChannelMembers = new HashSet<>();
	}
	
	public DataTransferChannel(String channelName, Variable variable) {
		super(channelName, variable);
		inputChannelMembers = new HashSet<>();
		outputChannelMembers = new HashSet<>();
		referenceChannelMembers = new HashSet<>();
	}
	
	public DataTransferChannel(String channelName, List<Variable> variables) {
		super(channelName, variables);
		inputChannelMembers = new HashSet<>();
		outputChannelMembers = new HashSet<>();
		referenceChannelMembers = new HashSet<>();
	}

	public Set<ChannelMember> getInputChannelMembers() {
		return inputChannelMembers;
	}
	
	public void setInputChannelMembers(Set<ChannelMember> inputChannelMembers) {
		this.inputChannelMembers = inputChannelMembers;
	}
	
	private void addInputChannelMember(ChannelMember inputChannelMember) {
		inputChannelMembers.add(inputChannelMember);
	}
	
	public Set<ChannelMember> getOutputChannelMembers() {
		return outputChannelMembers;
	}
	
	public void setOutputChannelMembers(Set<ChannelMember> outputChannelMembers) {
		this.outputChannelMembers = outputChannelMembers;
	}
	
	private void addOutputChannelMember(ChannelMember outputChannelMember) {
		outputChannelMembers.add(outputChannelMember);
	}
	
	public Set<ChannelMember> getReferenceChannelMembers() {
		return referenceChannelMembers;
	}
	
	public void setReferenceChannelMembers(Set<ChannelMember> referenceChannelMembers) {
		this.referenceChannelMembers = referenceChannelMembers;
	}
	
	private void addReferenceChannelMember(ChannelMember referenceChannelMember) {
		referenceChannelMembers.add(referenceChannelMember);
	}
		
	public void addChannelMemberAsInput(ChannelMember inputChannelMember) {
		addChannelMember(inputChannelMember);
		addInputChannelMember(inputChannelMember);
	}
	
	public void addChannelMemberAsOutput(ChannelMember outputChannelMember) {
		addChannelMember(outputChannelMember);
		addOutputChannelMember(outputChannelMember);
	}
	
	public void addChannelMemberAsReference(ChannelMember referenceChannelMember) {
		addChannelMember(referenceChannelMember);
		addReferenceChannelMember(referenceChannelMember);
	}
	
	public void removeChannelMember(ResourcePath id) {
		for (ChannelMember cm: inputChannelMembers) {
			if (cm.getResource() == id) {
				inputChannelMembers.remove(cm);
				super.removeChannelMember(id);
				return;
			}
		}
		for (ChannelMember cm: outputChannelMembers) {
			if (cm.getResource() == id) {
				outputChannelMembers.remove(cm);
				super.removeChannelMember(id);
				return;
			}
		}
		for (ChannelMember cm: referenceChannelMembers) {
			if (cm.getResource() == id) {
				referenceChannelMembers.remove(cm);
				super.removeChannelMember(id);
				return;
			}
		}
	}
	
	public Set<ResourcePath> getInputResources() {
		Set<ResourcePath> inputResources = new HashSet<>();
		for (ChannelMember member: inputChannelMembers) {
			inputResources.add(member.getResource());
		}
		return inputResources;
	}
	
	public Set<ResourcePath> getOutputResources() {
		Set<ResourcePath> outputResources = new HashSet<>();
		for (ChannelMember member: outputChannelMembers) {
			outputResources.add(member.getResource());
		}
		return outputResources;
	}
	
	public Set<ResourcePath> getReferenceResources() {
		Set<ResourcePath> referenceResources = new HashSet<>();
		for (ChannelMember member: referenceChannelMembers) {
			referenceResources.add(member.getResource());
		}
		return referenceResources;
	}
	
	/**
	 * Derive the update expression of the state of the target channel member.
	 * @param targetMember a channel member whose state is to be updated
	 * @return the derived update expression
	 * @throws ParameterizedIdentifierIsFutureWork
	 * @throws ResolvingMultipleDefinitionIsFutureWork
	 * @throws InvalidMessage
	 * @throws UnificationFailed
	 * @throws ValueUndefined
	 */
	public Expression deriveUpdateExpressionOf(ChannelMember targetMember) throws ParameterizedIdentifierIsFutureWork, ResolvingMultipleDefinitionIsFutureWork, InvalidMessage, UnificationFailed, ValueUndefined {
		IResourceStateAccessor defaultStateAccessor = new IResourceStateAccessor() {
			HashMap<String, Parameter> curStateParams = new HashMap<>();
			HashMap<String, Parameter> nextStateParams = new HashMap<>();

			@Override
			public Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from) {
				String resource = target.getResource().getLeafResourceName();
				Parameter curStateParam = curStateParams.get(resource);
				if (curStateParam == null) {
					curStateParam = new Parameter("cur" + resource);
					curStateParams.put(resource, curStateParam);
				}
				return curStateParam;
			}

			@Override
			public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
				String resource = target.getResource().getLeafResourceName();
				Parameter nextStateParam = nextStateParams.get(resource);
				if (nextStateParam == null) {
					nextStateParam = new Parameter("next" + target);
					nextStateParams.put(resource, nextStateParam);
				}
				return nextStateParam;
			}

			@Override
			public Expression getDirectStateAccessorFor(ResourcePath target, ResourcePath from) {
				String resource = target.getLeafResourceName();
				Parameter curStateParam = curStateParams.get(resource);
				if (curStateParam == null) {
					curStateParam = new Parameter("cur" + resource);
					curStateParams.put(resource, curStateParam);
				}
				return curStateParam;
			}
		};
		return deriveUpdateExpressionOf(targetMember, defaultStateAccessor).getKey();
	}
	
	/**
	 * Derive the state update calculation of the target channel member with a given resource push/pull state accessor.
	 * @param targetMember a channel member whose state is to be updated
	 * @param stateAccessor a push/pull resource state accessor
	 * @return the derived update calculation and the unified message
	 * @throws ParameterizedIdentifierIsFutureWork
	 * @throws ResolvingMultipleDefinitionIsFutureWork
	 * @throws InvalidMessage
	 * @throws UnificationFailed
	 * @throws ValueUndefined
	 */
	public Map.Entry<Expression, Expression> deriveUpdateExpressionOf(ChannelMember targetMember, IResourceStateAccessor stateAccessor) 
			throws ParameterizedIdentifierIsFutureWork, ResolvingMultipleDefinitionIsFutureWork, InvalidMessage, UnificationFailed, ValueUndefined {
		return deriveUpdateExpressionOf(targetMember, stateAccessor, null);
	}

	public Map.Entry<Expression, Expression> deriveUpdateExpressionOf(ChannelMember targetMember, IResourceStateAccessor stateAccessor, Map<ChannelMember, IResourceStateAccessor> inputResourceToStateAccessor) 
			throws ParameterizedIdentifierIsFutureWork, ResolvingMultipleDefinitionIsFutureWork, InvalidMessage, UnificationFailed, ValueUndefined {
		if (!getOutputChannelMembers().contains(targetMember)) return null;
		
		// Calculate unified message constraints
		Map<ChannelMember, Set<Position>> substitutedPositionsFromChannels = new HashMap<>();
		Term unifiedMessage = calcUnifiedMessage(targetMember, stateAccessor, inputResourceToStateAccessor, substitutedPositionsFromChannels);
		
		// Calculate the next state of target resource from the unified message and the current resource state
		Expression curOutputStateAccessor = stateAccessor.getCurrentStateAccessorFor(targetMember, targetMember);
		if (unifiedMessage == null) {
			// for IOChannel
			if (targetMember.getStateTransition().getMessageExpression() instanceof Term) {
				unifiedMessage = (Term) targetMember.getStateTransition().getMessageExpression();
			}
		}
		return new AbstractMap.SimpleEntry<>(targetMember.getStateTransition().deriveNextStateExpressionFor(curOutputStateAccessor, unifiedMessage), unifiedMessage);
	}
	
	/**
	 * Derive the state update calculation of the target channel member with a message and a given resource push/pull state accessor.
	 * @param targetMember a channel member whose state is to be updated
	 * @param message a message on the channel
	 * @param stateAccessor a push/pull resource state accessor
	 * @return the derived update calculation
	 * @throws ParameterizedIdentifierIsFutureWork
	 * @throws ResolvingMultipleDefinitionIsFutureWork
	 * @throws InvalidMessage
	 * @throws UnificationFailed
	 * @throws ValueUndefined
	 */
	public Expression deriveUpdateExpressionOf(ChannelMember targetMember, Term message, IResourceStateAccessor stateAccessor) 
			throws ParameterizedIdentifierIsFutureWork, ResolvingMultipleDefinitionIsFutureWork, InvalidMessage, UnificationFailed, ValueUndefined {
		if (!getOutputChannelMembers().contains(targetMember)) return null;
		
		// Calculate the next state of target resource from the unified message and the current resource state
		Expression curOutputStateAccessor = stateAccessor.getCurrentStateAccessorFor(targetMember, targetMember);
		return targetMember.getStateTransition().deriveNextStateExpressionFor(curOutputStateAccessor, message);
	}
	
	/**
	 * Fill outside resource paths with a given resource push/pull state accessor.
	 * @param targetMember a channel member whose state is to be updated
	 * @param stateAccessor a push/pull resource state accessor
	 * @return map from a depending channel member to the pair of the filled resource path and the set of the depended channel members
	 * @throws ParameterizedIdentifierIsFutureWork
	 * @throws ResolvingMultipleDefinitionIsFutureWork
	 * @throws InvalidMessage
	 * @throws UnificationFailed
	 * @throws ValueUndefined
	 */
	public Map<ChannelMember, Entry<ResourcePath, Set<ChannelMember>>> fillOutsideResourcePaths(ChannelMember targetMember, IResourceStateAccessor stateAccessor) 
			throws ParameterizedIdentifierIsFutureWork, ResolvingMultipleDefinitionIsFutureWork, InvalidMessage, UnificationFailed, ValueUndefined {		
		return fillOutsideResourcePaths(targetMember, stateAccessor, null);
	}

	public Map<ChannelMember, Entry<ResourcePath, Set<ChannelMember>>> fillOutsideResourcePaths(ChannelMember targetMember, IResourceStateAccessor stateAccessor, Map<ChannelMember, IResourceStateAccessor> inputResourceToStateAccessor) 
			throws ParameterizedIdentifierIsFutureWork, ResolvingMultipleDefinitionIsFutureWork, InvalidMessage, UnificationFailed, ValueUndefined {
		if (!getOutputChannelMembers().contains(targetMember)) return null;
		Map<ChannelMember, Entry<ResourcePath, Set<ChannelMember>>> resourcePaths = new HashMap<>();
		
		// Calculate unified message constraints from input and reference state transitions
		Map<ChannelMember, Set<Position>> substitutedPositionsInMessageFromChannels = new HashMap<>();
		Term unifiedMessage = calcUnifiedMessage(targetMember, stateAccessor, inputResourceToStateAccessor, substitutedPositionsInMessageFromChannels);
		
		// Fill outside resource paths
		if (unifiedMessage != null) {
			for (ChannelMember cm: getInputChannelMembers()) {
				if (cm.isOutside()) {
					Set<Position> dependingVarPosInMessage = new HashSet<>();
					ResourcePath filledResPath = fillOutsideResourcePath(cm.getResource(), unifiedMessage, targetMember.getStateTransition().getMessageExpression(), dependingVarPosInMessage);
					Set<ChannelMember> dependingChannelMembers =  new HashSet<>();
					for (ChannelMember otherCm: substitutedPositionsInMessageFromChannels.keySet()) {
						for (Position otherPos: substitutedPositionsInMessageFromChannels.get(otherCm)) {
							for (Position thisPos: dependingVarPosInMessage) {
								if (thisPos.isAncestorOf(otherPos)) {
									dependingChannelMembers.add(otherCm);
									break;
								}
							}
							if (dependingChannelMembers.contains(otherCm)) break;
						}
					}
					resourcePaths.put(cm, new AbstractMap.SimpleEntry<>(filledResPath, dependingChannelMembers));
				}
			}
			for (ChannelMember cm: getReferenceChannelMembers()) {
				if (cm.isOutside()) {
					Set<Position> dependingVarPosInMessage = new HashSet<>();
					ResourcePath filledResPath = fillOutsideResourcePath(cm.getResource(), unifiedMessage, targetMember.getStateTransition().getMessageExpression(), dependingVarPosInMessage);
					Set<ChannelMember> dependingChannelMembers =  new HashSet<>();
					for (ChannelMember otherCm: substitutedPositionsInMessageFromChannels.keySet()) {
						for (Position otherPos: substitutedPositionsInMessageFromChannels.get(otherCm)) {
							for (Position thisPos: dependingVarPosInMessage) {
								if (thisPos.isAncestorOf(otherPos)) {
									dependingChannelMembers.add(otherCm);
									break;
								}
							}
							if (dependingChannelMembers.contains(otherCm)) break;
						}
					}
					resourcePaths.put(cm, new AbstractMap.SimpleEntry<>(filledResPath, dependingChannelMembers));
				}
			}
			for (ChannelMember cm: getOutputChannelMembers()) {
				if (cm.isOutside()) {
					Set<Position> dependingVarPosInMessage = new HashSet<>();
					ResourcePath filledResPath = fillOutsideResourcePath(cm.getResource(), unifiedMessage, targetMember.getStateTransition().getMessageExpression(), dependingVarPosInMessage);
					Set<ChannelMember> dependingChannelMembers =  new HashSet<>();
					for (ChannelMember otherCm: substitutedPositionsInMessageFromChannels.keySet()) {
						for (Position otherPos: substitutedPositionsInMessageFromChannels.get(otherCm)) {
							for (Position thisPos: dependingVarPosInMessage) {
								if (thisPos.isAncestorOf(otherPos)) {
									dependingChannelMembers.add(otherCm);
									break;
								}
							}
							if (dependingChannelMembers.contains(otherCm)) break;
						}
					}
					resourcePaths.put(cm, new AbstractMap.SimpleEntry<>(filledResPath, dependingChannelMembers));
				}
			}
		}
		return resourcePaths;
	}
	
	public Map<ChannelMember, Set<ChannelMember>> getMemberDependency() {
		Map<ChannelMember, Set<ChannelMember>> dependency = new HashMap<>();
		
		// Collect depended channel members and their positions in the message
		Map<ChannelMember, Set<Position>> substitutedPositionsInMessage = getDependedChannelMembersAndTheirPositionsInMessage();
		
		// Resolve dependency for each outside resource path
		if (substitutedPositionsInMessage != null) {
			for (ChannelMember cm: getInputChannelMembers()) {
				if (cm.isOutside()) {
					Set<Position> dependingVarPosInMessage = getPossitionsInMessageThatChannelMemberDependsOn(cm.getResource(), cm.getStateTransition().getMessageExpression());
					Set<ChannelMember> dependingChannelMembers =  new HashSet<>();
					for (ChannelMember otherCm: substitutedPositionsInMessage.keySet()) {
						for (Position otherPos: substitutedPositionsInMessage.get(otherCm)) {
							for (Position thisPos: dependingVarPosInMessage) {
								if (thisPos.isAncestorOf(otherPos)) {
									dependingChannelMembers.add(otherCm);
									break;
								}
							}
							if (dependingChannelMembers.contains(otherCm)) break;
						}
					}
					dependency.put(cm, dependingChannelMembers);
				}
			}
			for (ChannelMember cm: getReferenceChannelMembers()) {
				if (cm.isOutside()) {
					Set<Position> dependingVarPosInMessage = getPossitionsInMessageThatChannelMemberDependsOn(cm.getResource(), cm.getStateTransition().getMessageExpression());
					Set<ChannelMember> dependingChannelMembers =  new HashSet<>();
					for (ChannelMember otherCm: substitutedPositionsInMessage.keySet()) {
						for (Position otherPos: substitutedPositionsInMessage.get(otherCm)) {
							for (Position thisPos: dependingVarPosInMessage) {
								if (thisPos.isAncestorOf(otherPos)) {
									dependingChannelMembers.add(otherCm);
									break;
								}
							}
							if (dependingChannelMembers.contains(otherCm)) break;
						}
					}
					dependency.put(cm, dependingChannelMembers);
				}
			}
			for (ChannelMember cm: getOutputChannelMembers()) {
				if (cm.isOutside()) {
					Set<Position> dependingVarPosInMessage = getPossitionsInMessageThatChannelMemberDependsOn(cm.getResource(), cm.getStateTransition().getMessageExpression());
					Set<ChannelMember> dependingChannelMembers =  new HashSet<>();
					for (ChannelMember otherCm: substitutedPositionsInMessage.keySet()) {
						for (Position otherPos: substitutedPositionsInMessage.get(otherCm)) {
							for (Position thisPos: dependingVarPosInMessage) {
								if (thisPos.isAncestorOf(otherPos)) {
									dependingChannelMembers.add(otherCm);
									break;
								}
							}
							if (dependingChannelMembers.contains(otherCm)) break;
						}
					}
					dependency.put(cm, dependingChannelMembers);
				}
			}
		}
		return dependency;
	}
	
	private Term calcUnifiedMessage(ChannelMember targetMember, IResourceStateAccessor stateAccessor, 
			Map<ChannelMember, IResourceStateAccessor> inputResourceToStateAccessor, Map<ChannelMember, Set<Position>> substitutedPositionsInMessageFromChannels)
			throws InvalidMessage, ResolvingMultipleDefinitionIsFutureWork, UnificationFailed {
		Set<Term> messageConstraints = new HashSet<>();
		
		// Calculate message constraints from input state transitions
		for (ChannelMember inputMember: getInputChannelMembers()) {
			Expression messageConstraintByInput = calcMessageConstraintForInputMember(inputMember, targetMember, stateAccessor, inputResourceToStateAccessor, substitutedPositionsInMessageFromChannels);
			messageConstraints.add((Term) messageConstraintByInput);
		}

		// Calculate message constraints from reference state transitions
		for (ChannelMember referenceMember: getReferenceChannelMembers()) {
			Expression messageConstraintByReference = calcMessageConstraintForReferenceMember(referenceMember, targetMember, stateAccessor, inputResourceToStateAccessor, substitutedPositionsInMessageFromChannels);
			messageConstraints.add((Term) messageConstraintByReference);
		}

		// Unify message constraints
		Term unifiedMessage = null;
		for (Term messageContraint: messageConstraints) {
			if (unifiedMessage == null) {
				unifiedMessage = messageContraint;
			} else {
				unifiedMessage = (Term) unifiedMessage.unify(messageContraint);
				if (unifiedMessage == null) {
					throw new UnificationFailed();
				}
			}
		}
		return unifiedMessage;
	}

	public Expression calcMessageConstraintForInputMember(ChannelMember inputMember, ChannelMember targetMember, IResourceStateAccessor stateAccessor, 
			Map<ChannelMember, IResourceStateAccessor> inputResourceToStateAccessor, Map<ChannelMember, Set<Position>> substitutedPositionsInMessageFromChannels)
			throws InvalidMessage, ResolvingMultipleDefinitionIsFutureWork {
		Expression curInputStateAccessor = null;
		Expression nextInputStateAccessor = null;
		if (inputResourceToStateAccessor == null) {
			curInputStateAccessor = stateAccessor.getCurrentStateAccessorFor(inputMember, targetMember);
			nextInputStateAccessor = stateAccessor.getNextStateAccessorFor(inputMember, targetMember);
		} else {
			curInputStateAccessor = inputResourceToStateAccessor.get(inputMember).getCurrentStateAccessorFor(inputMember, targetMember);
			nextInputStateAccessor = inputResourceToStateAccessor.get(inputMember).getNextStateAccessorFor(inputMember, targetMember);
		}
		Set<Position> substitutedPositionsInMessage = new HashSet<>();
		Expression messageConstraintByInput = inputMember.getStateTransition().deriveMessageConstraintFor(curInputStateAccessor, nextInputStateAccessor, substitutedPositionsInMessage);
		if (substitutedPositionsInMessage.size() > 0) substitutedPositionsInMessageFromChannels.put(inputMember, substitutedPositionsInMessage);
		return messageConstraintByInput;
	}

	public Expression calcMessageConstraintForReferenceMember(ChannelMember referenceMember, ChannelMember targetMember, IResourceStateAccessor stateAccessor,
			Map<ChannelMember, IResourceStateAccessor> inputResourceToStateAccessor, Map<ChannelMember, Set<Position>> substitutedPositionsInMessageFromChannels)
			throws InvalidMessage, ResolvingMultipleDefinitionIsFutureWork {
		Expression curInputStateAccessor = null;
		if (inputResourceToStateAccessor == null) {
			curInputStateAccessor = stateAccessor.getCurrentStateAccessorFor(referenceMember, targetMember);
		} else {
			curInputStateAccessor = inputResourceToStateAccessor.get(referenceMember).getCurrentStateAccessorFor(referenceMember, targetMember);
		}
		Set<Position> substitutedPositions = new HashSet<>();
		Expression messageConstraintByReference = referenceMember.getStateTransition().deriveMessageConstraintFor(curInputStateAccessor, substitutedPositions);
		if (substitutedPositions.size() > 0) substitutedPositionsInMessageFromChannels.put(referenceMember, substitutedPositions);
		return messageConstraintByReference;
	}
	
	/**
	 * Get depended channel members and their positions in the message.
	 * 
	 * @return depended channel members and their positions in the message
	 */
	private Map<ChannelMember, Set<Position>> getDependedChannelMembersAndTheirPositionsInMessage() {
		Map<ChannelMember, Set<Position>> channelMembersMessageDependsOn = new HashMap<>();
		// Collect channel members that the message depends on from input state transitions
		for (ChannelMember inputMember: getInputChannelMembers()) {
			Set<Position> substitutedPositionsInMessage = new HashSet<>();
			Expression messageTerm = inputMember.getStateTransition().getMessageExpression();
			Expression curStateTerm = inputMember.getStateTransition().getCurStateExpression();
			Expression nextStateTerm = inputMember.getStateTransition().getNextStateExpression();
			HashMap<Position, Variable> messageVars = messageTerm.getVariables();
			for (Entry<Position, Variable> varEnt: messageVars.entrySet()) {
				Variable var = varEnt.getValue();
				if (curStateTerm.getVariables().values().contains(var) || nextStateTerm.getVariables().values().contains(var)) {
					if (messageTerm instanceof Term) {
						substitutedPositionsInMessage.add(varEnt.getKey());
					} else if (messageTerm instanceof Variable) {
						if (messageTerm.equals(var)) {
							substitutedPositionsInMessage.add(new Position());
						}
					}
				}
			}
			if (substitutedPositionsInMessage.size() > 0) channelMembersMessageDependsOn.put(inputMember, substitutedPositionsInMessage);
		}

		// Collect channel members that the message depends on from reference state transitions
		for (ChannelMember referenceMember: getReferenceChannelMembers()) {
			Set<Position> substitutedPositionsInMessage = new HashSet<>();
			Expression messageTerm = referenceMember.getStateTransition().getMessageExpression();
			Expression curStateTerm = referenceMember.getStateTransition().getCurStateExpression();
			Expression nextStateTerm = referenceMember.getStateTransition().getNextStateExpression();
			HashMap<Position, Variable> messageVars = messageTerm.getVariables();
			for (Entry<Position, Variable> varEnt: messageVars.entrySet()) {
				Variable var = varEnt.getValue();
				if (curStateTerm.getVariables().values().contains(var) || nextStateTerm.getVariables().values().contains(var)) {
					if (messageTerm instanceof Term) {
						substitutedPositionsInMessage.add(varEnt.getKey());
					} else if (messageTerm instanceof Variable) {
						if (messageTerm.equals(var)) {
							substitutedPositionsInMessage.add(new Position());
						}
					}
				}
			}
			if (substitutedPositionsInMessage.size() > 0) channelMembersMessageDependsOn.put(referenceMember, substitutedPositionsInMessage);
		}
		return channelMembersMessageDependsOn;
	}

	public ResourcePath fillOutsideResourcePath(ResourcePath resource, Term unifiedMessage, Expression messageTerm, Set<Position> dependingVarPosInMessage) 
			throws ResolvingMultipleDefinitionIsFutureWork {
		ResourcePath filledResourcePath = new ResourcePath(resource);
		
		Map<Variable, Entry<Position, Expression>> bindings = new HashMap<>();
		Map<Position, Variable> messageVars = messageTerm.getVariables();
		for (Entry<Position, Variable> messageVarEnt: messageVars.entrySet()) {
			Variable var = messageVarEnt.getValue();
			Position varPos = messageVarEnt.getKey();
			Expression valueCalc = unifiedMessage.getSubTerm(varPos);
			if (valueCalc != null) {
				if (bindings.get(var) != null) throw new ResolvingMultipleDefinitionIsFutureWork();
				bindings.put(var, new AbstractMap.SimpleEntry<>(varPos, valueCalc));
			}
		}
		
		List<Expression> dstParams = filledResourcePath.getPathParams();
		for (int i = 0; i < filledResourcePath.getPathParams().size(); i++) {
			Expression pathParam = dstParams.get(i);
			if (pathParam instanceof Variable) {
				dstParams.set(i, bindings.get((Variable) pathParam).getValue());		// Replace a path parameter with a value in the unified message.
				dependingVarPosInMessage.add(bindings.get((Variable) pathParam).getKey());		// The position of the replaced variable in the message.
			} else if (pathParam instanceof Term) {
				Map<Position, Variable> pathParamVars = ((Term) pathParam).getVariables();
				for (Variable var: bindings.keySet()) {
					if (pathParamVars.values().contains(var)) {			// var is a subterm of a path parameter
						pathParam = ((Term) pathParam).substitute(var, bindings.get(var).getValue());	// Substitute a value in the unified message to var.
						dependingVarPosInMessage.add(bindings.get((Variable) var).getKey());		// The position of the replaced variable in the message.
					}
				}
				if (!(pathParam instanceof Constant)) {
					pathParam = ((Term) pathParam).reduce();
				}
				dstParams.set(i, pathParam);		// Replace a path parameter with the substituted term.
			}
		}
		return filledResourcePath;
	}
	
	private Set<Position> getPossitionsInMessageThatChannelMemberDependsOn(ResourcePath resourcePath, Expression messageTerm) {
		Set<Position> dependingVarPosInMessage = new HashSet<>();
		Map<Position, Variable> messageVars = messageTerm.getVariables();
		for (Expression pathParam: resourcePath.getPathParams()) {
			if (pathParam instanceof Variable) {
				for (Entry<Position, Variable> messageVarEnt: messageVars.entrySet()) {
					Variable var = messageVarEnt.getValue();
					Position varPos = messageVarEnt.getKey();
					if (pathParam.equals(var)) {
						dependingVarPosInMessage.add(varPos);
					}
				}
			} else if (pathParam instanceof Term) {
				Map<Position, Variable> pathParamVars = ((Term) pathParam).getVariables();
				for (Entry<Position, Variable> messageVarEnt: messageVars.entrySet()) {
					Variable var = messageVarEnt.getValue();
					Position varPos = messageVarEnt.getKey();
					if (pathParamVars.values().contains(var)) {
						dependingVarPosInMessage.add(varPos);
					}
				}
			}
		}
		return dependingVarPosInMessage;
	}
	
	@Override
	public String toString() {
		String channelSource = "channel " + getChannelName() + " {\n";
		for (ChannelMember inputMember: inputChannelMembers) {
			channelSource += "\t in " + inputMember + "\n";
		}
		for (ChannelMember refMember: referenceChannelMembers) {
			channelSource += "\t ref " + refMember + "\n";
		}
		for (ChannelMember outputMember: outputChannelMembers) {
			channelSource += "\t out " + outputMember + "\n";
		}
		channelSource += "}\n";
		return channelSource;
	}
	
	public interface IResourceStateAccessor {
		Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from);
		Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from);
		Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes);
	}
}
