package org.bdware.analysis.gas;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bdware.analysis.BasicBlock;
import org.bdware.analysis.CFGraph;
import org.bdware.analysis.OpInfo;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;

public class CountProgramPoint {
	public static HashMap<Integer, Map<String, Integer>> ppcount;
	CFGraph actionCfg;
	private String bdName;
	HashMap<String, CFGraph> cfgraphMap;
	String actionName;
	int globalPC;

	public CountProgramPoint(HashMap<String, CFGraph> cfgMap, int startPc, int endPc, String aName,
			HashMap<Integer, Map<String, Integer>> ppc) {
		ppcount = ppc;
		cfgraphMap = cfgMap;
		actionName = aName;
		typeCount(startPc, endPc);
	}

	private HashMap<Integer, Map<String, Integer>> callCount(AbstractInsnNode insn) {
		if (insn instanceof InvokeDynamicInsnNode) {
			invoke = ((InvokeDynamicInsnNode) insn).bsmArgs[0];
			String functionName = ((InvokeDynamicInsnNode) insn).name;
			dynCount(functionName);
		} else {
			normalCount(insn);
		}
		return ppcount;
	}

	Object invoke = 0;

	public HashMap<Integer, Map<String, Integer>> typeCount(int startPc, int endPc) {
		if (startPc == 0) {
			ppcount.put(startPc, new HashMap<>());
			globalPC = startPc;
		} else {
			ppcount.put(endPc, new HashMap<>());
			globalPC = endPc;
		}
		actionCfg = cfgraphMap.get(actionName);
		int blockStart = 0, blockend = 0;
		if (startPc == 0) {
			blockStart = 0;
		} else {
			OUT: for (int j = 0; j < actionCfg.getBasicBlockSize(); j++) {
				BasicBlock bb = actionCfg.getBasicBlockAt(j);
				AbstractInsnNode insnNode = bb.lastInsn();
				if (insnNode instanceof InvokeDynamicInsnNode) {
					invoke = ((InvokeDynamicInsnNode) insnNode).bsmArgs[0];
					if ((int) invoke == startPc) {
						blockStart = j;
						break OUT;
					}
				}
			}
		}
		OUT: for (int j = 0; j < actionCfg.getBasicBlockSize(); j++) {
			BasicBlock bb = actionCfg.getBasicBlockAt(j);
			AbstractInsnNode insnNode = bb.lastInsn();
			if (insnNode instanceof InvokeDynamicInsnNode) {
				invoke = ((InvokeDynamicInsnNode) insnNode).bsmArgs[0];
				if ((int) invoke == endPc) {
					blockend = j;
					break OUT;
				}
			}
		}

		return getInsn(blockStart, blockend);

	}

	private HashMap<Integer, Map<String, Integer>> getInsn(int blockStart, int blockend) {
		BasicBlock t;
		if (blockend == blockStart && blockStart != 0) {
			System.out.println("blockend" + blockend);
			t = actionCfg.getBasicBlockAt(blockend);
			if (t.list.size() > 0) {
				List<AbstractInsnNode> insnList = t.getInsn();
				for (AbstractInsnNode insn : insnList) {
					if (insn instanceof InvokeDynamicInsnNode) {
						callCount(insn);
					} else if (insn instanceof JumpInsnNode) {
						jumpCount(insn);
					} else {
						normalCount(insn);
					}
				}
			}
			return ppcount;
		} else if (blockend == 0) {
			return ppcount;
		} else if (blockend != blockStart && blockStart != 0) {
			blockStart = blockStart + 1;
			blockend = blockend + 1;
			System.out.println(blockStart);
		}
		for (int i = blockStart; i < blockend; i++) {
			t = actionCfg.getBasicBlockAt(i);
			System.out.println("[t.blockID]" + t.blockID);
			if (t.list.size() > 0) {
				List<AbstractInsnNode> insnList = t.getInsn();
				for (AbstractInsnNode insn : insnList) {
					if (insn != null) {
						OpInfo info = null;
						if (insn.getOpcode() >= 0) {
							info = OpInfo.ops[insn.getOpcode()];
							System.out.println("[info.toString()]" + info.toString());
						}
						if (info == null) {

						} else if (info.canThrow()) {
							callCount(insn);
						} else if (info.canBranch()) {
							jumpCount(insn);
						} else if (info.canContinue()) {
							normalCount(insn);
						} else if (info.canReturn()) {
							normalCount(insn);
						} else if (info.canSwitch()) {
							normalCount(insn);
						} else {
							System.out.println("[info:else]" + info);
							normalCount(insn);
						}
					}
				}
			}

		}
		return ppcount;
	}

	private void normalCount(AbstractInsnNode insn) {
		bdName = FeeSchedule.BDInsn.name();
		if (ppcount.get(globalPC).containsKey(bdName)) {
			ppcount.get(globalPC).put(bdName, ppcount.get(globalPC).get(bdName) + 1);
		} else {
			ppcount.get(globalPC).put(bdName, 1);
		}

	}

	private void jumpCount(AbstractInsnNode insn) {
		bdName = FeeSchedule.BDjump.name();
		if (ppcount.get(globalPC).containsKey(bdName)) {
			ppcount.get(globalPC).put(bdName, ppcount.get(globalPC).get(bdName) + 1);
		} else {
			ppcount.get(globalPC).put(bdName, 1);
		}
	}

	private void dynCount(String functionName) {
		if (functionName.contains("getProp")) {
			bdName = FeeSchedule.BDgetMethod.name();
			if (ppcount.get(globalPC).containsKey(bdName)) {
				ppcount.get(globalPC).put(bdName, ppcount.get(globalPC).get(bdName) + 1);
				System.out.println("   function   " + functionName);
			} else {
				ppcount.get(globalPC).put(bdName, 1);
			}
		} else if (functionName.contains("setProp")) {
			bdName = FeeSchedule.BDsetMethod.name();
			if (ppcount.get(globalPC).containsKey(bdName)) {
				ppcount.get(globalPC).put(bdName, ppcount.get(globalPC).get(bdName) + 1);
				System.out.println("   function   " + functionName);
			} else {
				ppcount.get(globalPC).put(bdName, 1);
			}
		} else if (functionName.contains("call") && functionName.contains("Util")) {
			bdName = FeeSchedule.BDcallUtil.name();
			if (ppcount.get(globalPC).containsKey(bdName)) {
				ppcount.get(globalPC).put(bdName, ppcount.get(globalPC).get(bdName) + 1);
				System.out.println("   function   " + functionName);
			} else {
				ppcount.get(globalPC).put(bdName, 1);
			}
		} else if (functionName.contains("call") && isInnerfunction(functionName)) {
			bdName = FeeSchedule.BDcallFuntion.name();
			if (ppcount.get(globalPC).containsKey(bdName)) {
				ppcount.get(globalPC).put(bdName, ppcount.get(globalPC).get(bdName) + 1);
				System.out.println("   function   " + functionName);
			} else {
				ppcount.get(globalPC).put(bdName, 1);
			}
		} else if (functionName.contains("call")) {
			bdName = FeeSchedule.BDcallUtil.name();
			if (ppcount.get(globalPC).containsKey(bdName)) {
				ppcount.get(globalPC).put(bdName, ppcount.get(globalPC).get(bdName) + 1);
				System.out.println("   function   " + functionName);
			} else {
				ppcount.get(globalPC).put(bdName, 1);
			}

		} else {
			bdName = FeeSchedule.BDInsn.name();
			if (ppcount.get(globalPC).containsKey(bdName)) {
				ppcount.get(globalPC).put(bdName, ppcount.get(globalPC).get(bdName) + 1);
			} else {
				ppcount.get(globalPC).put(bdName, 1);
			}
		}

	}

	private boolean isInnerfunction(String functionName) {
		String string = functionName.split(":")[2];
		if (cfgraphMap.containsKey(string)) {
			return true;
		} else {
			return false;
		}
	}

}
