package simulator;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.Term;
import models.algebra.Type;
import models.algebra.Variable;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.JsonTerm;
import models.dataConstraintModel.ListTerm;
import models.dataConstraintModel.MapTerm;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataFlowModel.DataTransferChannel;
import simulator.states.CompositeResourceState;
import simulator.states.JsonResourceState;
import simulator.states.ListResourceState;
import simulator.states.MapResourceState;
import simulator.states.PrimitiveResourceState;
import simulator.states.ResourceState;

public class SystemState {
	private Set<Resource> rootResources = new HashSet<>();
	private Map<DataTransferChannel, ChannelState> channelStates = new HashMap<>();
	private List<Event> events = new ArrayList<>();
	
	public SystemState() {
	}
	
	public SystemState(SystemState prevState) {
		for (Resource resource: prevState.getRootResources()) {
			rootResources.add(new Resource(resource));
		}
		for (Map.Entry<DataTransferChannel, ChannelState> channelEnt: prevState.getChannelStates().entrySet()) {
			if (channelEnt.getValue() != null) {
				channelStates.put(channelEnt.getKey(), (ChannelState) channelEnt.getValue().clone());
			} else {
				channelStates.put(channelEnt.getKey(), null);
			}
		}
	}
	
	public Set<Resource> getRootResources() {
		return rootResources;
	}
	
	public void addResource(Resource rootResource) {
		rootResources.add(rootResource);
	}
	
	public Resource getResource(ResourceIdentifier resourceIdentifier) {
		for (Resource root: rootResources) {
			Resource descendant = root.getDescendant(resourceIdentifier);
			if (descendant != null) return descendant;
		}
		return null;
	}
	
	public Resource getResource(String resourceIdentifier) {
		for (Resource root: rootResources) {
			Resource descendant = root.getDescendant(resourceIdentifier);
			if (descendant != null) return descendant;
		}
		return null;
	}

	/**
	 * update the state of a specified resource
	 * 
	 * @param resourceIdentifier a resource identifier to identify the resource
	 * @param curResVar a variable to represent the current state of the resource (may be replaced with resCurStateVal)
	 * @param resCurStateVal the value of the current state of the resource
	 * @param resNextStateVal the value of the new state of the resource (may contain curResVar)
	 * @return the identifier of the deepest resource created by this update.
	 */
	public List<ResourceIdentifier> updateResourceState(ResourceIdentifier resourceIdentifier, Variable curResVar, Expression resCurStateVal, Expression resNextStateVal) {
		Type resType = resourceIdentifier.getResourceStateType();
		if (resNextStateVal instanceof Term) {
			Term termValue = (Term) resNextStateVal;
			if (termValue.getSymbol().equals(DataConstraintModel.cond)) {
				// If resNextStateVal is a conditional term, then calculates it here.
				Expression condExp = termValue.getChild(0);
				if (condExp instanceof Term) {
					condExp = ((Term) condExp).substitute(curResVar, resCurStateVal);
					condExp = ((Term) condExp).reduce();
				}
				List<Expression> condArgs = new ArrayList<>();
				condArgs.add(condExp);
				condArgs.add(termValue.getChild(1));
				condArgs.add(termValue.getChild(2));
				resNextStateVal = DataConstraintModel.cond.calculate(condArgs);
				if (resNextStateVal != null) {
					return updateResourceState(resourceIdentifier, curResVar, resCurStateVal, resNextStateVal);
				}
				return null;
			}
		}
		if (resType != null && DataConstraintModel.typeList.isAncestorOf(resType)) {
			if (resNextStateVal instanceof Constant) {
			} else if (resNextStateVal instanceof ListTerm) {
				ListTerm listValue = (ListTerm) resNextStateVal;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				if (state instanceof ListResourceState) {
					List<ResourceIdentifier> createdResources = new ArrayList<>();
					((ListResourceState) state).clearChildStates();
					for (int i = 0; i < listValue.size(); i++) {
						Expression childExp = new Constant(Integer.toString(i), DataConstraintModel.typeInt);
						ResourceHierarchy childResourceHierarchy = null;
						if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
							childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
						} else {
							Type childResType = null;
							if (listValue.get(i) instanceof Variable) {
								childResType = ((Variable) listValue.get(i)).getType();
							} else if (listValue.get(i) instanceof Term) {
								childResType = ((Term) listValue.get(i)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, listValue.get(i));
						((ListResourceState) state).addChildState(childInfo.getKey());
						createdResources.addAll(childInfo.getValue());
					}
					return createdResources;
				}
				return null;
			} else if (resNextStateVal instanceof Term) {
				Term listValue = (Term) resNextStateVal;
				if (listValue.getSymbol().equals(DataConstraintModel.append)) {
					List<ResourceIdentifier> createdResources = new ArrayList<>();
					Resource res = getResource(resourceIdentifier);
					ResourceState state = res.getState();
					if (listValue.getChild(0) instanceof Term) {
						if (listValue.getChild(0) instanceof Constant && ((Constant) listValue.getChild(0)).getSymbol().equals(DataConstraintModel.nil)) {
							// If the child list is nil.
							ResourceState parentState = res.getParent().getState();
							ResourceState newState = new ListResourceState();
							if (parentState instanceof CompositeResourceState) {
								((CompositeResourceState) parentState).replaceChildState(state, newState);
							}
							state = newState;
							res.changeState(state);
						} else {
							Expression childList = ((Term) listValue.getChild(0)).reduce();
							if (childList instanceof ListTerm) {
								// If the child list is a list literal.
								List<ResourceIdentifier> childInfo = updateResourceState(resourceIdentifier, curResVar, resCurStateVal, childList);		// one more recursion.
								if (childInfo != null) createdResources.addAll(childInfo);
							} else if (childList instanceof Term) {
								// There may or may not be the current state of the resource (curResVar) under the child.
								List<ResourceIdentifier> childInfo = updateResourceState(resourceIdentifier, curResVar, resCurStateVal, listValue.getChild(0));
								createdResources.addAll(childInfo);
							}
						}
					}
					Expression childExp = null;
					if (state instanceof ListResourceState) {
						childExp = new Constant(Integer.toString(((ListResourceState) state).getChildStates().size()), DataConstraintModel.typeInt);
					} else if (state instanceof PrimitiveResourceState && ((PrimitiveResourceState) state).getValue().getSymbol().equals(DataConstraintModel.nil)) {
						// If the value of state is nil.
						childExp = new Constant("0", DataConstraintModel.typeInt);
						ResourceState parentState = res.getParent().getState();
						ResourceState newState = new ListResourceState();
						if (parentState instanceof CompositeResourceState) {
							((CompositeResourceState) parentState).replaceChildState(state, newState);
						}
						state = newState;
						res.changeState(state);
					}
					ResourceHierarchy childResourceHierarchy = null;
					if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
						childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
					} else {
						Type childResType = null;
						if (listValue.getChild(1) instanceof Variable) {
							childResType = ((Variable) listValue.getChild(1)).getType();
						} else if (listValue.getChild(1) instanceof Term) {
							childResType = ((Term) listValue.getChild(1)).getType();
						}
						childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
					}
					ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
					Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, listValue.getChild(1));
					((ListResourceState) state).addChildState(childInfo.getKey());
					createdResources.addAll(childInfo.getValue());
					return createdResources;
				}
			}
		} else if (resType != null && DataConstraintModel.typeMap.isAncestorOf(resType)) {
			if (resNextStateVal instanceof Constant) {
			} else if (resNextStateVal instanceof JsonTerm) {
				JsonTerm mapValue = (JsonTerm) resNextStateVal;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				if (state instanceof MapResourceState) {
					List<ResourceIdentifier> createdResources = new ArrayList<>();
					((MapResourceState) state).clearChildStates();
					for (String key: mapValue.keySet()) {
						Expression childExp = new Constant(key, DataConstraintModel.typeString);
						ResourceHierarchy childResourceHierarchy = null;
						if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
							childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
						} else {
							Type childResType = null;
							if (mapValue.get(key) instanceof Variable) {
								childResType = ((Variable) mapValue.get(key)).getType();
							} else if (mapValue.get(key) instanceof Term) {
								childResType = ((Term) mapValue.get(key)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, mapValue.get(key));
						((MapResourceState) state).addChildState(key, childInfo.getKey());
						createdResources.addAll(childInfo.getValue());
					}
					return createdResources;
				}
				return null;
			} else if (resNextStateVal instanceof MapTerm) {
				MapTerm mapValue = (MapTerm) resNextStateVal;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				List<ResourceIdentifier> createdResources = new ArrayList<>();
				for (String key: mapValue.keySet()) {
					Expression childExp = new Constant(key, DataConstraintModel.typeString);
					ResourceHierarchy childResourceHierarchy = null;
					if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
						childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
					} else {
						Type childResType = null;
						if (mapValue.get(key) instanceof Variable) {
							childResType = ((Variable) mapValue.get(key)).getType();
						} else if (mapValue.get(key) instanceof Term) {
							childResType = ((Term) mapValue.get(key)).getType();
						}
						childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
					}
					ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
					Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, mapValue.get(key));
					((MapResourceState) state).addChildState(key, childInfo.getKey());
					createdResources.addAll(childInfo.getValue());
				}
				return createdResources;
			} else if (resNextStateVal instanceof Term) {
				Term mapValue = (Term) resNextStateVal;
				if (mapValue.getSymbol().equals(DataConstraintModel.insert)) {
					List<ResourceIdentifier> createdResources = new ArrayList<>();
					Expression childKeyExp = mapValue.getChild(1);
					if (childKeyExp instanceof Term) {
						childKeyExp = ((Term) childKeyExp).substitute(curResVar, resCurStateVal);
						childKeyExp = ((Term) childKeyExp).reduce();
					}
					if (childKeyExp instanceof Constant) {
						Resource res = getResource(resourceIdentifier);
						ResourceState state = res.getState();
						if (mapValue.getChild(0) instanceof Term) {
							if (mapValue.getChild(0) instanceof Constant && ((Constant) mapValue.getChild(0)).getSymbol().equals(DataConstraintModel.nil)) {
								// If the child map is nil.
								ResourceState parentState = res.getParent().getState();
								ResourceState newState = new MapResourceState();
								if (parentState instanceof CompositeResourceState) {
									((CompositeResourceState) parentState).replaceChildState(state, newState);
								}
								state = newState;
								res.changeState(state);
							} else {
								Expression childMap = ((Term) mapValue.getChild(0)).reduce();
								if (childMap instanceof MapTerm) {
									// If the child map is a map literal.
									List<ResourceIdentifier> childInfo = updateResourceState(resourceIdentifier, curResVar, resCurStateVal, childMap);	// one more recursion
									createdResources.addAll(childInfo);
								} else if (childMap instanceof Term) {
									// There may or may not be the current state of the resource (curResVar) under the child.
									List<ResourceIdentifier> childInfo = updateResourceState(resourceIdentifier, curResVar, resCurStateVal, mapValue.getChild(0));
									createdResources.addAll(childInfo);
								}
							}
						}
						if (state instanceof PrimitiveResourceState && ((PrimitiveResourceState) state).getValue().getSymbol().equals(DataConstraintModel.nil)) {
							// If the value of state is nil.
							ResourceState parentState = res.getParent().getState();
							ResourceState newState = new MapResourceState();
							if (parentState instanceof CompositeResourceState) {
								((CompositeResourceState) parentState).replaceChildState(state, newState);
							}
							state = newState;
							res.changeState(state);
						}
						ResourceHierarchy childResourceHierarchy = null;
						if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
							childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
						} else {
							Type childResType = null;
							if (mapValue.getChild(2) instanceof Variable) {
								childResType = ((Variable) mapValue.getChild(2)).getType();
							} else if (mapValue.getChild(2) instanceof Term) {
								childResType = ((Term) mapValue.getChild(2)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childKeyExp, childResourceHierarchy);
						String childId = (String) ((Constant) childKeyExp).getValue();
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, mapValue.getChild(2));
						((MapResourceState) state).addChildState(childId, childInfo.getKey());
						createdResources.addAll(childInfo.getValue());
						return createdResources;
					}
				}
			}
		} else if (resType != null && DataConstraintModel.typeJson.isAncestorOf(resType)) {
			if (resNextStateVal instanceof Constant) {
			} else if (resNextStateVal instanceof JsonTerm) {
				JsonTerm jsonValue = (JsonTerm) resNextStateVal;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				if (state instanceof JsonResourceState) {
					List<ResourceIdentifier> createdResources = new ArrayList<>();
					for (String key: jsonValue.keySet()) {
						ResourceHierarchy childResourceHierarchy = null;
						for (ResourceHierarchy childRes: resourceIdentifier.getResourceHierarchy().getChildren()) {
							if (childRes.getResourceName().equals(key)) {
								childResourceHierarchy = childRes;
								break;
							}
						}
						if (childResourceHierarchy == null) {
							Type childResType = null;
							if (jsonValue.get(key) instanceof Variable) {
								childResType = ((Variable) jsonValue.get(key)).getType();
							} else if (jsonValue.get(key) instanceof Term) {
								childResType = ((Term) jsonValue.get(key)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), key, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, key, childResourceHierarchy);
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, jsonValue.get(key));
						ResourceState childState = childInfo.getKey();
						if (res.getChildrenMap() != null && res.getChildrenMap().get(key) != null) {
							res.getChildrenMap().get(key).changeState(childState);
						}
						((JsonResourceState) state).addChildState(key, childState);
						createdResources.addAll(childInfo.getValue());
					}
					return createdResources;
				}
				return null;
			} else if (resNextStateVal instanceof Term) {
				Term jsonValue = (Term) resNextStateVal;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				List<ResourceIdentifier> createdResources = new ArrayList<>();
				while (jsonValue.getSymbol().equals(DataConstraintModel.addMember)) {
					Expression childKeyExp = jsonValue.getChild(1);
					if (childKeyExp instanceof Constant) {
						String memberName = (String) ((Constant) childKeyExp).getValue();
						ResourceHierarchy childResourceHierarchy = null;
						for (ResourceHierarchy childRes: resourceIdentifier.getResourceHierarchy().getChildren()) {
							if (childRes.getResourceName().equals(memberName)) {
								childResourceHierarchy = childRes;
								break;
							}
						}
						if (childResourceHierarchy == null) {
							Type childResType = null;
							if (jsonValue.getChild(2) instanceof Variable) {
								childResType = ((Variable) jsonValue.getChild(2)).getType();
							} else if (jsonValue.getChild(2) instanceof Term) {
								childResType = ((Term) jsonValue.getChild(2)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), memberName, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, memberName, childResourceHierarchy);
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, jsonValue.getChild(2));
						((JsonResourceState) state).addChildState(memberName, childInfo.getKey());
						createdResources.addAll(childInfo.getValue());
					}
					if (!(jsonValue.getChild(0) instanceof Term)) break;
					jsonValue = (Term) jsonValue.getChild(0);
				}
				return createdResources;
			}
		} else {
			if (curResVar != null && resCurStateVal != null) {
				if (resNextStateVal instanceof Term) {
					resNextStateVal = ((Term) resNextStateVal).substitute(curResVar, resCurStateVal);
					resNextStateVal = ((Term) resNextStateVal).reduce();
				}
			}
			if (resNextStateVal instanceof Constant) {
				Resource res = getResource(resourceIdentifier);
				ResourceState state = null;
				if (res != null) {
					state = res.getState();
					((PrimitiveResourceState) state).setValue((Constant) resNextStateVal);
				} else {
					ResourceIdentifier parentResId = (ResourceIdentifier) resourceIdentifier.getParent();
					Type parentResType = parentResId.getResourceStateType();
					if (parentResType != null && DataConstraintModel.typeList.isAncestorOf(parentResType)) {
					} else if (parentResType != null && DataConstraintModel.typeMap.isAncestorOf(parentResType)) {
						JsonResourceState parentState = (JsonResourceState) getResource(parentResId).getState();
						parentState.addChildState((String) ((Constant) resourceIdentifier.getLastParam()).getValue(), new PrimitiveResourceState((Constant) resNextStateVal));
					} else if (parentResType != null && DataConstraintModel.typeJson.isAncestorOf(parentResType)) {
						JsonResourceState parentState = (JsonResourceState) getResource(parentResId).getState();
						parentState.addChildState(resourceIdentifier.getLeafResourceName(), new PrimitiveResourceState((Constant) resNextStateVal));
					}
				}
				List<ResourceIdentifier> updatedResources = new ArrayList<>();
				updatedResources.add(resourceIdentifier);
				return updatedResources;
			}
		}
		return null;
	}

	public Map.Entry<ResourceState, List<ResourceIdentifier>> createResourceState(ResourceIdentifier resourceIdentifier, Variable curResVar, Expression resCurStateVal, Expression resStateValue) {
		Type resType = resourceIdentifier.getResourceStateType();
		if (resType == null && resStateValue instanceof Term) {
			resType = ((Term) resStateValue).getType();
			resStateValue = ((Term) resStateValue).reduce();
		}
		if (resType != null) {
			if (DataConstraintModel.typeList.isAncestorOf(resType)) {
				if (resStateValue instanceof Constant) {
				} else if (resStateValue instanceof ListTerm) {
					ListTerm listValue = (ListTerm) resStateValue;
					ListResourceState state = new ListResourceState();
					Map.Entry<ResourceState, List<ResourceIdentifier>> createInfo = new AbstractMap.SimpleEntry<>(null, new ArrayList<>());
					for (int i = 0; i < listValue.size(); i++) {
						Expression childExp = new Constant(Integer.toString(i), DataConstraintModel.typeInt);
						ResourceHierarchy childResourceHierarchy = null;
						if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
							childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
						} else {
							Type childResType = null;
							if (listValue.get(i) instanceof Variable) {
								childResType = ((Variable) listValue.get(i)).getType();
							} else if (listValue.get(i) instanceof Term) {
								childResType = ((Term) listValue.get(i)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, listValue.get(i));
						state.addChildState(childInfo.getKey());
						createInfo.getValue().addAll(childInfo.getValue());
						createInfo = new AbstractMap.SimpleEntry<>(state, createInfo.getValue());
					}
					return createInfo;
				} else if (resStateValue instanceof Term) {
					Term listValue = (Term) resStateValue;
					if (listValue.getSymbol().equals(DataConstraintModel.append)) {
						List<ResourceIdentifier> createdResources = new ArrayList<>();
						ListResourceState state = new ListResourceState();
						if (listValue.getChild(0) instanceof Term) {
							if (listValue.getChild(0) instanceof Constant && ((Constant) listValue.getChild(0)).getSymbol().equals(DataConstraintModel.nil)) {
								// If the child list is nil.
							} else {
								Expression childList = ((Term) listValue.getChild(0));
								childList = ((Term) childList).substitute(curResVar, resStateValue);
								childList = ((Term) childList).reduce();
								if (childList instanceof ListTerm) {
									// If the child list is a list literal.
									Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(resourceIdentifier, curResVar, resCurStateVal, childList);		// one more recursion.
									state = (ListResourceState) childInfo.getKey();
									if (childInfo != null) createdResources.addAll(childInfo.getValue());
								} else if (childList instanceof Term) {
									// If the child list is a list term.
									Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(resourceIdentifier, curResVar, resCurStateVal, listValue.getChild(0));
									state = (ListResourceState) childInfo.getKey();
									createdResources.addAll(childInfo.getValue());
								}
							}
						}
						Expression childExp = new Constant(Integer.toString(((ListResourceState) state).getChildStates().size()), DataConstraintModel.typeInt);
						ResourceHierarchy childResourceHierarchy = null;
						if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
							childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
						} else {
							Type childResType = null;
							if (listValue.getChild(1) instanceof Variable) {
								childResType = ((Variable) listValue.getChild(1)).getType();
							} else if (listValue.getChild(1) instanceof Term) {
								childResType = ((Term) listValue.getChild(1)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, listValue.getChild(1));
						state.addChildState(childInfo.getKey());
						createdResources.addAll(childInfo.getValue());
						return new AbstractMap.SimpleEntry<>(state, createdResources);
					}
				}
			} else if (DataConstraintModel.typeMap.isAncestorOf(resType)) {
				if (resStateValue instanceof Constant) {
				} else if (resStateValue instanceof JsonTerm) {
					JsonTerm mapValue = (JsonTerm) resStateValue;
					MapResourceState state = new MapResourceState();
					Map.Entry<ResourceState, List<ResourceIdentifier>> createInfo = new AbstractMap.SimpleEntry<>(null, new ArrayList<>());
					for (String key: mapValue.keySet()) {
						Expression childExp = new Constant(key, DataConstraintModel.typeString);
						ResourceHierarchy childResourceHierarchy = null;
						if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
							childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
						} else {
							Type childResType = null;
							if (mapValue.get(key) instanceof Variable) {
								childResType = ((Variable) mapValue.get(key)).getType();
							} else if (mapValue.get(key) instanceof Term) {
								childResType = ((Term) mapValue.get(key)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, mapValue.get(key));
						state.addChildState(key, childInfo.getKey());
						createInfo.getValue().addAll(childInfo.getValue());
						createInfo = new AbstractMap.SimpleEntry<>(state, createInfo.getValue());
					}
					return createInfo;
				} else if (resStateValue instanceof MapTerm) {
					MapTerm mapValue = (MapTerm) resStateValue;
					MapResourceState state = new MapResourceState();
					Map.Entry<ResourceState, List<ResourceIdentifier>> createInfo = new AbstractMap.SimpleEntry<>(null, new ArrayList<>());
					for (String key: mapValue.keySet()) {
						Expression childExp = new Constant(key, DataConstraintModel.typeString);
						ResourceHierarchy childResourceHierarchy = null;
						if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
							childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
						} else {
							Type childResType = null;
							if (mapValue.get(key) instanceof Variable) {
								childResType = ((Variable) mapValue.get(key)).getType();
							} else if (mapValue.get(key) instanceof Term) {
								childResType = ((Term) mapValue.get(key)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, mapValue.get(key));
						state.addChildState(key, childInfo.getKey());
						createInfo.getValue().addAll(childInfo.getValue());
						createInfo = new AbstractMap.SimpleEntry<>(state, createInfo.getValue());
					}
					return createInfo;
				} else if (resStateValue instanceof Term) {
					Term mapValue = (Term) resStateValue;
					if (mapValue.getSymbol().equals(DataConstraintModel.insert)) {
						Expression childExp = mapValue.getChild(1);
						if (childExp instanceof Constant) {
							List<ResourceIdentifier> createdResources = new ArrayList<>();
							MapResourceState state = new MapResourceState();
							if (mapValue.getChild(0) instanceof Term) {
								if (mapValue.getChild(0) instanceof Constant && ((Constant) mapValue.getChild(0)).getSymbol().equals(DataConstraintModel.nil)) {
									// If the child list is nil.
								} else {
									Expression childMap = ((Term) mapValue.getChild(0));
									childMap = ((Term) childMap).substitute(curResVar, resStateValue);
									childMap = ((Term) childMap).reduce();
									if (childMap instanceof MapTerm) {
										// If the child map is a map literal.
										Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(resourceIdentifier, curResVar, resCurStateVal, childMap);	// one more recursion.
										state = (MapResourceState) childInfo.getKey();
										if (childInfo != null) createdResources.addAll(childInfo.getValue());
									} else if (childMap instanceof Term) {
										// If the child map is a map term.
										Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(resourceIdentifier, curResVar, resCurStateVal, mapValue.getChild(0));
										state = (MapResourceState) childInfo.getKey();
										createdResources.addAll(childInfo.getValue());
									}
								}
							}
							ResourceHierarchy childResourceHierarchy = null;
							if (resourceIdentifier.getResourceHierarchy().getChildren() != null && resourceIdentifier.getResourceHierarchy().getChildren().size() > 0) {
								childResourceHierarchy = resourceIdentifier.getResourceHierarchy().getChildren().iterator().next();
							} else {
								Type childResType = null;
								if (mapValue.getChild(2) instanceof Variable) {
									childResType = ((Variable) mapValue.getChild(2)).getType();
								} else if (mapValue.getChild(2) instanceof Term) {
									childResType = ((Term) mapValue.getChild(2)).getType();
								}
								childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1, childResType);
							}
							ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
							String childId = (String) ((Constant) childExp).getValue();
							Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, mapValue.getChild(2));
							state.addChildState(childId, childInfo.getKey());
							createdResources.addAll(childInfo.getValue());
							return new AbstractMap.SimpleEntry<>(state, createdResources);
						}
					}
				}
			} else if (DataConstraintModel.typeJson.isAncestorOf(resType)) {
				if (resStateValue instanceof Constant) {
				} else if (resStateValue instanceof JsonTerm) {
					JsonTerm jsonValue = (JsonTerm) resStateValue;
					JsonResourceState state = new JsonResourceState();
					Map.Entry<ResourceState, List<ResourceIdentifier>> createInfo = new AbstractMap.SimpleEntry<>(null, new ArrayList<>());
					for (String key: jsonValue.keySet()) {
						ResourceHierarchy childResourceHierarchy = null;
						for (ResourceHierarchy childRes: resourceIdentifier.getResourceHierarchy().getChildren()) {
							if (childRes.getResourceName().equals(key)) {
								childResourceHierarchy = childRes;
								break;
							}
						}
						if (childResourceHierarchy == null) {
							Type childResType = null;
							if (jsonValue.get(key) instanceof Variable) {
								childResType = ((Variable) jsonValue.get(key)).getType();
							} else if (jsonValue.get(key) instanceof Term) {
								childResType = ((Term) jsonValue.get(key)).getType();
							}
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), key, childResType);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, key, childResourceHierarchy);
						Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, jsonValue.get(key));
						ResourceState childState = childInfo.getKey();
						Resource res = getResource(resourceIdentifier);
						if (res != null && res.getChildrenMap() != null && res.getChildrenMap().get(key) != null) {
							res.getChildrenMap().get(key).changeState(childState);
						}
						state.addChildState(key, childState);
						createInfo.getValue().addAll(childInfo.getValue());
						createInfo = new AbstractMap.SimpleEntry<>(state, createInfo.getValue());
					}
					return createInfo;
				} else if (resStateValue instanceof Term) {
					Term jsonValue = (Term) resStateValue;
					JsonResourceState state = new JsonResourceState();
					Map.Entry<ResourceState, List<ResourceIdentifier>> createInfo = new AbstractMap.SimpleEntry<>(null, new ArrayList<>());
					while (jsonValue.getSymbol().equals(DataConstraintModel.addMember)) {
						Expression childExp = jsonValue.getChild(1);
						if (childExp instanceof Constant) {
							String memberName = (String) ((Constant) childExp).getValue();
							ResourceHierarchy childResourceHierarchy = null;
							for (ResourceHierarchy childRes: resourceIdentifier.getResourceHierarchy().getChildren()) {
								if (childRes.getResourceName().equals(memberName)) {
									childResourceHierarchy = childRes;
									break;
								}
							}
							if (childResourceHierarchy == null) {
								Type childResType = null;
								if (jsonValue.getChild(2) instanceof Variable) {
									childResType = ((Variable) jsonValue.getChild(2)).getType();
								} else if (jsonValue.getChild(2) instanceof Term) {
									childResType = ((Term) jsonValue.getChild(2)).getType();
								}
								childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), memberName, childResType);
							}
							ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, memberName, childResourceHierarchy);
							Map.Entry<ResourceState, List<ResourceIdentifier>> childInfo = createResourceState(childResourceIdentifier, curResVar, resCurStateVal, jsonValue.getChild(2));
							state.addChildState(memberName, childInfo.getKey());
							createInfo.getValue().addAll(childInfo.getValue());
							createInfo = new AbstractMap.SimpleEntry<>(state, createInfo.getValue());
						}
						if (!(jsonValue.getChild(0) instanceof Term)) break;
						jsonValue = (Term) jsonValue.getChild(0);
					}
					return createInfo;
				}
			}
		}
		if (resStateValue instanceof Constant) {
			Resource res = getResource(resourceIdentifier);
			ResourceState state = null;
			if (res != null) {
				state = res.getState();
				((PrimitiveResourceState) state).setValue((Constant) resStateValue);
			} else {
				state = new PrimitiveResourceState((Constant) resStateValue);
			}
			List<ResourceIdentifier> updatedResources = new ArrayList<>();
			updatedResources.add(resourceIdentifier);
			return new AbstractMap.SimpleEntry<>(state, updatedResources);
		}
		return null;
	}
	
	public Map<DataTransferChannel, ChannelState> getChannelStates() {
		return channelStates;
	}
	
	public ChannelState getChannelState(DataTransferChannel channel) {
		return channelStates.get(channel);
	}
	
	public void addChannel(DataTransferChannel channel) {
		channelStates.put(channel, null);
	}
	
	public void updateChannelState(DataTransferChannel channel, ChannelState channelState) {
		channelStates.put(channel, channelState);
	}

	public List<Event> getEvents() {
		return events;
	}
	
	public void addEvent(Event event) {
		events.add(event);
	}
}
