package generators;

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

import code.ast.CompilationUnit;
import code.ast.MethodDeclaration;
import code.ast.TypeDeclaration;
import code.ast.VariableDeclaration;
import models.Edge;
import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.Field;
import models.algebra.InvalidMessage;
import models.algebra.Parameter;
import models.algebra.ParameterizedIdentifierIsFutureWork;
import models.algebra.Position;
import models.algebra.Symbol;
import models.algebra.Term;
import models.algebra.Type;
import models.algebra.UnificationFailed;
import models.algebra.ValueUndefined;
import models.algebra.Variable;
import models.dataConstraintModel.Channel;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.JsonAccessor;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;
import models.dataConstraintModel.Selector;
import models.dataFlowModel.DataTransferModel;
import models.dataFlowModel.DataTransferChannel;
import models.dataFlowModel.DataTransferChannel.IResourceStateAccessor;
import models.dataFlowModel.PushPullAttribute;
import models.dataFlowModel.PushPullValue;
import models.dataFlowModel.ChannelNode;
import models.dataFlowModel.ResolvingMultipleDefinitionIsFutureWork;
import models.dataFlowModel.DataFlowEdge;
import models.dataFlowModel.DataFlowGraph;
import models.dataFlowModel.ResourceNode;
import models.dataFlowModel.StoreAttribute;

public class JavaMethodBodyGenerator {
	public static ArrayList<CompilationUnit> doGenerate(DataFlowGraph graph, DataTransferModel model, ArrayList<CompilationUnit> codes) {
		// Create a map from type names (lower case) to their types.
		Map<String, TypeDeclaration> componentMap = new HashMap<>();
		for (CompilationUnit code: codes) {
			for (TypeDeclaration component: code.types()) {
				componentMap.put(component.getTypeName(), component);
			}
		}
		
		// Generate the body of each update or getter method.
		try {
			Map<MethodDeclaration, Set<ResourcePath>> referredResources = new HashMap<>(); 
			for (Edge e: graph.getEdges()) {
				DataFlowEdge resToCh = (DataFlowEdge) e;
				if (!resToCh.isChannelToResource()) {
					PushPullAttribute pushPull = (PushPullAttribute) resToCh.getAttribute();
					ResourceNode src = (ResourceNode) resToCh.getSource();
					for (Edge chToRes: resToCh.getDestination().getOutEdges()) {
						ResourceNode dst = (ResourceNode) chToRes.getDestination();
						String srcResourceName = JavaCodeGenerator.getComponentName(src.getResourceHierarchy());
						String dstResourceName = JavaCodeGenerator.getComponentName(dst.getResourceHierarchy());
						TypeDeclaration srcComponent = componentMap.get(srcResourceName);
						TypeDeclaration dstComponent = componentMap.get(dstResourceName);
						DataTransferChannel ch = ((ChannelNode) resToCh.getDestination()).getChannel();
						for (ChannelMember out: ch.getOutputChannelMembers()) {
							if (dst.getInSideResources().contains(out.getResource())) {
								// Check if the input resource is outside of the channel scope.
								boolean outsideInputResource = false;
								for (ChannelMember cm: ch.getInputChannelMembers()) {
									if (src.getOutSideResources().contains(cm.getResource()) && cm.isOutside()) {
										outsideInputResource = true;	// Regarded as pull transfer.
										break;
									}
								}
								// Check if the output resource is outside of the channel scope.
								boolean outsideOutputResource = out.isOutside();
								if ((pushPull.getOptions().get(0) == PushPullValue.PUSH && !outsideInputResource) || outsideOutputResource) {
									// for push data transfer
									MethodDeclaration update = null;
									if (dstComponent == null) {
										String dstParentResourceName = JavaCodeGenerator.getComponentName(dst.getResourceHierarchy().getParent());
										dstComponent = componentMap.get(dstParentResourceName);
										update = getUpdateMethod(dstComponent, dstResourceName, srcResourceName);
									} else {
										update = getUpdateMethod(dstComponent, null, srcResourceName);
									}
									if (((StoreAttribute) dst.getAttribute()).isStored()) {
										// update stored state of dst side resource (when every incoming edge is in push style)
										Expression updateExp = null;
										if (ch.getReferenceChannelMembers().size() == 0) {
											updateExp = ch.deriveUpdateExpressionOf(out, JavaCodeGenerator.pushAccessor).getKey();
										} else {
											// if there exists one or more reference channel member.
											HashMap<ChannelMember, IResourceStateAccessor> inputResourceToStateAccessor = new HashMap<>();
											for (ChannelMember in: ch.getInputChannelMembers()) {
												inputResourceToStateAccessor.put(in, JavaCodeGenerator.pushAccessor);
											}
											for (ChannelMember c: ch.getReferenceChannelMembers()) {
												inputResourceToStateAccessor.put(c, JavaCodeGenerator.refAccessor);
											}
											updateExp = ch.deriveUpdateExpressionOf(out, JavaCodeGenerator.pushAccessor, inputResourceToStateAccessor).getKey();
										}
										// Replace Json constructor with a constructor of the child resource.
										ResourceHierarchy outRes = out.getResource().getResourceHierarchy();
										if (outRes.getChildren().size() == 1 && outRes.getChildren().iterator().next().getNumParameters() > 0) {
											ResourceHierarchy childRes = outRes.getChildren().iterator().next();
											Type childStateType = childRes.getResourceStateType();
											String childComponentName = JavaCodeGenerator.getComponentName(childRes);
											TypeDeclaration childComponent = componentMap.get(childComponentName);
											if (DataConstraintModel.typeJson.isAncestorOf(childStateType)) {
												replaceJsonTermWithConstructorInvocation(updateExp, childStateType, childComponentName, childComponent);
											}
										}
										// Add statements to the update method.
										String[] sideEffects = new String[] {""};
										String newState = updateExp.toImplementation(sideEffects);
										int numOfOutResourcesWithTheSameHierarchy = 0;
										for (ResourcePath outResPath: ch.getOutputResources()) {
											if (outResPath.getResourceHierarchy().equals(outRes)) {
												numOfOutResourcesWithTheSameHierarchy++;
											}
										}
										String updateStatement = "";
										if (JavaCodeGenerator.generatesComponent(outRes)) {
											if (updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect()) {
												updateStatement = sideEffects[0];
											} else {
												updateStatement = sideEffects[0] + "this.value = " + newState + ";";
											}
										} else {
											if (sideEffects[0] != null) {
												updateStatement = sideEffects[0];	
												updateStatement = updateStatement.replace(".value", "." + JavaCodeGenerator.toVariableName(JavaCodeGenerator.getComponentName(outRes)));
											}
											if (DataConstraintModel.typeList.isAncestorOf(outRes.getParent().getResourceStateType())) {
												Term selector = new Term(DataConstraintModel.set);
												selector.addChild(new Field("value"));
												selector.addChild(new Variable(update.getParameters().get(update.getParameters().size() - 2).getName()));
												selector.addChild(new Constant(newState));
												String[] sideEffects2 = new String[] {""};
												String newList = selector.toImplementation(sideEffects2);
												updateStatement += sideEffects2[0];
											} else if (DataConstraintModel.typeMap.isAncestorOf(outRes.getParent().getResourceStateType())) {
												Term selector = new Term(DataConstraintModel.insert);
												selector.addChild(new Field("value"));
												selector.addChild(new Variable(update.getParameters().get(update.getParameters().size() - 2).getName()));
												selector.addChild(new Constant(newState));
												String[] sideEffects2 = new String[] {""};
												String newMap = selector.toImplementation(sideEffects2);
												updateStatement += sideEffects2[0];
											} else if (!(updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect())) {
												updateStatement += "this." + JavaCodeGenerator.toVariableName(JavaCodeGenerator.getComponentName(outRes)) + " = " + newState + ";";
											}
										}
										if (numOfOutResourcesWithTheSameHierarchy == 1) {
											update.addFirstStatement(updateStatement);
										} else {
											Term conditions = null;
											int v = 1;
											Map<ChannelMember, Entry<ResourcePath, Set<ChannelMember>>> resourcePaths = ch.fillOutsideResourcePaths(out, JerseyCodeGenerator.pushAccessor);
											for (Expression pathParam: out.getResource().getPathParams()) {
												if (pathParam instanceof Variable) {
													String selfParamName = ((Variable) pathParam).getName();
													Expression arg = null;
													for (Selector selector: ch.getAllSelectors()) {
														if (selector.getExpression() instanceof Variable) {
															Variable selVar = (Variable) selector.getExpression();
															if (selVar.getName().equals(selfParamName)) {
																arg = selVar;
																break;
															}
														}
													}
													if (arg == null) {
														ResourcePath filledPath = resourcePaths.get(out).getKey();
														arg = filledPath.getPathParams().get(v - 1);
													}
													Term condition = new Term(DataConstraintModel.eq, new Expression[] {
															new Parameter("self" + (v > 1 ? v : "")), 
															arg});
													if (conditions == null) {
														conditions = condition;
													} else {
														conditions = new Term(DataConstraintModel.and, new Expression[] {
																conditions, 
																condition});
													}
												}
												v++;
											}
											String ifStatement = "if (" + conditions.toImplementation(new String[] {})+ ") {\n";
											update.addFirstStatement(ifStatement + "\t" + updateStatement.replace("\n", "\n\t") + "\n}");
										}
									}
									if (resToCh.getDestination().getIndegree() > 1
											|| (resToCh.getDestination().getIndegree() == 1 && ch.getInputChannelMembers().iterator().next().getStateTransition().isRightPartial())) {
										// update a cache of src side resource (when incoming edges are multiple)
										String cacheStatement = "this." + JavaCodeGenerator.toVariableName(srcResourceName) + " = " + JavaCodeGenerator.toVariableName(srcResourceName) + ";";
										if (update.getBody() == null || !update.getBody().getStatements().contains(cacheStatement)) {
											update.addStatement(cacheStatement);
										}								
									}
									if (((StoreAttribute) dst.getAttribute()).isStored()) {
										// returns the current state stored in a field.
										MethodDeclaration getter = null;
										if (JavaCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
											getter = getMethod(dstComponent, "getValue");
										} else {
											getter = getGetterMethod(dstComponent, dstResourceName);
										}
										if (getter.getBody() == null || getter.getBody().getStatements().size() == 0) {
											Type resourceType = dst.getResourceStateType();
											if (dst.getResourceHierarchy().getNumParameters() == 0) {
												if (JavaCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
													// dst has a component.
													if (model.isPrimitiveType(resourceType)) {
														getter.addStatement("return value;");
													} else {
														// copy the current state to be returned as a 'value'
														String implTypeName = resourceType.getImplementationTypeName();
														getter.addStatement("return new " + implTypeName + "(value);");
													}
												} else {
													// dst has no component.
													String dstResName = JavaCodeGenerator.toVariableName(JavaCodeGenerator.getComponentName(dst.getResourceHierarchy()));
													if (model.isPrimitiveType(resourceType)) {
														getter.addStatement("return " + dstResName + ";");
													} else {
														// copy the current state to be returned as a 'value'
														String implTypeName = resourceType.getImplementationTypeName();
														getter.addStatement("return new " + implTypeName + "(" + dstResName + ");");
													}
												}
											} else {
												String[] sideEffects = new String[] {""};
												if (DataConstraintModel.typeList.isAncestorOf(dst.getParent().getResourceStateType())) {
													Term selector = new Term(DataConstraintModel.get);
													selector.addChild(new Field("value"));
													selector.addChild(dst.getSelectors().get(dst.getSelectors().size() - 1).getExpression());
													String curState = selector.toImplementation(sideEffects);
													getter.addStatement(sideEffects[0] + "return " + curState + ";");
												} else if (DataConstraintModel.typeMap.isAncestorOf(dst.getParent().getResourceStateType())) {
													Term selector = new Term(DataConstraintModel.lookup);
													selector.addChild(new Field("value"));
													selector.addChild(dst.getSelectors().get(dst.getSelectors().size() - 1).getExpression());
													String curState = selector.toImplementation(sideEffects);
													getter.addStatement(sideEffects[0] + "return " + curState + ";");
												}
											}
										}
									}
									// src side (for a chain of update method invocations)
									ChannelMember in = null;
									for (ChannelMember cm: ch.getInputChannelMembers()) {
										if (src.getOutSideResources().contains(cm.getResource())) {
											in = cm;
											break;
										}
									}
									String srcResName = null;
									if (srcComponent == null) {
										String srcParentResourceName = JavaCodeGenerator.getComponentName(src.getResourceHierarchy().getParent());
										srcComponent = componentMap.get(srcParentResourceName);
										srcResName = srcResourceName;
									}
									for (MethodDeclaration srcUpdate: getUpdateMethods(srcComponent, srcResName)) {
										ResourcePath dstRes = out.getResource();
										// Values of path parameters.
										String pathParams = "";
										for (Expression pathParam: dstRes.getPathParams()) {
											if (pathParam instanceof Variable) {
												Variable pathVar = (Variable) pathParam;
												pathParams += pathVar.getName() + ", ";
											}
										}
										// Values of channel parameters.
										String chParams = "";
										for (Selector selector: ch.getAllSelectors()) {
											if (selector.getExpression() instanceof Variable) {
												Variable selVar = (Variable) selector.getExpression();
												chParams += selVar.getName() + ", ";
											}
										}
										String refParams = "";
										Set<ResourcePath> referredSet = referredResources.get(srcUpdate);
										for (ChannelMember rc: ch.getReferenceChannelMembers()) {
											// to get the value of reference member.
											ResourcePath ref = rc.getResource();
											if (referredSet == null) {
												referredSet = new HashSet<>();
												referredResources.put(srcUpdate, referredSet);
											}
											if (!dst.getInSideResources().contains(ref)) {
												String refVarName = ref.getLeafResourceName();
												if (!referredSet.contains(ref)) {
													referredSet.add(ref);
													Expression refGetter = JavaCodeGenerator.pullAccessor.getCurrentStateAccessorFor(rc, in);
													String[] sideEffects = new String[] {""};
													String refExp = refGetter.toImplementation(sideEffects);
													String refTypeName = ref.getResourceStateType().getInterfaceTypeName();
													srcUpdate.addFirstStatement(sideEffects[0] + refTypeName + " " + refVarName + " = " + refExp + ";");
												}
												refParams += ", " + refVarName;
											}
										}
										String updateMethodName = null;
										if (JavaCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
											updateMethodName = "updateFrom" + srcResourceName;
										} else {
											updateMethodName = "update" + dstResourceName + "From" + srcResourceName;
										}
										// Value of the source side (input side) resource.
										String srcFieldName = "value";
										if (!JavaCodeGenerator.generatesComponent(src.getResourceHierarchy())) {
											srcFieldName = JavaCodeGenerator.toVariableName(srcResourceName);
										}
										if (!outsideOutputResource) {
											// The destination resource is not outside.
											if (srcComponent != dstComponent) {
												srcUpdate.addStatement("this." + JavaCodeGenerator.toVariableName(dstComponent.getTypeName()) + "." + updateMethodName + "(" + pathParams + chParams + srcFieldName + refParams + ");");
											} else {
												srcUpdate.addStatement("this." + updateMethodName + "(" + pathParams + chParams + srcFieldName + refParams + ");");
											}
										} else {
											// Use the reference field to refer to outside destination resource.
											srcUpdate.addStatement("this." + JavaCodeGenerator.toVariableName(dstComponent.getTypeName()) + "." + updateMethodName + "(" + pathParams + chParams + srcFieldName + refParams + ");");											
										}
									}
									for (MethodDeclaration srcInput: getInputMethods(srcComponent, src, model)) {
										ResourcePath dstRes = out.getResource();
										// Values of path parameters.
										String pathParams = "";
										for (Expression pathParam: dstRes.getPathParams()) {
											if (pathParam instanceof Variable) {
												Variable pathVar = (Variable) pathParam;
												pathParams += pathVar.getName() + ", ";
											}
										}
										// Values of channel parameters.
										String chParams = "";
										for (Selector selector: ch.getAllSelectors()) {
											if (selector.getExpression() instanceof Variable) {
												Variable selVar = (Variable) selector.getExpression();
												chParams += selVar.getName() + ", ";
											}
										}
										String refParams = "";
										Set<ResourcePath> referredSet = referredResources.get(srcInput);
										for (ChannelMember rc: ch.getReferenceChannelMembers()) {
											// to get the value of reference member.
											ResourcePath ref = rc.getResource();
											if (referredSet == null) {
												referredSet = new HashSet<>();
												referredResources.put(srcInput, referredSet);
											}
											if (!dst.getInSideResources().contains(ref)) {
												String refVarName = ref.getLeafResourceName();
												if (!referredSet.contains(ref)) {
													referredSet.add(ref);
													Expression refGetter = JavaCodeGenerator.pullAccessor.getCurrentStateAccessorFor(rc, in);
													String[] sideEffects = new String[] {""};
													String refExp = refGetter.toImplementation(sideEffects);
													String refTypeName = ref.getResourceStateType().getInterfaceTypeName();
													srcInput.addFirstStatement(sideEffects[0] + refTypeName + " " + refVarName + " = " + refExp + ";");
												}
												refParams += ", " + refVarName;
											}
										}
										String updateMethodName = null;
										if (JavaCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
											updateMethodName = "updateFrom" + srcResourceName;
										} else {
											updateMethodName = "update" + dstResourceName + "From" + srcResourceName;
										}
										String srcFieldName = "value";
										if (!JavaCodeGenerator.generatesComponent(src.getResourceHierarchy())) {
											srcFieldName = JavaCodeGenerator.toVariableName(srcResourceName);
										}
										if (!outsideOutputResource) {
											// The destination resource is not outside.
											if (srcComponent != dstComponent) {
												srcInput.addStatement("this." + JavaCodeGenerator.toVariableName(dstComponent.getTypeName()) + "." + updateMethodName + "(" + pathParams + chParams + srcFieldName + refParams + ");");
											} else {
												srcInput.addStatement("this." + updateMethodName + "(" + pathParams + chParams + srcFieldName + refParams + ");");
											}
										} else {
											// Use the reference field to refer to outside destination resource.
											srcInput.addStatement("this." + JavaCodeGenerator.toVariableName(dstComponent.getTypeName()) + "." + updateMethodName + "(" + pathParams + chParams + srcFieldName + refParams + ");");
										}
									}
								} else if ((pushPull.getOptions().get(0) != PushPullValue.PUSH && !outsideOutputResource) || outsideInputResource) {
									// for pull (or push/pull) data transfer
									if (dstComponent == null) {
										String dstParentResourceName = JavaCodeGenerator.getComponentName(dst.getResourceHierarchy().getParent());
										dstComponent = componentMap.get(dstParentResourceName);
									}
									MethodDeclaration getter = null;
									if (JavaCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
										getter = getMethod(dstComponent, "getValue");
									} else {
										getter = getGetterMethod(dstComponent, dstResourceName);
									}
									if (getter.getBody() == null || getter.getBody().getStatements().size() == 0) {
										boolean isContainedPush = false;
										Map<ChannelMember, IResourceStateAccessor> inputResourceToStateAccessor = new HashMap<>();
										for (Edge chToRes2: dst.getInEdges()) {
											DataTransferChannel ch2 = ((ChannelNode) chToRes2.getSource()).getChannel();
											for (Edge resToCh2: chToRes2.getSource().getInEdges()) {
												DataFlowEdge dIn = (DataFlowEdge) resToCh2;
												ChannelMember in = null;
												for (ChannelMember cm: ch2.getInputChannelMembers()) {
													if (((ResourceNode) dIn.getSource()).getOutSideResources().contains(cm.getResource())) {
														in = cm;
														break;
													}
												}
												if (((PushPullAttribute) dIn.getAttribute()).getOptions().get(0) == PushPullValue.PUSH) {
													isContainedPush = true;
													inputResourceToStateAccessor.put(in, JavaCodeGenerator.pushAccessor);
												} else {
													inputResourceToStateAccessor.put(in, JavaCodeGenerator.pullAccessor);
												}
											}
										}
										// for reference channel members
										for (ChannelMember c: ch.getReferenceChannelMembers()) {
											inputResourceToStateAccessor.put(c, JavaCodeGenerator.pullAccessor);			// by pull data transfer
										}
										String[] sideEffects = new String[] {""};
										// generate a return statement.
										// An input resource is outside.
										if (!isContainedPush) {
											// All incoming edges are in PULL style.
											String curState = ch.deriveUpdateExpressionOf(out, JavaCodeGenerator.pullAccessor).getKey().toImplementation(sideEffects);
											getter.addStatement(sideEffects[0] + "return " + curState + ";");
										} else {
											// At least one incoming edge is in PUSH style.
											String curState = ch.deriveUpdateExpressionOf(out, JavaCodeGenerator.pullAccessor, inputResourceToStateAccessor).getKey().toImplementation(sideEffects);
											getter.addStatement(sideEffects[0] + "return " + curState + ";");
										}
									}
									if (outsideInputResource) {
										// Update fields to refer to outside resources.
										Map<ChannelMember, Entry<ResourcePath, Set<ChannelMember>>> resourcePaths = ch.fillOutsideResourcePaths(out, JavaCodeGenerator.pullAccessor);
										if (resourcePaths != null && resourcePaths.size() > 0) {
											for (ChannelMember outsideMember: resourcePaths.keySet()) {
												ResourcePath outsidePath = resourcePaths.get(outsideMember).getKey();
												if (!JavaCodeGenerator.generatesComponent(outsidePath.getResourceHierarchy())) {
													outsidePath = outsidePath.getParent();
												}
												String outsideResName = JavaCodeGenerator.toVariableName(JavaCodeGenerator.getComponentName(outsidePath.getResourceHierarchy()));
												Set<ChannelMember> dependingMembers = resourcePaths.get(outsideMember).getValue();
												for (ChannelMember dependingMember: dependingMembers) {
													ResourcePath dependingRes = dependingMember.getResource();
													ResourceNode dependingNode = null;
													PushPullAttribute pushPull2 = null;
													for (Edge resToCh2: resToCh.getDestination().getInEdges()) {
														if (((ResourceNode) resToCh2.getSource()).getOutSideResources().contains(dependingRes)) {
															dependingNode = (ResourceNode) resToCh2.getSource();
															pushPull2 = (PushPullAttribute) resToCh.getAttribute();
														}
													}
													TypeDeclaration dependingComponent = null;
													if (JavaCodeGenerator.generatesComponent(dependingRes.getResourceHierarchy())) {
														String dependingResourceName = JavaCodeGenerator.getComponentName(dependingRes.getResourceHierarchy());
														dependingComponent = componentMap.get(dependingResourceName);
													} else {
														String dependingParentResourceName = JavaCodeGenerator.getComponentName(dependingRes.getParent().getResourceHierarchy());
														dependingComponent = componentMap.get(dependingParentResourceName);
													}
													Expression outsideExp = JavaCodeGenerator.pullAccessor.getDirectStateAccessorFor(outsidePath, null);
													if (JavaCodeGenerator.generatesComponent(outsidePath.getResourceHierarchy())) {
														outsideExp = ((Term) outsideExp).getChild(0);
													}
													Expression nextExp = dependingMember.getStateTransition().getNextStateExpression();
													if (nextExp != null && outsideExp instanceof Term) {
														if (nextExp instanceof Variable) {
															outsideExp = ((Term) outsideExp).substitute((Variable) nextExp, new Field(JavaCodeGenerator.toVariableName(JavaCodeGenerator.getComponentName(dependingRes.getResourceHierarchy())))); 
														} else {
															// ToDo.
														}
													}
													if (dstComponent == dependingComponent) {
														// In the common parent.
														if (dependingNode != null) {															// Inspect further dependency.
															for (Edge chToRes2: dependingNode.getInEdges()) {
																DataTransferChannel ch2 = ((ChannelNode) chToRes2.getSource()).getChannel();
																if (ch2.getInputChannelMembers().size() == 0) {
																	// In an input method of the parent component.
																	Set<ChannelMember> outs = ch2.getOutputChannelMembers();
																	MethodDeclaration input = getInputMethod(dependingComponent, outs.iterator().next(), outs.size());
																	String[] sideEffects = new String[] {""};
																	String outsideAccessor = outsideExp.toImplementation(sideEffects);
																	input.addStatement("this." + outsideResName + " = " + outsideAccessor + ";");		// change the reference field.
																	// Update constructor.
																	MethodDeclaration constructor = getConstructor(dependingComponent);
																	constructor.addStatement("this." + outsideResName + " = " + outsideAccessor + ";");		// initialize the reference field.
																} else {
																	boolean isPush = true;
																	for (Edge resToCh2: chToRes2.getSource().getInEdges()) {
																		if (((PushPullAttribute) resToCh2.getAttribute()).getOptions().get(0) != PushPullValue.PUSH) {
																			isPush = false;
																			break;
																		}
																	}
																	if (isPush) {
																		String[] sideEffects = new String[] {""};
																		String outsideAccessor = outsideExp.toImplementation(sideEffects);
																		for (Edge resToCh2: chToRes2.getSource().getInEdges()) {
																			// In an update method of the parent component.
																			ResourceNode dependingResSrc = (ResourceNode) resToCh2.getSource();
																			MethodDeclaration update = null;
																			if (JavaCodeGenerator.generatesComponent(dependingRes.getResourceHierarchy())) {
																				update = getUpdateMethod(dependingComponent, null, JavaCodeGenerator.getComponentName(dependingResSrc.getResourceHierarchy()));
																			} else {
																				String dependingResName = JavaCodeGenerator.getComponentName(dependingRes.getResourceHierarchy());
																				update = getUpdateMethod(dependingComponent, dependingResName, JavaCodeGenerator.getComponentName(dependingResSrc.getResourceHierarchy()));																				
																			}
																			update.addStatement("this." + outsideResName + " = " + outsideAccessor + ";");		// change the reference field.
																		}
																		// Update constructor.
																		MethodDeclaration constructor = getConstructor(dependingComponent);
																		constructor.addStatement("this." + outsideResName + " = " + outsideAccessor + ";");		// initialize the reference field.
																	}
																}
															}
														}
													} else {
														if (pushPull2.getOptions().get(0) == PushPullValue.PUSH) {
															// In an update method of the destination component.
															MethodDeclaration update = null;
															if (JavaCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
																update = getUpdateMethod(dstComponent, null, JavaCodeGenerator.getComponentName(dependingRes.getResourceHierarchy()));
															} else {
																String dstResName = JavaCodeGenerator.getComponentName(dst.getResourceHierarchy());
																update = getUpdateMethod(dstComponent, dstResName, JavaCodeGenerator.getComponentName(dependingRes.getResourceHierarchy()));
															}
															String[] sideEffects = new String[] {""};
															String outsideAccessor = outsideExp.toImplementation(sideEffects);
															update.addStatement("this." + outsideResName + " = " + outsideAccessor + ";");		// change the reference field.
															// Update constructor.
															MethodDeclaration constructor = getConstructor(dstComponent);
															constructor.addStatement("this." + outsideResName + " = " + outsideAccessor + ";");		// initialize the reference field.
														}
													}
												}
											}
										}										
									}
								}
							}
						}
					}
				}
			}
			// for source nodes
			TypeDeclaration mainComponent = componentMap.get(JavaCodeGenerator.mainTypeName);
			for (ResourceHierarchy resource: model.getResourceHierarchies()) {
//				ResourceNode resource = (ResourceNode) n;
				String resourceName = JavaCodeGenerator.getComponentName(resource);
				TypeDeclaration component = componentMap.get(resourceName);
				if (component != null) {
					// state getter method
					Type resourceType = JavaCodeGenerator.getImplStateType(resource);
					MethodDeclaration stateGetter = getMethod(component, "getValue");
					if (stateGetter != null && (stateGetter.getBody() == null || stateGetter.getBody().getStatements().size() == 0)) {
						if (model.isPrimitiveType(resourceType)) {
							// primitive type
							stateGetter.addStatement("return value;");							
						} else {
							if (resource.getChildren() != null && resource.getChildren().size() == 1 && resource.getChildren().iterator().next().getNumParameters() > 0) {
								// list or map
								String implTypeName = resourceType.getImplementationTypeName();
								// copy the current state to be returned as a 'value'
								stateGetter.addStatement("return new " + implTypeName + "(value);");
							} else {
								if (resource.getChildren() == null || resource.getChildren().size() == 0) {
									// a leaf resource
									String implTypeName = resourceType.getImplementationTypeName();
									stateGetter.addStatement("return new " + implTypeName + "(value);");
								} else {
									Term composer = null; 
									Term composerSub = new Constant(DataConstraintModel.nil);
									composerSub.setType(DataConstraintModel.typeMap);
									for (ResourceHierarchy child: resource.getChildren()) {
										String childTypeName = JavaCodeGenerator.getComponentName(child);
										String fieldName = JavaCodeGenerator.toVariableName(childTypeName);
										Term childGetter = null; 
										if ((child.getChildren() == null || child.getChildren().size() == 0) && child.getNumParameters() == 0) {
											// the child is not a class
											childGetter = new Term(new Symbol("get" + childTypeName, 1, Symbol.Type.METHOD));
											childGetter.addChild(new Constant("this"));
										} else {
											// the child is a class
											childGetter = new Term(new Symbol("getValue", 1, Symbol.Type.METHOD));
											childGetter.addChild(new Field(fieldName, JavaCodeGenerator.getImplStateType(child)));
										}
										composer = new Term(DataConstraintModel.insert);
										composer.addChild(composerSub);
										composer.addChild(new Constant("\"" + fieldName + "\"", DataConstraintModel.typeString));	// key
										composer.addChild(childGetter);																// value
										composer.setType(DataConstraintModel.typeMap);
										composerSub = composer;
									}
									composer.setType(stateGetter.getReturnType());
									String[] sideEffects = new String[] {null};
									String returnValue = composer.toImplementation(sideEffects);
									if (sideEffects[0] != null) {
										stateGetter.addStatement(sideEffects[0] + "return " + returnValue+ ";");
									} else {
										stateGetter.addStatement("return " + returnValue+ ";");
									}
								}
							}
						}
					}
					
					// child getter method
					if (resource.getChildren().size() > 0) {
						for (ResourceHierarchy child: resource.getChildren()) {
							String methodName = "get" + JavaCodeGenerator.getComponentName(child);
							MethodDeclaration childGetter = getMethod(component, methodName);
							if (childGetter != null && (childGetter.getBody() == null || childGetter.getBody().getStatements().size() == 0)) {
								if (DataConstraintModel.typeList.isAncestorOf(resource.getResourceStateType())) {
									Term selector = new Term(DataConstraintModel.get);
									selector.addChild(new Field("value"));
									selector.addChild(new Variable(childGetter.getParameters().get(childGetter.getParameters().size() - 1).getName()));
									selector.setType(childGetter.getReturnType());
									String[] sideEffects = new String[] {null};
									String returnValue = selector.toImplementation(sideEffects);
									if (sideEffects[0] != null) childGetter.addStatement(sideEffects[0]);
									childGetter.addStatement("return " + returnValue + ";");
								} else if (DataConstraintModel.typeMap.isAncestorOf(resource.getResourceStateType())) {
									Term selector = new Term(DataConstraintModel.lookup);
									selector.addChild(new Field("value"));
									selector.addChild(new Variable(childGetter.getParameters().get(childGetter.getParameters().size() - 1).getName()));
									selector.setType(childGetter.getReturnType());
									String[] sideEffects = new String[] {null};
									String returnValue = selector.toImplementation(sideEffects);
									if (sideEffects[0] != null) childGetter.addStatement(sideEffects[0]);
									childGetter.addStatement("return " + returnValue+ ";");
								} else {
									String fieldName = JavaCodeGenerator.getComponentName(child);
									String returnValue = JavaCodeGenerator.toVariableName(fieldName);
									childGetter.addStatement("return this." + returnValue + ";");
								}
							}
						}
					}
				}
				
				// methods for input events
				Map<DataTransferChannel, Set<ChannelMember>> ioChannelsAndMembers = getIOChannelsAndMembers(resource, model);
				for (Map.Entry<DataTransferChannel, Set<ChannelMember>> entry: ioChannelsAndMembers.entrySet()) {
					Set<ChannelMember> outs = entry.getValue();
					DataTransferChannel ch = entry.getKey();
					for (ChannelMember out: outs) {
						MethodDeclaration input = null;
						if (JavaCodeGenerator.generatesComponent(resource)) {
							// A component is generated for this resource.
							input = getInputMethod(component, out, ch.getOutputChannelMembers().size());
						} else {
							// No component is generated for this resource.
							ResourceHierarchy parent = resource.getParent();
							if (parent != null) {
								TypeDeclaration parentType = componentMap.get(JavaCodeGenerator.getComponentName(parent));
								input = getInputMethod(parentType, out, ch.getOutputChannelMembers().size());
							}
						}
						if (input != null) {
							// In each resource
							Expression updateExp = ch.deriveUpdateExpressionOf(out, JavaCodeGenerator.refAccessor).getKey();
							// Replace Json constructor with a constructor of the child resource.
							ResourceHierarchy outRes = out.getResource().getResourceHierarchy();
							if (outRes.getChildren().size() == 1 && outRes.getChildren().iterator().next().getNumParameters() > 0) {
								ResourceHierarchy childRes = outRes.getChildren().iterator().next();
								Type childStateType = childRes.getResourceStateType();
								String childComponentName = JavaCodeGenerator.getComponentName(childRes);
								TypeDeclaration childComponent = componentMap.get(childComponentName);
								if (DataConstraintModel.typeJson.isAncestorOf(childStateType)) {
									replaceJsonTermWithConstructorInvocation(updateExp, childStateType, childComponentName, childComponent);
								}
							}
							// Add statements to the input method.
							String[] sideEffects = new String[] {""};
							String newState = updateExp.toImplementation(sideEffects);
							if (JavaCodeGenerator.generatesComponent(resource)) {
								String updateStatement;
								if (updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect()) {
									updateStatement = sideEffects[0];	
								} else {
									updateStatement = sideEffects[0] + "this.value = " + newState + ";";
								}
								if (input.getBody() == null || !input.getBody().getStatements().contains(updateStatement)) {
									input.addFirstStatement(updateStatement);
								}
							} else {
								String updateStatement = "";
								if (sideEffects[0] != null) {
									updateStatement = sideEffects[0];	
									updateStatement = updateStatement.replace(".value", "." + JavaCodeGenerator.toVariableName(JavaCodeGenerator.getComponentName(resource)));
								}
								if (DataConstraintModel.typeList.isAncestorOf(resource.getParent().getResourceStateType())) {
									Term selector = new Term(DataConstraintModel.set);
									selector.addChild(new Field("value"));
									selector.addChild(new Variable(input.getParameters().get(input.getParameters().size() - 2).getName()));
									selector.addChild(new Constant(newState));
									String[] sideEffects2 = new String[] {""};
									String newList = selector.toImplementation(sideEffects2);
									updateStatement += sideEffects2[0];
								} else if (DataConstraintModel.typeMap.isAncestorOf(resource.getParent().getResourceStateType())) {
									Term selector = new Term(DataConstraintModel.insert);
									selector.addChild(new Field("value"));
									selector.addChild(new Variable(input.getParameters().get(input.getParameters().size() - 2).getName()));
									selector.addChild(new Constant(newState));
									String[] sideEffects2 = new String[] {""};
									String newMap = selector.toImplementation(sideEffects2);
									updateStatement += sideEffects2[0];
								} else if (!(updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect())) {
									updateStatement += "this." + JavaCodeGenerator.toVariableName(JavaCodeGenerator.getComponentName(resource)) + " = " + newState + ";";
								}
								if (updateStatement != null && (input.getBody() == null || !input.getBody().getStatements().contains(updateStatement))) {
									input.addFirstStatement(updateStatement);
								}
							}
							
							// In the main type
							if (mainComponent != null) {
								Expression message = out.getStateTransition().getMessageExpression();
								String inputAccessorName = input.getName();
								if (message instanceof Term) {
									inputAccessorName = ((Term) message).getSymbol().getImplName();
								} else if (message instanceof Variable) {
									inputAccessorName = ((Variable) message).getName();
								}
								MethodDeclaration inputAccessor = getMethod(mainComponent, inputAccessorName);
								if (inputAccessor != null) {
									Set<ResourcePath> referredSet = referredResources.get(input);
									for (ChannelMember rc: ch.getReferenceChannelMembers()) {
										// For each reference channel member, get the current state of the reference side resource by pull data transfer.
										ResourcePath ref = rc.getResource();
										if (referredSet == null) {
											referredSet = new HashSet<>();
											referredResources.put(input, referredSet);
										}
										if (!out.getResource().equals(ref)) {
											String refVarName = ref.getLeafResourceName();
											if (!referredSet.contains(ref)) {
												referredSet.add(ref);
												Expression refGetter = JavaCodeGenerator.pullAccessor.getDirectStateAccessorFor(ref, null);
												sideEffects = new String[] {""};
												String refExp = refGetter.toImplementation(sideEffects);
												String refTypeName = ref.getResourceStateType().getInterfaceTypeName();
												inputAccessor.addFirstStatement(sideEffects[0] + refTypeName + " " + refVarName + " = " + refExp + ";");
											}
										}
									}
									Expression resExp = JavaCodeGenerator.pullAccessor.getDirectStateAccessorFor(out.getResource(), null);
									List<String> args = new ArrayList<>();
									if (resExp instanceof Term) {
										// to access the parent
										if (((Term) resExp).getChildren().size() > 1 && ((Term) resExp).getChild(1) instanceof Variable) {
											args.add(((Variable)((Term) resExp).getChild(1)).getName());
										}
										resExp = ((Term) resExp).getChild(0);
									}
									String resourceAccess = resExp.toImplementation(new String[] {null});
									// Values of channel parameters.
									for (Selector selector: ch.getAllSelectors()) {
										if (selector.getExpression() instanceof Variable) {
											Variable selVar = (Variable) selector.getExpression();
											if (!args.contains(selVar.getName())) {
												args.add(selVar.getName());
											}
										}
									}
									// Values of message parameters.
									if (message instanceof Term) {
										for (Map.Entry<Position, Variable> varEnt: message.getVariables().entrySet()) {
											String refVarName = null;
											for (ChannelMember rc: ch.getReferenceChannelMembers()) {
												Expression varExp = rc.getStateTransition().getMessageExpression().getSubTerm(varEnt.getKey());
												if (varExp != null && rc.getStateTransition().getCurStateExpression().contains(varExp)) {
													refVarName = rc.getResource().getLeafResourceName();
													break;
												}
											}
											if (refVarName != null) {
												if (!args.contains(refVarName)) {
													args.add(refVarName);
												}
											} else {
												if (!args.contains(varEnt.getValue().getName())) {
													args.add(varEnt.getValue().getName());
												}
											}
										}
									}
									String argsStr = "";
									String delimiter = "";
									for (String arg: args) {
										argsStr += delimiter + arg;
										delimiter = ", ";
									}
									inputAccessor.addStatement(resourceAccess + "." + input.getName() + "(" + argsStr + ");");
								}
							}
						}
					}
				}
			}
		} catch (ParameterizedIdentifierIsFutureWork | ResolvingMultipleDefinitionIsFutureWork
				| InvalidMessage | UnificationFailed | ValueUndefined e1) {
			e1.printStackTrace();
		}
		return codes;
	}

	private static void replaceJsonTermWithConstructorInvocation(Expression exp, Type replacedJsonType, String replacingClassName, TypeDeclaration childComponent) {
		Map<Position, Term> subTerms = ((Term) exp).getSubTerms(Term.class);
		Iterator<Entry<Position, Term>> termEntItr = subTerms.entrySet().iterator();
		while (termEntItr.hasNext()) {
			Entry<Position, Term> termEnt = termEntItr.next();
			Term jsonTerm = termEnt.getValue();
			if (jsonTerm.getType().equals(replacedJsonType)) {
				String constructorInvocation = "new " + replacingClassName + "(";
				MethodDeclaration childConstructor = getConstructor(childComponent);
				String delimiter = "";
				for (VariableDeclaration var: childConstructor.getParameters()) {
					JsonAccessor jsonMember = new JsonAccessor(DataConstraintModel.dot);
					jsonMember.addChild(jsonTerm);
					jsonMember.addChild(new Constant("\"" + var.getName() + "\""));
					Expression param = jsonMember.reduce();
					if (param != null) {
						if (param instanceof Term) {
							if (((Term) param).getType() == null) {
								((Term) param).setType(var.getType());
							}
						} else if (param instanceof Variable) {
							if (((Variable) param).getType() == null) {
								((Variable) param).setType(var.getType());
							}
						}
						constructorInvocation = constructorInvocation + delimiter + param.toImplementation(null);
					} else {
						constructorInvocation = constructorInvocation + delimiter + var.getName();
					}
					delimiter = ", ";
				}
				constructorInvocation += ")";
				((Term) exp).replaceSubTerm(termEnt.getKey(), new Constant(constructorInvocation));
				subTerms = ((Term) exp).getSubTerms(Term.class);
				termEntItr = subTerms.entrySet().iterator();
			}
		}
	}

	private static MethodDeclaration getConstructor(TypeDeclaration component) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.isConstructor()) return m;
		}
		return null;
	}

	private static MethodDeclaration getUpdateMethod(TypeDeclaration component, String dstResName, String srcResName) {
		for (MethodDeclaration m: component.getMethods()) {
			if (dstResName == null) {
				if (m.getName().equals("updateFrom" + srcResName)) return m;
			} else {
				if (m.getName().equals("update" + dstResName + "From" + srcResName)) return m;
			}
		}
		return null;
	}

	private static List<MethodDeclaration> getUpdateMethods(TypeDeclaration component, String resName) {
		List<MethodDeclaration> updates = new ArrayList<>();
		for (MethodDeclaration m: component.getMethods()) {
			if (resName == null) {
				if (m.getName().startsWith("updateFrom")) {
					updates.add(m);
				}
			} else {
				if (m.getName().startsWith("update" + resName + "From")) {
					updates.add(m);
				}
			}
		}
		return updates;
	}
	
	private static MethodDeclaration getGetterMethod(TypeDeclaration component, String resourceName) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.getName().startsWith("get" + resourceName)) return m;
		}
		return null;
	}
	
	private static Map<DataTransferChannel, Set<ChannelMember>> getIOChannelsAndMembers(ResourceHierarchy resource, DataTransferModel model) {
		Map<DataTransferChannel, Set<ChannelMember>> ioChannelsAndMembers = new HashMap<>();
		for (Channel c: model.getInputChannels()) {
			DataTransferChannel ch = (DataTransferChannel) c;
			// I/O channel
			for (ChannelMember out: ch.getOutputChannelMembers()) {
				if (resource.equals(out.getResource().getResourceHierarchy())) {
					if (out.getStateTransition().getMessageExpression() instanceof Term || out.getStateTransition().getMessageExpression() instanceof Variable) {
						Set<ChannelMember> channelMembers = ioChannelsAndMembers.get(ch);
						if (channelMembers == null) {
							channelMembers = new HashSet<>();
							ioChannelsAndMembers.put(ch, channelMembers);
						}
						channelMembers.add(out);
					}
				}
			}
		}
		return ioChannelsAndMembers;
	}

	private static List<MethodDeclaration> getInputMethods(TypeDeclaration component, ResourceNode resource, DataTransferModel model) {
		List<MethodDeclaration> inputs = new ArrayList<>();
		for (Channel c: model.getInputChannels()) {
			DataTransferChannel channel = (DataTransferChannel) c;
			// I/O channel
			for (ChannelMember out: channel.getOutputChannelMembers()) {
				if (resource.getInSideResources().contains(out.getResource())) {
					MethodDeclaration input = getInputMethod(component, out, channel.getOutputChannelMembers().size());
					inputs.add(input);
				}
			}
		}
		return inputs;
	}

	private static List<MethodDeclaration> getInputMethods(TypeDeclaration component, ResourceHierarchy resource, DataTransferModel model) {
		List<MethodDeclaration> inputs = new ArrayList<>();
		for (Channel c: model.getInputChannels()) {
			DataTransferChannel channel = (DataTransferChannel) c;
			// I/O channel
			for (ChannelMember out: channel.getOutputChannelMembers()) {
				if (resource.equals(out.getResource().getResourceHierarchy())) {
					MethodDeclaration input = getInputMethod(component, out, channel.getOutputChannelMembers().size());
					inputs.add(input);
				}
			}
		}
		return inputs;
	}

	private static MethodDeclaration getInputMethod(TypeDeclaration component, ChannelMember cm, int outNumber) {
		String inputMethodName = null;
		if (cm.getStateTransition().getMessageExpression() instanceof Term) {
			Term message = (Term) cm.getStateTransition().getMessageExpression();
			inputMethodName =message.getSymbol().getImplName();
		} else if (cm.getStateTransition().getMessageExpression() instanceof Variable) {
			Variable message = (Variable) cm.getStateTransition().getMessageExpression();
			inputMethodName = message.getName();
		}
		if (outNumber > 1) {
			inputMethodName += "For" + JavaCodeGenerator.getComponentName(cm.getResource().getResourceHierarchy());
		}
		MethodDeclaration input = getMethod(component, inputMethodName);
		return input;
	}

	private static MethodDeclaration getMethod(TypeDeclaration component, String methodName) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.getName().equals(methodName)) return m;
		}
		return null;
	}
}
