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.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 resStateValue a new state of the resource
	 * @return
	 */
	public ResourceIdentifier updateResourceState(ResourceIdentifier resourceIdentifier, Expression resStateValue) {
		Type resType = resourceIdentifier.getResourceStateType();
		if (resType != null && DataConstraintModel.typeList.isAncestorOf(resType)) {
			if (resStateValue instanceof Constant) {
			} else if (resStateValue instanceof ListTerm) {
				ListTerm listValue = (ListTerm) resStateValue;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				ResourceIdentifier createdResource = null;
				for (int i = 0; i < listValue.size(); i++) {
					Expression childExp = new Constant(Integer.toString(i), DataConstraintModel.typeInt);
					ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, 
																						childExp, 
																						resourceIdentifier.getResourceHierarchy().getChildren().iterator().next());
					Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, listValue.get(i));
					((ListResourceState) state).addChildState(childInfo.getKey());
					createdResource = childInfo.getValue();
				}
				return createdResource;
			} else if (resStateValue instanceof Term) {
				Term listValue = (Term) resStateValue;
				if (listValue.getSymbol().equals(DataConstraintModel.append)) {
					Resource res = getResource(resourceIdentifier);
					ResourceState state = res.getState();
					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 {
						childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1);
					}
					ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
					Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, listValue.getChild(1));
					((ListResourceState) state).addChildState(childInfo.getKey());
					return childInfo.getValue();
				}
			}
		} else if (resType != null && DataConstraintModel.typeMap.isAncestorOf(resType)) {
			if (resStateValue instanceof Constant) {
			} else if (resStateValue instanceof JsonTerm) {
				JsonTerm mapValue = (JsonTerm) resStateValue;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				ResourceIdentifier createdResource = null;
				for (String key: mapValue.keySet()) {
					Expression childExp = new Constant(key, DataConstraintModel.typeString);
					ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, 
																						childExp, 
																						resourceIdentifier.getResourceHierarchy().getChildren().iterator().next());
					Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, mapValue.get(key));
					((MapResourceState) state).addChildState(key, childInfo.getKey());
					createdResource = childInfo.getValue();
				}
				return createdResource;
			} else if (resStateValue instanceof MapTerm) {
				MapTerm mapValue = (MapTerm) resStateValue;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				ResourceIdentifier createdResource = null;
				for (String key: mapValue.keySet()) {
					Expression childExp = new Constant(key, DataConstraintModel.typeString);
					ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, 
																						childExp, 
																						resourceIdentifier.getResourceHierarchy().getChildren().iterator().next());
					Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, mapValue.get(key));
					((MapResourceState) state).addChildState(key, childInfo.getKey());
					createdResource = childInfo.getValue();
				}
				return createdResource;
			} else if (resStateValue instanceof Term) {
				Term mapValue = (Term) resStateValue;
				if (mapValue.getSymbol().equals(DataConstraintModel.insert)) {
					Expression childExp = mapValue.getChild(1);
					if (childExp instanceof Constant) {
						Resource res = getResource(resourceIdentifier);
						ResourceState state = res.getState();
						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 {
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), 1);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, childExp, childResourceHierarchy);
						String childId = ((Constant) childExp).toString();
						Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, mapValue.getChild(2));
						((MapResourceState) state).addChildState(childId, childInfo.getKey());
						return childInfo.getValue();
					}
				}
			}
		} else if (resType != null && DataConstraintModel.typeJson.isAncestorOf(resType)) {
			if (resStateValue instanceof Constant) {
			} else if (resStateValue instanceof JsonTerm) {
				JsonTerm jsonValue = (JsonTerm) resStateValue;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				ResourceIdentifier createdResource = null;
				for (String key: jsonValue.keySet()) {
					String memberName = key.replace("\"", "");
					ResourceHierarchy childResourceHierarchy = null;
					for (ResourceHierarchy childRes: resourceIdentifier.getResourceHierarchy().getChildren()) {
						if (childRes.getResourceName().equals(memberName)) {
							childResourceHierarchy = childRes;
							break;
						}
					}
					if (childResourceHierarchy == null) {
						childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), memberName);
					}
					ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, memberName, childResourceHierarchy);
					Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, jsonValue.get(key));
					ResourceState childState = childInfo.getKey();
					if (res.getChildrenMap() != null && res.getChildrenMap().get(memberName) != null) {
						res.getChildrenMap().get(memberName).changeState(childState);
					}
					((JsonResourceState) state).addChildState(memberName, childState);
					createdResource = childInfo.getValue();
				}
				return createdResource;
			} else if (resStateValue instanceof Term) {
				Term jsonValue = (Term) resStateValue;
				Resource res = getResource(resourceIdentifier);
				ResourceState state = res.getState();
				ResourceIdentifier createdResource = null;
				while (jsonValue.getSymbol().equals(DataConstraintModel.addMember)) {
					Expression childExp = jsonValue.getChild(1);
					if (childExp instanceof Constant) {
						String memberName = ((Constant) childExp).getSymbol().getName().replace("\"", "");
						ResourceHierarchy childResourceHierarchy = null;
						for (ResourceHierarchy childRes: resourceIdentifier.getResourceHierarchy().getChildren()) {
							if (childRes.getResourceName().equals(memberName)) {
								childResourceHierarchy = childRes;
								break;
							}
						}
						if (childResourceHierarchy == null) {
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), memberName);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, memberName, childResourceHierarchy);
						String childId = ((Constant) childExp).toString();
						Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, jsonValue.getChild(2));
						((JsonResourceState) state).addChildState(childId, childInfo.getKey());
						createdResource = childInfo.getValue();
					}
					if (!(jsonValue.getChild(0) instanceof Term)) break;
					jsonValue = (Term) jsonValue.getChild(0);
				}
				return createdResource;
			}
		} else {
			if (resStateValue instanceof Constant) {
				Resource res = getResource(resourceIdentifier);
				ResourceState state = null;
				if (res != null) {
					state = res.getState();
					((PrimitiveResourceState) state).setValue((Constant) resStateValue);
				} 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(((Constant) resourceIdentifier.getLastParam()).toString(), new PrimitiveResourceState((Constant) resStateValue));
					} else if (parentResType != null && DataConstraintModel.typeJson.isAncestorOf(parentResType)) {
						JsonResourceState parentState = (JsonResourceState) getResource(parentResId).getState();
						parentState.addChildState("\"" + resourceIdentifier.getLeafResourceName() + "\"", new PrimitiveResourceState((Constant) resStateValue));
					}
				}
				return resourceIdentifier;
			}
		}
		return resourceIdentifier;
	}

	public Map.Entry<ResourceState, ResourceIdentifier> createResourceState(ResourceIdentifier resourceIdentifier, 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, ResourceIdentifier> createInfo = null;
					for (int i = 0; i < listValue.size(); i++) {
						Expression childExp = new Constant(Integer.toString(i), DataConstraintModel.typeInt);
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, 
																							childExp, 
																							resourceIdentifier.getResourceHierarchy().getChildren().iterator().next());
						Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, listValue.get(i));
						state.addChildState(childInfo.getKey());
						createInfo = new AbstractMap.SimpleEntry<>(state, childInfo.getValue());
					}
					return createInfo;
				} else if (resStateValue instanceof Term) {
					Term listValue = (Term) resStateValue;
					if (listValue.getSymbol().equals(DataConstraintModel.append)) {
						ListResourceState state = new ListResourceState();
						Expression childExp = new Constant(Integer.toString(((ListResourceState) state).getChildStates().size()), DataConstraintModel.typeInt);
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, 
																							childExp, 
																							resourceIdentifier.getResourceHierarchy().getChildren().iterator().next());
						Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, listValue.getChild(1));
						state.addChildState(childInfo.getKey());
						return new AbstractMap.SimpleEntry<>(state, childInfo.getValue());
					}
				}
			} 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, ResourceIdentifier> createInfo = null;
					for (String key: mapValue.keySet()) {
						Expression childExp = new Constant(key, DataConstraintModel.typeString);
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, 
																							childExp, 
																							resourceIdentifier.getResourceHierarchy().getChildren().iterator().next());
						Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, mapValue.get(key));
						state.addChildState(key, childInfo.getKey());
						createInfo = new AbstractMap.SimpleEntry<>(state, childInfo.getValue());
					}
					return createInfo;
				} else if (resStateValue instanceof MapTerm) {
					MapTerm mapValue = (MapTerm) resStateValue;
					MapResourceState state = new MapResourceState();
					Map.Entry<ResourceState, ResourceIdentifier> createInfo = null;
					for (String key: mapValue.keySet()) {
						Expression childExp = new Constant(key, DataConstraintModel.typeString);
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, 
																							childExp, 
																							resourceIdentifier.getResourceHierarchy().getChildren().iterator().next());
						Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, mapValue.get(key));
						state.addChildState(key, childInfo.getKey());
						createInfo = new AbstractMap.SimpleEntry<>(state, childInfo.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) {
							MapResourceState state = new MapResourceState();
							ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, 
																								childExp, 
																								resourceIdentifier.getResourceHierarchy().getChildren().iterator().next());
							String childId = ((Constant) childExp).toString();
							Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, mapValue.getChild(2));
							state.addChildState(childId, childInfo.getKey());
							return new AbstractMap.SimpleEntry<>(state, childInfo.getValue());
						}
					}
				}
			} 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, ResourceIdentifier> createInfo = null;
					for (String key: jsonValue.keySet()) {
						String memberName = key.replace("\"", "");
						ResourceHierarchy childResourceHierarchy = null;
						for (ResourceHierarchy childRes: resourceIdentifier.getResourceHierarchy().getChildren()) {
							if (childRes.getResourceName().equals(memberName)) {
								childResourceHierarchy = childRes;
								break;
							}
						}
						if (childResourceHierarchy == null) {
							childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), memberName);
						}
						ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, memberName, childResourceHierarchy);
						Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, jsonValue.get(key));
						ResourceState childState = childInfo.getKey();
						Resource res = getResource(resourceIdentifier);
						if (res != null && res.getChildrenMap() != null && res.getChildrenMap().get(memberName) != null) {
							res.getChildrenMap().get(memberName).changeState(childState);
						}
						state.addChildState(memberName, childState);
						createInfo = new AbstractMap.SimpleEntry<>(state, childInfo.getValue());
					}
					return createInfo;
				} else if (resStateValue instanceof Term) {
					Term jsonValue = (Term) resStateValue;
					JsonResourceState state = new JsonResourceState();
					Map.Entry<ResourceState, ResourceIdentifier> createInfo = null;
					while (jsonValue.getSymbol().equals(DataConstraintModel.addMember)) {
						Expression childExp = jsonValue.getChild(1);
						if (childExp instanceof Constant) {
							String memberName = ((Constant) childExp).getSymbol().getName().replace("\"", "");
							ResourceHierarchy childResourceHierarchy = null;
							for (ResourceHierarchy childRes: resourceIdentifier.getResourceHierarchy().getChildren()) {
								if (childRes.getResourceName().equals(memberName)) {
									childResourceHierarchy = childRes;
									break;
								}
							}
							if (childResourceHierarchy == null) {
								childResourceHierarchy = new ResourceHierarchy(resourceIdentifier.getResourceHierarchy(), memberName);
							}
							ResourceIdentifier childResourceIdentifier = new ResourceIdentifier(resourceIdentifier, memberName, childResourceHierarchy);
							String childId = ((Constant) childExp).toString();
							Map.Entry<ResourceState, ResourceIdentifier> childInfo = createResourceState(childResourceIdentifier, jsonValue.getChild(2));
							state.addChildState(childId, childInfo.getKey());
							createInfo =  new AbstractMap.SimpleEntry<>(state, childInfo.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);
			}
			return new AbstractMap.SimpleEntry<>(state, resourceIdentifier);
		}
		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);
	}
}
