package org.ntlab.trace;

import interactivefeaturelocator.model.MethodInfo;

import java.util.ArrayList;
import java.util.HashSet;

public class MethodExecution {
	private String signature;
	private String callerSideSignature;
	private String thisClassName;
	private String thisObjId;
	private ArrayList<ObjectReference> arguments;
	private ObjectReference returnValue = null;
	private boolean isConstractor;
	private boolean isStatic;
	private boolean isCollectionType;
	private ArrayList<Statement> statements = new ArrayList<Statement>();
	private ArrayList<MethodExecution> children = new ArrayList<MethodExecution>();
	private MethodExecution callerMethodExecution = null;
	private int callerStatementExecution = -1;
	private boolean isTerminated = false;
	private AugmentationInfo augmentation = null;

	private long timeStamp = 0L;
	public MethodExecution(String signature, String callerSideSignature,
			String thisClassName, String thisObjId, boolean isConstractor,
			boolean isStatic, long timeStamp) {
		this.signature = signature;
		this.callerSideSignature = callerSideSignature;
		this.thisClassName = thisClassName;
		this.thisObjId = thisObjId;
		this.isConstractor = isConstractor;
		this.isStatic = isStatic;
		this.isCollectionType = false;
		this.isTerminated = false;
		this.timeStamp  = timeStamp;
		this.augmentation = new MethodInfo();
	}

	public void setArguments(ArrayList<ObjectReference> arguments) {
		this.arguments = arguments;
	}
	
	public void setThisObjeId(String thisObjId) {
		this.thisObjId = thisObjId;
	}

	public void setReturnValue(ObjectReference returnValue) {
		this.returnValue = returnValue;
	}
	
	public void setCollectionType(boolean isCollectionType) {
		this.isCollectionType = isCollectionType;
	}

	public void setTerminated(boolean isTerminated) {
		this.isTerminated = isTerminated;
	}

	public String getSignature() {
		return signature;
	}

	public String getCallerSideSignature() {
		return callerSideSignature;
	}

	public String getThisClassName() {
		return thisClassName;
	}

	public String getThisObjId() {
		if (isStatic) return Trace.getNull();
		return thisObjId;
	}

	public ArrayList<ObjectReference> getArguments() {
		if (arguments == null) arguments = new ArrayList<ObjectReference>();
		return arguments;
	}

	public ObjectReference getReturnValue() {
		return returnValue;
	}

	public boolean isConstractor() {
		return isConstractor;
	}

	public boolean isStatic() {
		return isStatic;
	}

	public boolean isCollectionType() {
		return isCollectionType;
	}
	
	public boolean isTerminated() {
		return isTerminated;
	}

	public long getTimeStamp() {
		return timeStamp;
	}

	public void addStatement(Statement statement) {
		statements.add(statement);
		if (statement instanceof MethodInvocation) {
			children.add(((MethodInvocation)statement).getCalledMethodExecution());
		}
	}

	public ArrayList<Statement> getStatements() {
		return statements;
	}

	public ArrayList<MethodExecution> getChildren() {
		return children;
	}

	public void setCaller(MethodExecution callerMethodExecution, int callerStatementExecution) {
		this.callerMethodExecution  = callerMethodExecution;
		this.callerStatementExecution = callerStatementExecution;
	}

	public MethodExecution getParent() {
		return callerMethodExecution;
	}
	
	public TracePoint getEntryPoint() {
		return new TracePoint(this, 0);
	}
	
	public TracePoint getExitPoint() {
		return new TracePoint(this, statements.size() - 1);
	}
	
	public TracePoint getExitOutPoint() {
		return new TracePoint(this, statements.size());
	}

	public MethodExecution getCallerMethodExecution() {
		return callerMethodExecution;
	}

	public int getCallerStatementExecution() {
		return callerStatementExecution;
	}

	public TracePoint getCallerTracePoint() {
		if (callerMethodExecution == null) return null; 
		return new TracePoint(callerMethodExecution, callerStatementExecution);
	}
	
	/**
	 * このメソッド実行およびその全呼び出し先を呼び出し木の中で逆向きに探索する(ただし、visitor が true を返すまで)
	 * @param visitor ビジター
	 * @return　true -- 探索を中断した, false -- 最後まで探索した
	 */
	public boolean traverseMethodExecutionsBackward(IMethodExecutionVisitor visitor) {
		if (visitor.preVisitMethodExecution(this)) return true;
		ArrayList<MethodExecution> calledMethodExecutions = getChildren();
		for (int i = calledMethodExecutions.size() - 1; i >= 0; i--) {
			MethodExecution child = calledMethodExecutions.get(i);
			if (child.traverseMethodExecutionsBackward(visitor)) return true;
		}		
		if (visitor.postVisitMethodExecution(this, null)) return true;
		return false;
	}

	public void traverseMarkedMethodExecutions(IMethodExecutionVisitor visitor, long markStart, long markEnd) {
		if (timeStamp <= markEnd) {
			if (timeStamp >= markStart) {
				ArrayList<MethodExecution> markedChildren = new ArrayList<MethodExecution>();
				visitor.preVisitMethodExecution(this);
				for (int i = 0; i < children.size(); i++) {
					MethodExecution child = children.get(i);
					if (child.getTimeStamp() <= markEnd) {
						child.traverseMarkedMethodExecutions(visitor, markStart, markEnd);
						markedChildren.add(child);
					} else {
						break;
					}
				}
				visitor.postVisitMethodExecution(this, markedChildren);
			} else {
				for (int i = 0; i < children.size(); i++) {
					MethodExecution child = children.get(i);
					if (child.getTimeStamp() <= markEnd) {
						child.traverseMarkedMethodExecutions(visitor, markStart, markEnd);
					}
				}				
			}
		}
	}

	public void getMarkedMethodSignatures(HashSet<String> signatures, long markStart, long markEnd) {
		if (timeStamp <= markEnd) {
			if (timeStamp >= markStart) {
				signatures.add(getSignature());
			}
			for (int i = 0; i < children.size(); i++) {
				MethodExecution child = children.get(i);
				child.getMarkedMethodSignatures(signatures, markStart, markEnd);
			}
		}
	}

	public void getUnmarkedMethodSignatures(HashSet<String> signatures, long markStart, long markEnd) {
		if (timeStamp < markStart || timeStamp > markEnd) {
			signatures.add(getSignature());
		}
	}
	
	public AugmentationInfo getAugmentation() {
		return augmentation;
	}

	public void setAugmentation(AugmentationInfo augmentation) {
		this.augmentation = augmentation;
	}
	
	public String getMethodName() {
		String signature = getSignature();
		int n = signature.indexOf("(");
		String str = signature.substring(0, n);
		String[] str2 = str.split("\\.");
		String methodName = str2[str2.length-1] + signature.substring(n);
		return methodName;
	}
}
