/**
 * Copyright (C) . - All Rights Reserved.
 * Unauthorized copying of this file via any medium is
 * strictly prohibited Proprietary and Confidential.
 * Written by .
 */

package pda.core.trace.inst.visitor;

import org.eclipse.jdt.core.dom.*;
import pda.common.conf.Constant;
import pda.common.utils.Identifier;
import pda.common.utils.Pair;
import pda.core.trace.inst.gen.GenStatement;

import java.util.ArrayList;
import java.util.List;

/**
 * @author
 *
 */
public class PredicateInstrumentVisitor extends TraversalVisitor {

	private final static String __name__ = "@NewPredicateInstrumentVisitor ";

	private int _line = -1;

	private List<Pair<String, String>> _condition = null;

	/**
	 * 
	 */
	public PredicateInstrumentVisitor(List<Pair<String, String>> condition, int line) {
		_condition = condition;
		_line = line;
	}

	public void setCondition(List<Pair<String, String>> condition) {
		_condition = condition;
	}

	@Override
	public boolean visit(MethodDeclaration node) {

		String message = buildMethodInfoString(node);
		if (message == null) {
			return true;
		}
		String keyValue = String.valueOf(Identifier.getIdentifier(message));
		// optimize instrument
		// message = Constant.INSTRUMENT_FLAG + _methodFlag +
		// Constant.INSTRUMENT_STR_SEP + keyValue + "#" + _line;
		message = keyValue + "#" + _line;

		Block methodBody = node.getBody();

		if (methodBody == null) {
			return true;
		}

		List<ASTNode> blockStatement = new ArrayList<>();

		int startLine = _cu.getLineNumber(node.getStartPosition());
		int endLine = _cu.getLineNumber(node.getStartPosition() + node.getLength());

		if (_line < startLine || endLine < _line) {
			return true;
		}

		for (int i = 0; i < methodBody.statements().size(); i++) {
			ASTNode astNode = (ASTNode) methodBody.statements().get(i);
			blockStatement.addAll(process(astNode, message));
		}

		methodBody.statements().clear();
		for (ASTNode statement : blockStatement) {
			methodBody.statements().add(ASTNode.copySubtree(methodBody.getAST(), statement));
		}

		return true;
	}

	private List<ASTNode> process(ASTNode statement, String message) {

		List<ASTNode> result = new ArrayList<>();

		int startLine = _cu.getLineNumber(statement.getStartPosition());
		int endLine = _cu.getLineNumber(statement.getStartPosition() + statement.getLength());
		if (startLine > _line || endLine < _line) {
			result.add(ASTNode.copySubtree(AST.newAST(Constant.AST_LEVEL), statement));
			return result;
		}

		if (statement instanceof IfStatement) {

			IfStatement ifStatement = (IfStatement) statement;
			startLine = _cu.getLineNumber(ifStatement.getExpression().getStartPosition());
			if (startLine == _line) {
				// ASTNode inserted =
				// GenStatement.genPredicateStatement(_condition, message,
				// _line);
				for (int count = 0; count < _condition.size(); count++) {
					String condition = _condition.get(count).getFirst();
					String prob = _condition.get(count).getSecond();
					ASTNode inserted = GenStatement.newGenPredicateStatement(condition,
							message + "#" + condition + "#" + prob + "#" + count);
					if (inserted != null) {
						result.add(inserted);
					}
				}
				result.add(ASTNode.copySubtree(AST.newAST(Constant.AST_LEVEL), statement));
				return result;
			}

			Statement thenBody = ifStatement.getThenStatement();

			if (thenBody != null) {
				startLine = _cu.getLineNumber(thenBody.getStartPosition());
				endLine = _cu.getLineNumber(thenBody.getStartPosition() + thenBody.getLength());
				if (startLine <= _line && _line <= endLine) {
					Block thenBlock = null;
					if (thenBody instanceof Block) {
						thenBlock = (Block) thenBody;
					} else {
						AST ast = AST.newAST(Constant.AST_LEVEL);
						thenBlock = ast.newBlock();
						thenBlock.statements().add(ASTNode.copySubtree(thenBlock.getAST(), thenBody));
					}

					Block newThenBlock = processBlock(thenBlock, message);
					ifStatement.setThenStatement((Statement) ASTNode.copySubtree(ifStatement.getAST(), newThenBlock));
				}
			}

			Statement elseBody = ifStatement.getElseStatement();
			if (elseBody != null) {
				startLine = _cu.getLineNumber(elseBody.getStartPosition());
				endLine = _cu.getLineNumber(elseBody.getStartPosition() + elseBody.getLength());
				if (startLine <= _line && _line <= endLine) {
					Block elseBlock = null;
					if (elseBody instanceof Block) {
						elseBlock = (Block) elseBody;
					} else {
						AST ast = AST.newAST(Constant.AST_LEVEL);
						elseBlock = ast.newBlock();
						elseBlock.statements().add(ASTNode.copySubtree(elseBlock.getAST(), elseBody));
					}
					Block newElseBlock = processBlock(elseBlock, message);
					ifStatement.setElseStatement((Statement) ASTNode.copySubtree(ifStatement.getAST(), newElseBlock));
				}
			}
			result.add(ifStatement);
		} else if (statement instanceof WhileStatement) {

			WhileStatement whileStatement = (WhileStatement) statement;

			int lineNumber = _cu.getLineNumber(whileStatement.getExpression().getStartPosition());
			if (lineNumber == _line) {
				// ASTNode inserted =
				// GenStatement.genPredicateStatement(_condition, message,
				// _line);
				Block block = whileStatement.getAST().newBlock();
				for (int count = 0; count < _condition.size(); count++) {
					String condition = _condition.get(count).getFirst();
					String prob = _condition.get(count).getSecond();
					ASTNode inserted = GenStatement.newGenPredicateStatement(condition,
							message + "#" + condition + "#" + prob + "#" + count);
					if (inserted != null) {
						result.add(inserted);
						block.statements().add(ASTNode.copySubtree(block.getAST(), inserted));
					}
				}
				block = extractNodeIntoBlock(block, whileStatement.getBody());
				whileStatement.setBody(block);
				result.add(ASTNode.copySubtree(AST.newAST(Constant.AST_LEVEL), whileStatement));
				return result;
			}

			Statement whilebody = whileStatement.getBody();

			if (whilebody != null) {
				startLine = _cu.getLineNumber(whilebody.getStartPosition());
				endLine = _cu.getLineNumber(whilebody.getStartPosition() + whilebody.getLength());
				if (startLine <= _line && _line <= endLine) {
					Block whileBlock = null;
					if (whilebody instanceof Block) {
						whileBlock = (Block) whilebody;
					} else {
						AST ast = AST.newAST(Constant.AST_LEVEL);
						whileBlock = ast.newBlock();
						whileBlock.statements().add(ASTNode.copySubtree(whileBlock.getAST(), whilebody));
					}

					Block newWhileBlock = processBlock(whileBlock, message);
					whileStatement.setBody((Statement) ASTNode.copySubtree(whileStatement.getAST(), newWhileBlock));
				}
			}
			result.add(whileStatement);
		} else if (statement instanceof ForStatement) {

			ForStatement forStatement = (ForStatement) statement;

			int lineNumber = -1;
			if (forStatement.getExpression() != null) {
				lineNumber = _cu.getLineNumber(forStatement.getExpression().getStartPosition());
			} else if (forStatement.initializers() != null && forStatement.initializers().size() > 0) {
				lineNumber = _cu.getLineNumber(((ASTNode) forStatement.initializers().get(0)).getStartPosition());
			} else if (forStatement.updaters() != null && forStatement.updaters().size() > 0) {
				lineNumber = _cu.getLineNumber(((ASTNode) forStatement.updaters().get(0)).getStartPosition());
			}

			if (lineNumber == _line) {
				// ASTNode inserted =
				// GenStatement.genPredicateStatement(_condition, message,
				// _line);
				Block block = forStatement.getAST().newBlock();
				for (int count = 0; count < _condition.size(); count++) {
					String condition = _condition.get(count).getFirst();
					String prob = _condition.get(count).getSecond();
					ASTNode inserted = GenStatement.newGenPredicateStatement(condition,
							message + "#" + condition + "#" + prob + "#" + count);
					if (inserted != null) {
						block.statements().add(ASTNode.copySubtree(block.getAST(), inserted));
						result.add(inserted);
					}
				}

				block = extractNodeIntoBlock(block, forStatement.getBody());
				forStatement.setBody(block);
				result.add(ASTNode.copySubtree(AST.newAST(Constant.AST_LEVEL), forStatement));
				return result;
			}

			Statement forBody = forStatement.getBody();

			if (forBody != null) {
				startLine = _cu.getLineNumber(forBody.getStartPosition());
				endLine = _cu.getLineNumber(forBody.getStartPosition() + forBody.getLength());
				if (startLine <= _line && _line <= endLine) {
					Block forBlock = null;
					if (forBody instanceof Block) {
						forBlock = (Block) forBody;
					} else {
						AST ast = AST.newAST(Constant.AST_LEVEL);
						forBlock = ast.newBlock();
						forBlock.statements().add(ASTNode.copySubtree(forBlock.getAST(), forBody));
					}

					Block newForBlock = processBlock(forBlock, message);
					forStatement.setBody((Statement) ASTNode.copySubtree(forStatement.getAST(), newForBlock));
				}
			}

			result.add(forStatement);
		} else if (statement instanceof DoStatement) {

			DoStatement doStatement = (DoStatement) statement;

			int lineNumber = _cu.getLineNumber(doStatement.getExpression().getStartPosition());
			if (lineNumber == _line) {
				// ASTNode inserted =
				// GenStatement.genPredicateStatement(_condition, message,
				// _line);
				Block block = doStatement.getAST().newBlock();
				block = extractNodeIntoBlock(block, doStatement.getBody());

				for (int count = 0; count < _condition.size(); count++) {
					String condition = _condition.get(count).getFirst();
					String prob = _condition.get(count).getSecond();
					ASTNode inserted = GenStatement.newGenPredicateStatement(condition,
							message + "#" + condition + "#" + prob + "#" + count);
					if (inserted != null) {
						block.statements().add(ASTNode.copySubtree(block.getAST(), inserted));
					}
				}

				doStatement.setBody(block);
				result.add(ASTNode.copySubtree(AST.newAST(Constant.AST_LEVEL), doStatement));
				return result;
			}

			Statement doBody = doStatement.getBody();
			if (doBody != null) {
				startLine = _cu.getLineNumber(doBody.getStartPosition());
				endLine = _cu.getLineNumber(doBody.getStartPosition() + doBody.getLength());
				if (startLine <= _line && _line <= endLine) {
					Block doBlock = null;
					if (doBody instanceof Block) {
						doBlock = (Block) doBody;
					} else {
						AST ast = AST.newAST(Constant.AST_LEVEL);
						doBlock = ast.newBlock();
						doBlock.statements().add(ASTNode.copySubtree(doBlock.getAST(), doBody));
					}

					Block newDoBlock = processBlock(doBlock, message);
					doStatement.setBody((Statement) ASTNode.copySubtree(doStatement.getAST(), newDoBlock));
				}
			}

			result.add(doStatement);
		} else if (statement instanceof Block) {
			Block block = (Block) statement;
			Block newBlock = processBlock(block, message);
			result.add(newBlock);
		} else if (statement instanceof EnhancedForStatement) {

			EnhancedForStatement enhancedForStatement = (EnhancedForStatement) statement;

			int lineNumber = _cu.getLineNumber(enhancedForStatement.getExpression().getStartPosition());
			if (lineNumber == _line) {
				// ASTNode inserted =
				// GenStatement.genPredicateStatement(_condition, message,
				// _line);
				Block block = enhancedForStatement.getAST().newBlock();
				for (int count = 0; count < _condition.size(); count++) {
					String condition = _condition.get(count).getFirst();
					String prob = _condition.get(count).getSecond();
					ASTNode inserted = GenStatement.newGenPredicateStatement(condition,
							message + "#" + condition + "#" + prob + "#" + count);
					if (inserted != null) {
						result.add(inserted);
						block.statements().add(ASTNode.copySubtree(block.getAST(), inserted));
					}
				}
				block = extractNodeIntoBlock(block, enhancedForStatement.getBody());
				enhancedForStatement.setBody(block);
				result.add(ASTNode.copySubtree(AST.newAST(Constant.AST_LEVEL), enhancedForStatement));
				return result;
			}

			Statement enhancedBody = enhancedForStatement.getBody();
			if (enhancedBody != null) {

				startLine = _cu.getLineNumber(enhancedBody.getStartPosition());
				endLine = _cu.getLineNumber(enhancedBody.getStartPosition() + enhancedBody.getLength());
				if (startLine <= _line && _line <= endLine) {

					Block enhancedBlock = null;
					if (enhancedBody instanceof Block) {
						enhancedBlock = (Block) enhancedBody;
					} else {
						AST ast = AST.newAST(Constant.AST_LEVEL);
						enhancedBlock = ast.newBlock();
						enhancedBlock.statements().add(ASTNode.copySubtree(enhancedBlock.getAST(), enhancedBody));
					}
					Block newEnhancedBlock = processBlock(enhancedBlock, message);
					enhancedForStatement
							.setBody((Statement) ASTNode.copySubtree(enhancedForStatement.getAST(), newEnhancedBlock));
				}
			}

			result.add(enhancedForStatement);
		} else if (statement instanceof SwitchStatement) {

			SwitchStatement switchStatement = (SwitchStatement) statement;

			int lineNumber = _cu.getLineNumber(switchStatement.getExpression().getStartPosition());
			if (lineNumber == _line) {
				// ASTNode inserted =
				// GenStatement.genPredicateStatement(_condition, message,
				// _line);
				for (int count = 0; count < _condition.size(); count++) {
					String condition = _condition.get(count).getFirst();
					String prob = _condition.get(count).getSecond();
					ASTNode inserted = GenStatement.newGenPredicateStatement(condition,
							message + "#" + condition + "#" + prob + "#" + count);
					if (inserted != null) {
						result.add(inserted);
					}
				}
				result.add(ASTNode.copySubtree(AST.newAST(Constant.AST_LEVEL), statement));
				return result;
			}

			List<ASTNode> statements = new ArrayList<>();
			AST ast = AST.newAST(Constant.AST_LEVEL);
			for (Object object : switchStatement.statements()) {
				ASTNode astNode = (ASTNode) object;
				statements.add(ASTNode.copySubtree(ast, astNode));
			}

			switchStatement.statements().clear();

			for (ASTNode astNode : statements) {
				for (ASTNode node : process(astNode, message)) {
					switchStatement.statements().add(ASTNode.copySubtree(switchStatement.getAST(), node));
				}
			}

			result.add(switchStatement);
		} else if (statement instanceof TryStatement) {

			TryStatement tryStatement = (TryStatement) statement;

			Block tryBlock = tryStatement.getBody();

			if (tryBlock != null) {
				startLine = _cu.getLineNumber(tryBlock.getStartPosition());
				endLine = _cu.getLineNumber(tryBlock.getStartPosition() + tryBlock.getLength());
				if (startLine <= _line && _line <= endLine) {
					Block newTryBlock = processBlock(tryBlock, message);
					tryStatement.setBody((Block) ASTNode.copySubtree(tryStatement.getAST(), newTryBlock));
				}
			}

			List catchList = tryStatement.catchClauses();
			if (catchList != null) {
				for (Object object : catchList) {
					if (object instanceof CatchClause) {
						CatchClause catchClause = (CatchClause) object;
						Block catchBlock = catchClause.getBody();
						Block newCatchBlock = processBlock(catchBlock, message);
						catchClause.setBody((Block) ASTNode.copySubtree(catchClause.getAST(), newCatchBlock));
					}
				}
			}

			Block finallyBlock = tryStatement.getFinally();
			if (finallyBlock != null) {
				startLine = _cu.getLineNumber(finallyBlock.getStartPosition());
				endLine = _cu.getLineNumber(finallyBlock.getStartPosition() + finallyBlock.getLength());
				if (startLine <= _line && _line <= endLine) {
					Block newFinallyBlock = processBlock(finallyBlock, message);
					tryStatement.setFinally((Block) ASTNode.copySubtree(tryStatement.getAST(), newFinallyBlock));
				}
			}

			result.add(tryStatement);
		} else {
			Statement copy = (Statement) ASTNode.copySubtree(AST.newAST(Constant.AST_LEVEL), statement);
			// Statement insert = GenStatement.genPredicateStatement(_condition,
			// message, _line);
			List<ASTNode> tmpInserted = new ArrayList<>();
			for (int count = 0; count < _condition.size(); count++) {
				String condition = _condition.get(count).getFirst();
				String prob = _condition.get(count).getSecond();
				ASTNode inserted = GenStatement.newGenPredicateStatement(condition,
						message + "#" + condition + "#" + prob + "#" + count);
				if (inserted != null) {
					tmpInserted.add(inserted);
				}
			}
			// Statement insert = GenStatement.newGenPredicateStatement(_condition,
			// message);

			// fix, 2018-1-5, insert statement for left hand side variable in
			// assignment
			if ((statement instanceof ExpressionStatement
					&& ((ExpressionStatement) statement).getExpression() instanceof Assignment)
					|| statement instanceof VariableDeclarationStatement || statement instanceof ConstructorInvocation
					|| statement instanceof SuperConstructorInvocation) {
				result.add(copy);
				// result.add(insert);
				result.addAll(tmpInserted);
			} else if (statement instanceof ContinueStatement || statement instanceof BreakStatement
					|| statement instanceof ReturnStatement || statement instanceof ThrowStatement
					|| statement instanceof AssertStatement || statement instanceof ExpressionStatement) {
				// result.add(insert);
				result.addAll(tmpInserted);
				result.add(copy);

			} else if (statement instanceof LabeledStatement) {
				// result.add(insert);
				result.addAll(tmpInserted);
				result.add(copy);
			} else if (statement instanceof SynchronizedStatement) {
				// result.add(insert);
				result.addAll(tmpInserted);
				result.add(copy);
			} else {
				// result.add(insert);
				result.addAll(tmpInserted);
				result.add(copy);
			}
		}

		return result;
	}

	private Block extractNodeIntoBlock(Block block, ASTNode node) {
		if (node != null) {
			if (node instanceof Block) {
				block.statements().addAll(ASTNode.copySubtrees(block.getAST(), ((Block) node).statements()));
			} else {
				block.statements().add(ASTNode.copySubtree(block.getAST(), node));
			}
		}
		return block;
	}

	private Block processBlock(Block block, String message) {
		Block newBlock = AST.newAST(Constant.AST_LEVEL).newBlock();
		if (block == null) {
			return newBlock;
		}
		for (Object object : block.statements()) {
			ASTNode astNode = (ASTNode) object;
			List<ASTNode> newStatements = process(astNode, message);
			for (ASTNode newStatement : newStatements) {
				newBlock.statements().add(ASTNode.copySubtree(newBlock.getAST(), newStatement));
			}
		}
		return newBlock;
	}

}
