package generators;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import code.ast.Block;
import code.ast.CompilationUnit;
import code.ast.FieldDeclaration;
import code.ast.MethodDeclaration;
import code.ast.TypeDeclaration;
import code.ast.VariableDeclaration;
import models.Edge;
import models.Node;
import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.Field;
import models.algebra.Parameter;
import models.algebra.Symbol;
import models.algebra.Term;
import models.algebra.Type;
import models.algebra.Variable;
import models.dataConstraintModel.Channel;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;
import models.dataConstraintModel.Selector;
import models.dataFlowModel.ChannelNode;
import models.dataFlowModel.DataFlowEdge;
import models.dataFlowModel.DataFlowGraph;
import models.dataFlowModel.DataTransferChannel;
import models.dataFlowModel.DataTransferModel;
import models.dataFlowModel.PushPullAttribute;
import models.dataFlowModel.PushPullValue;
import models.dataFlowModel.ResourceNode;
import models.dataFlowModel.DataTransferChannel.IResourceStateAccessor;

/**
 * Common generator for prototypes
 * 
 * @author Nitta
 *
 */
public abstract class CodeGenerator {
	public static final String fieldOfResourceState = "value";
	public static final String getterPrefix = "get";
	public static final String getterOfResourceState = "getValue";
	public static final String updateMethodPrefix = "update";
	public static final String from = "From";
	public static final String _for = "For";
	private static String mainTypeName = null;
	private static ILanguageSpecific langSpec = null;
		
	public static String getMainTypeName() {
		return mainTypeName;
	}

	public static void setMainTypeName(String mainTypeName) {
		CodeGenerator.mainTypeName = mainTypeName;
	}
	
	public static void resetMainTypeName() {
		CodeGenerator.mainTypeName = null;
	}

	public static String getComponentName(ResourceHierarchy res, ILanguageSpecific langSpec) {
		String name = res.getResourceName();
		if (res.getNumParameters() > 0) {
			if (name.length() > 3 && name.endsWith("ies")) {
				name = name.substring(0, name.length() - 3) + "y";
			} else if (name.length() > 1 && name.endsWith("s")) {
				name = name.substring(0, name.length() - 1);
			} else {
				name += "Element";
			}
		}
		return langSpec.toComponentName(name);
	}
	
	public static Type getImplStateType(ResourceHierarchy res, ILanguageSpecific langSpec) {
		Set<ResourceHierarchy> children = res.getChildren();
		if (children == null || children.size() == 0) {
			// leaf resource.
			return res.getResourceStateType();
		} else {
			ResourceHierarchy child = children.iterator().next();
			if (children.size() == 1 && child.getNumParameters() > 0) {
				// map or list.
				if (DataConstraintModel.typeList.isAncestorOf(res.getResourceStateType())) {
					// list.
					if (generatesComponent(child)) {
						return langSpec.newListType(getComponentName(child, langSpec));
					} else {
						return langSpec.newListType(getImplStateType(child, langSpec).getImplementationTypeName());
					}
				} else if (DataConstraintModel.typeMap.isAncestorOf(res.getResourceStateType())) {
					// map.
					if (generatesComponent(child)) {
						return langSpec.newMapType(DataConstraintModel.typeString, getComponentName(child, langSpec));
					} else {
						return langSpec.newMapType(DataConstraintModel.typeString, getImplStateType(child, langSpec).getImplementationTypeName());
					}
				}
				return null;
			} else {
				// class
				return res.getResourceStateType();
			}
		}
	}
	
	public static boolean generatesComponent(ResourceHierarchy res) {
		return res.getParent() == null || !(res.getChildren() == null || res.getChildren().size() == 0);
//		Type resType = res.getResourceStateType();
//		return DataConstraintModel.typeJson.isAncestorOf(resType) || DataConstraintModel.typeList.isAncestorOf(resType) || DataConstraintModel.typeMap.isAncestorOf(resType);
	}

	/**
	 * Generate source codes in specified language from data-flow/control-flow graph.
	 * 
	 * @param model architecture model
	 * @param flowGraph data-flow or control-flow graph
	 * @param langSpec specified language
	 * @return source codes
	 */
	public ArrayList<CompilationUnit> generateCode(DataTransferModel model, DataFlowGraph flowGraph, ILanguageSpecific langSpec) {
		CodeGenerator.langSpec = langSpec;
		ArrayList<CompilationUnit> codes = new ArrayList<>();
		
		// Get the dependency among root nodes.
		Map<ResourceHierarchy, Set<ResourceHierarchy>> dependedRootComponentGraph = getDependedRootComponentGraph(model);
		
		// Sort the all components.
		ArrayList<ResourceNode> components = determineComponentOrder(flowGraph, dependedRootComponentGraph);
		
		// Add the main component.
		if (mainTypeName == null) {
			mainTypeName = langSpec.getMainComponentName();
		}
		TypeDeclaration mainComponent = langSpec.newTypeDeclaration(mainTypeName);
		MethodDeclaration mainConstructor = mainComponent.createConstructor();
		CompilationUnit mainCU = langSpec.newCompilationUnit(mainComponent);
		codes.add(mainCU);
		
		// Generate the other components.
		generateCodeFromFlowGraph(model, flowGraph, components, dependedRootComponentGraph, mainComponent, mainConstructor, codes, langSpec);
		
		return codes;
	}
	
	public abstract void generateCodeFromFlowGraph(DataTransferModel model, DataFlowGraph flowGraph, ArrayList<ResourceNode> components, Map<ResourceHierarchy, Set<ResourceHierarchy>> dependedRootComponentGraph, 
			TypeDeclaration mainComponent, MethodDeclaration mainConstructor, ArrayList<CompilationUnit> codes, ILanguageSpecific langSpec);
	
	private static Map<ResourceHierarchy, Set<ResourceHierarchy>> getDependedRootComponentGraph(DataTransferModel model) {
		Map<ResourceHierarchy, Set<ResourceHierarchy>> dependedComponentGraph = new HashMap<>();
		for (Channel ch: model.getChannels()) {
			DataTransferChannel dtCh = (DataTransferChannel) ch;
			Set<ResourceHierarchy> inRes = new HashSet<>();
			Set<ResourceHierarchy> outRes = new HashSet<>();
			for (ChannelMember cm: dtCh.getChannelMembers()) {
				if (cm.isOutside()) {
					outRes.add(cm.getResource().getResourceHierarchy());
				} else {
					inRes.add(cm.getResource().getResourceHierarchy());
				}
			}
			if (outRes.size() > 0 && inRes.size() > 0) {
				for (ResourceHierarchy out: outRes) {
					for (ResourceHierarchy in: inRes) {
						Set<ResourceHierarchy> dependings = dependedComponentGraph.get(out.getRoot());
						if (dependings == null) {
							dependings = new HashSet<>();
							dependedComponentGraph.put(out.getRoot(), dependings);
						}
						dependings.add(in.getRoot());
					}
				}
			}
		}
		return dependedComponentGraph;
	}
	
	private static ArrayList<ResourceNode> determineComponentOrder(DataFlowGraph graph, Map<ResourceHierarchy, Set<ResourceHierarchy>> dependedRootComponentGraph) {
		ArrayList<ResourceNode> objects = new ArrayList<>();
		Set<ResourceNode> visited = new HashSet<>();
		Collection<ResourceNode> allNodes = graph.getResourceNodes();
		for (ResourceNode resNode: allNodes) {
			topologicalSort(resNode, allNodes, dependedRootComponentGraph, visited, objects);
		}
		return objects;
	}
	
	private static void topologicalSort(ResourceNode curResNode, Collection<ResourceNode> allNodes, Map<ResourceHierarchy, Set<ResourceHierarchy>> dependedRootComponentGraph, Set<ResourceNode> visited, List<ResourceNode> orderedList) {
		if (visited.contains(curResNode)) return;
		visited.add(curResNode);
		// A caller is before the callee
		
		// For each incoming PUSH transfer.		
		for (Edge chToRes: curResNode.getInEdges()) {
			for (Edge resToCh: chToRes.getSource().getInEdges()) {
				if (!(resToCh instanceof DataFlowEdge) || ((PushPullAttribute)((DataFlowEdge) resToCh).getAttribute()).getOptions().get(0) == PushPullValue.PUSH) {
					topologicalSort((ResourceNode) resToCh.getSource(), allNodes, dependedRootComponentGraph, visited, orderedList);
				}
			}
		}
		// For each outgoing PULL transfer.
		if (curResNode instanceof ResourceNode) {
			for (Edge resToCh: curResNode.getOutEdges()) {
				DataFlowEdge de = (DataFlowEdge) resToCh;
				if (((PushPullAttribute) de.getAttribute()).getOptions().get(0) != PushPullValue.PUSH) {
					for (Edge chToRes : resToCh.getDestination().getOutEdges()) {
						topologicalSort((ResourceNode) chToRes.getDestination(), allNodes, dependedRootComponentGraph, visited, orderedList);
					}
				}
			}
		}
		// For each depending root node.
		if (curResNode instanceof ResourceNode && dependedRootComponentGraph.get(curResNode.getResourceHierarchy()) != null) {
			for (ResourceHierarchy dependingRes: dependedRootComponentGraph.get(curResNode.getResourceHierarchy())) {
				for (ResourceNode rootNode: allNodes) {
					ResourceHierarchy rootRes = rootNode.getResourceHierarchy();
					if (rootRes.getParent() == null && rootRes.equals(dependingRes)) {
						topologicalSort(rootNode, allNodes, dependedRootComponentGraph, visited, orderedList);
					}
				}
			}
		}
		// For each reference resource.
		ResourceNode cn = null;
		if (curResNode instanceof ResourceNode) {
			cn = (ResourceNode) curResNode;
		}
		if (cn != null) {
			for (Node n: allNodes) {
				ResourceNode resNode = null;
				if (n instanceof ResourceNode) {
					resNode = (ResourceNode) n;
				}
				if (resNode != null) {
					for (Edge resToCh: resNode.getOutEdges()) {
						ChannelNode chNode = (ChannelNode) resToCh.getDestination();
						for (ChannelMember m: chNode.getChannel().getReferenceChannelMembers()) {
							if (curResNode.getOutSideResources().contains(m.getResource())) {
								topologicalSort(resNode, allNodes, dependedRootComponentGraph, visited, orderedList);
							}
						}
					}
				}
			}
		}
		orderedList.add(0, curResNode);
	}

	protected void updateMainComponent(TypeDeclaration mainType, MethodDeclaration mainConstructor, Node componentNode, 
			MethodDeclaration constructor, final List<ResourceHierarchy> depends, ILanguageSpecific langSpec) {
		// Declare the field to refer to each object in the main type.
		ResourceNode resNode = null;
		String nodeName = null;
		if (componentNode instanceof ResourceNode) {
			resNode = (ResourceNode) componentNode;
			nodeName = resNode.getResourceName();
		}
		String componentName = langSpec.toComponentName(nodeName);
		// Declare a field to refer each object.
		if (langSpec.declareField()) {
			FieldDeclaration refField = langSpec.newFieldDeclaration(new Type(componentName, componentName), nodeName);
			mainType.addField(refField);
		}
		// Add a statement to instantiate each object to the main constructor.
		List<String> parameters = new ArrayList<>();
		for (ResourceHierarchy res: depends) {
			parameters.add(res.getResourceName());
		}
		if (constructor.getParameters() != null) {
			for (VariableDeclaration var: constructor.getParameters()) {
				parameters.add(var.getName());
			}
		}

		Block mainConstructorBody = mainConstructor.getBody();
		if (mainConstructorBody == null) {
			mainConstructorBody = new Block();
			mainConstructor.setBody(mainConstructorBody);
		}
		mainConstructorBody.addStatement(langSpec.getFieldAccessor(nodeName) + langSpec.getAssignment() + langSpec.getConstructorInvocation(componentName, parameters) + langSpec.getStatementDelimiter());
	}

	protected ResourceHierarchy addReference(TypeDeclaration component, MethodDeclaration constructor, ResourceHierarchy dstRes, ILanguageSpecific langSpec) {
		if (!generatesComponent(dstRes)) {
			dstRes = dstRes.getParent();
		}
		String dstComponentName = getComponentName(dstRes, langSpec);
		if (dstComponentName != null) {
			String dstNodeName = langSpec.toVariableName(dstComponentName);
			if (langSpec.declareField()) {
				// Declare a field to refer to another component.
				component.addField(langSpec.newFieldDeclaration(new Type(dstComponentName, dstComponentName), dstNodeName));
			}
			// Initialize the field to refer to another component.
			constructor.addParameter(langSpec.newVariableDeclaration(new Type(dstComponentName, dstComponentName), dstNodeName));
			constructor.getBody().addStatement(langSpec.getFieldAccessor(dstNodeName) + langSpec.getAssignment() + dstNodeName + langSpec.getStatementDelimiter());
		}
		return dstRes;
	}

	protected void fillStateGetterMethod(MethodDeclaration stateGetter, ResourceHierarchy resourceHierarchy, Type resStateType, ILanguageSpecific langSpec) {
		// returns the state field when all incoming data-flow edges are PUSH-style.
		if (langSpec.isValueType(resStateType)) {
			stateGetter.addStatement(langSpec.getReturnStatement(langSpec.getFieldAccessor(fieldOfResourceState)) + langSpec.getStatementDelimiter());		// return value;
		} else {
			if (resourceHierarchy.getChildren() != null && resourceHierarchy.getChildren().size() == 1 && resourceHierarchy.getChildren().iterator().next().getNumParameters() > 0) {
				// list or map
				String implTypeName = resStateType.getImplementationTypeName();
				// copy the current state to be returned as a 'value'
				List<String> parameters = new ArrayList<>();
				parameters.add(langSpec.getFieldAccessor(fieldOfResourceState));
				stateGetter.addStatement(langSpec.getReturnStatement(langSpec.getConstructorInvocation(implTypeName, parameters)) + langSpec.getStatementDelimiter());	// return new Resource(value);
			} else {
				if (resourceHierarchy.getChildren() == null || resourceHierarchy.getChildren().size() == 0) {
					// a leaf resource
					String implTypeName = resStateType.getImplementationTypeName();
					// copy the current state to be returned as a 'value'
					List<String> parameters = new ArrayList<>();
					parameters.add(langSpec.getFieldAccessor(fieldOfResourceState));
					stateGetter.addStatement(langSpec.getReturnStatement(langSpec.getConstructorInvocation(implTypeName, parameters)) + langSpec.getStatementDelimiter());	// return new Resource(value);
				} else {
					Term composer = null; 
					Term composerSub = new Constant(DataConstraintModel.nil);
					composerSub.setType(DataConstraintModel.typeMap);
					for (ResourceHierarchy child: resourceHierarchy.getChildren()) {
						String childTypeName = getComponentName(child, langSpec);
						String fieldName = langSpec.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(getterOfResourceState, 1, Symbol.Type.METHOD));
							childGetter.addChild(new Field(fieldName, getImplStateType(child, langSpec)));
						}
						composer = new Term(DataConstraintModel.insert);
						composer.addChild(composerSub);
						composer.addChild(new Constant(langSpec.getStringDelimiter() + fieldName + langSpec.getStringDelimiter(), 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] + langSpec.getReturnStatement(returnValue) + langSpec.getStatementDelimiter());
					} else {
						stateGetter.addStatement(langSpec.getReturnStatement(returnValue) + langSpec.getStatementDelimiter());
					}
				}
			}
		}
	}

	protected void fillChildGetterMethod(MethodDeclaration childGetter, ResourceHierarchy child, Type parentResourceType, ILanguageSpecific langSpec) {
		if (DataConstraintModel.typeList.isAncestorOf(parentResourceType)) {
			Term selector = new Term(DataConstraintModel.get);
			selector.addChild(new Variable(langSpec.getFieldAccessor(fieldOfResourceState)));
			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(langSpec.getReturnStatement(returnValue) + langSpec.getStatementDelimiter());
		} else if (DataConstraintModel.typeMap.isAncestorOf(parentResourceType)) {
			Term selector = new Term(DataConstraintModel.lookup);
			selector.addChild(new Variable(langSpec.getFieldAccessor(fieldOfResourceState)));
			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(langSpec.getReturnStatement(returnValue) + langSpec.getStatementDelimiter());
		} else {
			String fieldName = langSpec.toVariableName(getComponentName(child, langSpec));
			childGetter.addStatement(langSpec.getReturnStatement(langSpec.getFieldAccessor(fieldName)) + langSpec.getStatementDelimiter());
		}		
	}

	protected void declareAccessorInMainComponent(TypeDeclaration mainComponent, ResourceNode accessRes, MethodDeclaration stateGetter, ILanguageSpecific langSpec) {
		List<VariableDeclaration> mainGetterParams = new ArrayList<>();
		int v = 1;
		for (Expression param: accessRes.getPrimaryResourcePath().getPathParams()) {
			if (param instanceof Variable) {
				Variable var = (Variable) param;
				mainGetterParams.add(langSpec.newVariableDeclaration(var.getType(), var.getName()));
			} else if (param instanceof Term) {
				Term var = (Term) param;
				mainGetterParams.add(new VariableDeclaration(var.getType(), "v" + v));
			}
			v++;
		}
		MethodDeclaration accessor = null;
		if (mainGetterParams.size() == 0) {
			accessor = langSpec.newMethodDeclaration(getterPrefix + getComponentName(accessRes.getResourceHierarchy(), langSpec), getImplStateType(accessRes.getResourceHierarchy(), langSpec));
		} else {
			accessor = langSpec.newMethodDeclaration(getterPrefix + getComponentName(accessRes.getResourceHierarchy(), langSpec), false, getImplStateType(accessRes.getResourceHierarchy(), langSpec), mainGetterParams);
		}
		Block block = new Block();
		Expression getState = getPullAccessor().getDirectStateAccessorFor(accessRes.getPrimaryResourcePath(), null);
		block.addStatement(langSpec.getReturnStatement(getState.toImplementation(new String[] {null})) + langSpec.getStatementDelimiter());
//		if (stateGetter.getParameters() == null || stateGetter.getParameters().size() == 0) {
//			block.addStatement(langSpec.getReturnStatement(langSpec.getMethodInvocation(accessRes.getResourceName(), stateGetter.getName())) + langSpec.getStatementDelimiter());
//		} else {
//			List<String> resParams = new ArrayList<>();
//			for (VariableDeclaration var: stateGetter.getParameters()) {
//				resParams.add(var.getName());
//			}
//			block.addStatement(langSpec.getReturnStatement(langSpec.getMethodInvocation(accessRes.getResourceName(), stateGetter.getName(), resParams)) + langSpec.getStatementDelimiter());
//		}
		accessor.setBody(block);
		mainComponent.addMethod(accessor);
	}

	protected void declareFieldsToReferenceResources(DataTransferModel model, ResourceNode resourceNode, TypeDeclaration component, MethodDeclaration constructor,
			final List<ResourceHierarchy> depends, ILanguageSpecific langSpec) {
		Set<ResourcePath> refs = new HashSet<>();
		for (Channel ch : model.getChannels()) {
			DataTransferChannel c = (DataTransferChannel) ch;
			if (resourceNode.getOutSideResource(c) != null) {
				for (ResourcePath res: c.getReferenceResources()) {
					if (!refs.contains(res) && !depends.contains(res.getResourceHierarchy())) {
						refs.add(res);
						String refResName = langSpec.toComponentName(res.getLeafResourceName());
						component.addField(langSpec.newFieldDeclaration(new Type(refResName, refResName), res.getLeafResourceName()));
						constructor.addParameter(langSpec.newVariableDeclaration(new Type(refResName, refResName), res.getLeafResourceName()));						
						constructor.getBody().addStatement(langSpec.getFieldAccessor(res.getLeafResourceName()) + langSpec.getAssignment() + res.getLeafResourceName() + langSpec.getStatementDelimiter());
					}
				}
			}
		}
	}
	
	protected MethodDeclaration getConstructor(TypeDeclaration component) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.isConstructor()) return m;
		}
		return null;
	}
	
	protected MethodDeclaration getUpdateMethod(Edge inEdge, TypeDeclaration component,
			Map<Edge, Map<PushPullValue, List<ResourceNode>>> dataFlowInform, ILanguageSpecific langSpec) {
		List<ResourceNode> passedResoueces = dataFlowInform.get(inEdge).get(PushPullValue.PUSH);
		String methodName = updateMethodPrefix;
		for (ResourceNode rn: passedResoueces) {
			methodName += langSpec.toComponentName(rn.getResourceName());
		}
		return getMethod(component, methodName);
	}

	protected MethodDeclaration getInputMethod(ResourceNode resourceNode, DataTransferChannel ch, TypeDeclaration component) {
		MethodDeclaration input = null;
		for (ChannelMember out : ch.getOutputChannelMembers()) {
			if (resourceNode.getInSideResources().contains(out.getResource())) {
				Expression message = out.getStateTransition().getMessageExpression();
				if (message instanceof Term) {
					input = getMethod(component, ((Term) message).getSymbol().getImplName());
				} else if (message instanceof Variable) {
					// Declare an input method in this component.
					input = getMethod(component, ((Variable) message).getName());
				}
				break;
			}
		}
		return input;
	}

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

	protected IResourceStateAccessor getPushAccessor() {
		return new IResourceStateAccessor() {
			@Override
			public Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from) {
				ResourcePath targetRes= target.getResource();
				ResourcePath fromRes= from.getResource();
				if (targetRes.equals(fromRes)) {
					return new Field(fieldOfResourceState,
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
				// use the cached value as the current state
				return new Field(targetRes.getLeafResourceName(),
						targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}

			@Override
			public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
				ResourcePath targerRes= target.getResource();
				return new Parameter(targerRes.getLeafResourceName(),
						targerRes.getResourceStateType() != null ? targerRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}

			@Override
			public Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes) {
				if (fromRes != null && targetRes.equals(fromRes)) {
					return new Field(fieldOfResourceState,
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
				// for reference channel member
				return new Parameter(targetRes.getLeafResourceName(),
						targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}
		};
	}

	protected IResourceStateAccessor getPullAccessor() {
		return new IResourceStateAccessor() {
			@Override
			public Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from) {
				ResourcePath targetRes= target.getResource();
				if (from != null) {
					ResourcePath fromRes= from.getResource();
					if (!target.isOutside()) {
						return getDirectStateAccessorFor(targetRes, fromRes);
					}
					Term getter = null;
					String targetComponentName = getComponentName(targetRes.getResourceHierarchy(), langSpec);
					if (generatesComponent(targetRes.getResourceHierarchy())) {
						getter = new Term(new Symbol(getterOfResourceState, 1, Symbol.Type.METHOD));
						getter.addChild(new Field(langSpec.toVariableName(targetComponentName), targetRes.getResourceStateType()));
					} else {
						String parentName = langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy().getParent(), langSpec));
						Type parentType = targetRes.getResourceHierarchy().getParent().getResourceStateType();
						getter = new Term(new Symbol(getterPrefix + targetComponentName, 1, Symbol.Type.METHOD));
						getter.addChild(new Field(parentName, parentType));
					}
					return getter;
				} else {
					return getDirectStateAccessorFor(targetRes, null);
				}
			}

			@Override
			public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
				ResourcePath targetRes= target.getResource();
				if (from != null) {
					ResourcePath fromRes= from.getResource();
					if (!target.isOutside()) {
						return getDirectStateAccessorFor(targetRes, fromRes);
					}
					Term getter = null;
					String targetComponentName = getComponentName(targetRes.getResourceHierarchy(), langSpec);
					if (generatesComponent(targetRes.getResourceHierarchy())) {
						getter = new Term(new Symbol(getterOfResourceState, 1, Symbol.Type.METHOD));
						getter.addChild(new Field(langSpec.toVariableName(targetComponentName), targetRes.getResourceStateType()));
					} else {
						String parentName = langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy().getParent(), langSpec));
						Type parentType = targetRes.getResourceHierarchy().getParent().getResourceStateType();
						getter = new Term(new Symbol(getterPrefix + targetComponentName, 1, Symbol.Type.METHOD));
						getter.addChild(new Field(parentName, parentType));
					}
					return getter;
				} else {
					return getDirectStateAccessorFor(targetRes, null);
				}
			}
			
			@Override
			public Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes) {
				if (fromRes != null) {
					if (targetRes.equals(fromRes)) {
						return new Field(fieldOfResourceState,
								targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
										: DataConstraintModel.typeInt);
					}
					// for reference channel member
					Term getter = new Term(new Symbol(getterOfResourceState, 1, Symbol.Type.METHOD));
					getter.addChild(new Field(targetRes.getLeafResourceName(), targetRes.getResourceStateType()));
					return getter;
				} else {
					// access from the outside of the hierarchy
					Stack<ResourcePath> pathStack = new Stack<>();
					ResourcePath curPath = targetRes;
					do {
						pathStack.push(curPath);
						curPath = curPath.getParent();					
					} while (curPath != null);
					// iterate from the root resource
					Term getter = null;
					int v = 1;
					while (!pathStack.empty()) {
						curPath = pathStack.pop();
						String typeName = getComponentName(curPath.getResourceHierarchy(), langSpec);
						if (getter == null) {
							// root resource
							String fieldName = langSpec.toVariableName(typeName);
							getter = new Field(fieldName, new Type(typeName, typeName));
						} else {
							Term newGetter = new Term(new Symbol(getterPrefix + typeName, -1, Symbol.Type.METHOD));
							newGetter.addChild(getter);
							if (curPath.getResourceHierarchy().getNumParameters() > 0) {
								Variable var = null;
								Expression param = curPath.getLastParam();
								if (param instanceof Variable) {
									var = (Variable) param;
								} else if (param instanceof Term) {
									var = new Variable("v" + v, ((Term) param).getType());
								}
								if (var != null) {
									newGetter.addChild(var);
									newGetter.getSymbol().setArity(2);
								}
								v++;
							}
							getter = newGetter;
						}
					}
					
					if (generatesComponent(targetRes.getResourceHierarchy())) {
						Term newGetter = new Term(new Symbol(getterOfResourceState, 1, Symbol.Type.METHOD));
						newGetter.addChild(getter);
						getter = newGetter;
					}
					return getter;
				}
			}
		};
	}
	
	protected IResourceStateAccessor getRefAccessor() {
		return new IResourceStateAccessor() {
			@Override
			public Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from) {
				ResourcePath targetRes= target.getResource();
				ResourcePath fromRes= from.getResource();
				if (targetRes.equals(fromRes)) {
					return new Field(fieldOfResourceState,
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
				// use the cached value as the current state
				return new Parameter(targetRes.getLeafResourceName(),
						targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}

			@Override
			public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
				ResourcePath targerRes= target.getResource();
				return new Parameter(targerRes.getLeafResourceName(),
						targerRes.getResourceStateType() != null ? targerRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}

			@Override
			public Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes) {
				if (fromRes != null && targetRes.equals(fromRes)) {
					return new Field(fieldOfResourceState,
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
				// for reference channel member
				return new Parameter(targetRes.getLeafResourceName(),
						targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}
		};
	}
}
