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

package pda.core.analyze.struct;

import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Type;
import pda.common.utils.LevelLogger;
import pda.common.utils.Pair;

import java.util.*;

/**
 * 
 * @author
 *
 */
public class BasicBlock {

	public enum BLOCKTYPE {
		FILE,
		TYPE,
		METHOD,
		IF,
		SWITCH,
		ENFOR,
		FOR,
		WHILE,
		DO,
		BLOCK,
		TRY,
		SYNC,
		CATCH,
		FINALLY,
		CASE,
		UNKNOWN
	}

	private final static boolean _record_global_variable = false;
	private static Set<Variable> _globalVariables = new HashSet<>();
	// private static Map<String, Use> _unresolvedUse = new HashMap<>();

	public static void addGlobalVariables(Variable variable) {
		// _globalVariables.add(variable);
	}

	public static Set<Variable> getGlobalVariables() {
		return _globalVariables;
	}

	private int _level;
	private String _file;
	private String _package;
	private String _publicClazz;
	private CompilationUnit _unit;
	private Set<Variable> _variables;
	private Set<ConstValue> _constants;
	private Set<BasicBlock> _children;
	private BasicBlock _parent;
	private BLOCKTYPE _blockType = BLOCKTYPE.UNKNOWN;
	private String _blockName = "Unknown";
	private Pair<Integer, Integer> _codeRange = new Pair<Integer, Integer>(0, 0);

	public BasicBlock(String file, String pkage, String clazz, CompilationUnit unit, int minLineNumber,
			int maxLineNumber, BasicBlock parent)
			throws IllegalArgumentException {
		setCodeRange(minLineNumber, maxLineNumber);
		_file = file;
		_package = pkage;
		_publicClazz = clazz;
		_unit = unit;
		_variables = new HashSet<>(7);
		_constants = new HashSet<>(7);
		_children = new HashSet<>(3);
		_parent = parent;
		_level = 0;
		if (parent != null) {
			_level = parent.getBlockLevel() + 1;
			parent.addChild(this);
		}
	}

	public void setCodeRange(int minLineNumber, int maxLineNumber) throws IllegalArgumentException {
		if (minLineNumber > maxLineNumber) {
			throw new IllegalArgumentException(
					"minLineRange should not be bigger than maxLineRange : " + minLineNumber + " " + maxLineNumber);
		}
		_codeRange.setFirst(minLineNumber);
		_codeRange.setSecond(maxLineNumber);
	}

	public void setBlockType(BLOCKTYPE type, String blockName) {
		_blockType = type;
		_blockName = blockName;
	}

	public void addVariables(Variable variable) {
		_variables.add(variable);
		variable.setParentBlock(this);
		variable.setClazz(_publicClazz);
		variable.setPackage(_package);
	}

	public void addConstant(ConstValue constValue) {
		_constants.add(constValue);
		constValue.setParentBlock(this);
	}

	public void addVariableUse(String varName, Use use) {
		Variable variable = null;
		if (use.isFieldUse()) {
			variable = topDownSearchVariable(varName);
		} else {
			variable = bottomUpSearchVariable(varName);
		}

		if (variable == null) {
			if (_record_global_variable) {
				LevelLogger.warn("@BasicBlock Cannot find defined variable with name : " + varName);
			}
		} else {
			variable.addUse(use);
		}
	}

	private Variable topDownSearchVariable(String varName) {
		Variable temp = null;
		BasicBlock basicBlock = this;
		while (basicBlock != null) {
			for (Variable variable : basicBlock.getDefinedVariables()) {
				if (varName.equals(variable.getName())) {
					temp = variable;
					break;
				}
			}
			basicBlock = basicBlock.getParent();
		}
		return temp;
	}

	private Variable bottomUpSearchVariable(String varName) {
		for (Variable variable : _variables) {
			if (varName.equals(variable.getName())) {
				return variable;
			}
		}
		if (getParent() != null) {
			return getParent().bottomUpSearchVariable(varName);
		} else {
			for (Variable variable : getGlobalVariables()) {
				if (varName.equals(variable.getName())) {
					return variable;
				}
			}
		}
		return null;
	}

	public void addChild(BasicBlock child) {
		_children.add(child);
	}

	public String getSourceFile() {
		return _file;
	}

	public BasicBlock getParent() {
		return _parent;
	}

	public CompilationUnit getCompilationUnit() {
		return _unit;
	}

	public int getBlockLevel() {
		return _level;
	}

	public BLOCKTYPE getBlockType() {
		return _blockType;
	}

	public String getBlockName() {
		return _blockName;
	}

	public Set<Variable> getDefinedVariables() {
		return _variables;
	}

	public Set<BasicBlock> getChildrenBlock() {
		return _children;
	}

	public Set<ConstValue> getConstants() {
		return _constants;
	}

	public Pair<Integer, Integer> getCodeRange() {
		return _codeRange;
	}

	public BasicBlock getMinimalBasicBlock(int line) {
		if (line < _codeRange.getFirst() || _codeRange.getSecond() < line) {
			return null;
		}
		for (BasicBlock basicBlock : _children) {
			BasicBlock block = basicBlock.getMinimalBasicBlock(line);
			if (block != null) {
				return block;
			}
		}
		return this;
	}

	public Set<Variable> getAllValidVariables(int line) {
		BasicBlock basicBlock = getMinimalBasicBlock(line);
		Set<Variable> variables = new HashSet<>();
		while (basicBlock != null) {
			for (Variable variable : basicBlock._variables) {
				if (variable.getLineNumber() < line) {
					variables.add(variable);
				}
			}
			basicBlock = basicBlock.getParent();
		}
		variables.addAll(getGlobalVariables());
		return Collections.unmodifiableSet(variables);
	}

	public Set<Variable> getAllValidVariables(int line, Type type) {
		if (type == null) {
			return getAllValidVariables(line);
		} else {
			return getAllValidVariables(line, type.toString());
		}
	}

	public Set<Variable> getAllValidVariables(int line, String type) {
		BasicBlock basicBlock = getMinimalBasicBlock(line);
		Set<Variable> variables = new HashSet<>();
		while (basicBlock != null) {
			for (Variable variable : basicBlock._variables) {
				if (variable.getLineNumber() < line && type.equals(variable.getType().toString())) {
					variables.add(variable);
				}
			}
			basicBlock = basicBlock.getParent();
		}
		variables.addAll(getGlobalVariables());
		return Collections.unmodifiableSet(variables);
	}

	public Set<Variable> recursivelyGetVariables() {
		Set<Variable> variables = new HashSet<>();
		variables.addAll(_variables);
		for (BasicBlock basicBlock : getChildrenBlock()) {
			variables.addAll(basicBlock.recursivelyGetVariables());
		}
		return Collections.unmodifiableSet(variables);
	}

	public Set<ConstValue> getAllConstValueUsedUp2Now() {
		Set<ConstValue> constValues = new HashSet<>();
		constValues.addAll(_constants);
		BasicBlock parent = this.getParent();
		while (parent != null) {
			if (parent.getBlockType() == BLOCKTYPE.METHOD) {
				break;
			}
			constValues.addAll(parent.getConstants());
			parent = parent.getParent();
		}
		return constValues;
	}

	public Set<Use> recursivelyGetUses() {
		Set<Use> uses = new HashSet<>();
		for (Variable variable : _variables) {
			uses.addAll(variable.getUseSet());
		}
		for (BasicBlock basicBlock : getChildrenBlock()) {
			uses.addAll(basicBlock.recursivelyGetUses());
		}
		return Collections.unmodifiableSet(uses);
	}

	public Set<Use> getVariableUses(int line) {
		BasicBlock basicBlock = getMinimalBasicBlock(line);
		Set<Use> uses = new HashSet<>();
		Set<Use> filter = new HashSet<>();
		while (basicBlock != null) {
			for (Variable variable : basicBlock._variables) {
				for (Use use : variable.getUseSet()) {
					if (use.getLineNumber() == line) {
						if (use.getUseType() == Use.USETYPE.DEFINE) {
							filter.add(use);
						} else {
							uses.add(use);
						}
					}
				}
			}
			basicBlock = basicBlock.getParent();
		}
		Set<Use> filtered = new HashSet<>();
		for (Use use : uses) {
			boolean canUse = true;
			for (Use f : filter) {
				if (use.getVariableDefine() == f.getVariableDefine() && use.getColumnNumber() < f.getColumnNumber()) {
					canUse = false;
					break;
				}
			}
			if (canUse) {
				filtered.add(use);
			}
		}
		return Collections.unmodifiableSet(filtered);
	}

	public void dump() {
		System.out.println("FILE : " + getSourceFile());
		System.out.println("PACKAGE : " + _package);
		System.out.println("CLASS : " + _publicClazz);
		System.out.println("Code range : " + getCodeRange().toString());
		System.out.println("Variables : ");
		List<Variable> variables = new ArrayList<>(getDefinedVariables());
		Collections.sort(variables, new Comparator<Variable>() {
			@Override
			public int compare(Variable o1, Variable o2) {
				int line = o1.getLineNumber() - o2.getLineNumber();
				if (line == 0) {
					return o1.getColumn() - o2.getColumn();
				}
				return line;
			}
		});
		for (Variable variable : variables) {
			variable.dump();
		}

		List<BasicBlock> children = new ArrayList<>(getChildrenBlock());
		Collections.sort(children, new Comparator<BasicBlock>() {

			@Override
			public int compare(BasicBlock o1, BasicBlock o2) {
				int line = o1.getCodeRange().getFirst() - o2.getCodeRange().getFirst();
				if (line == 0) {
					return o1.getCodeRange().getSecond() - o2.getCodeRange().getSecond();
				}
				return line;
			}

		});

		for (BasicBlock child : getChildrenBlock()) {
			child.dump();
		}

	}

}
