package jadx.core.utils;

import jadx.core.codegen.CodeWriter;
import jadx.core.codegen.InsnGen;
import jadx.core.codegen.MethodGen;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
import jadx.core.dex.visitors.regions.TracedRegionVisitor;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;

import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Deprecated
public class DebugUtils {
	private static final Logger LOG = LoggerFactory.getLogger(DebugUtils.class);

	public static void dump(MethodNode mth) {
		File out = new File("test-graph-tmp");
		DotGraphVisitor.dump(out).visit(mth);
		DotGraphVisitor.dumpRaw(out).visit(mth);
		DotGraphVisitor.dumpRegions(out).visit(mth);
	}

	public static void printRegionsWithBlock(MethodNode mth, final BlockNode block) {
		final Set<IRegion> regions = new LinkedHashSet<IRegion>();
		DepthRegionTraversal.traverse(mth, new TracedRegionVisitor() {
			@Override
			public void processBlockTraced(MethodNode mth, IBlock container, IRegion currentRegion) {
				if (block.equals(container)) {
					regions.add(currentRegion);
				}
			}
		});
		LOG.debug(" Found block: {} in regions: {}", block, regions);
	}

	public static void printRegions(MethodNode mth) {
		printRegions(mth, false);
	}

	public static void printRegions(MethodNode mth, boolean printInsns) {
		LOG.debug("|{}", mth.toString());
		printRegion(mth, mth.getRegion(), "|  ", printInsns);
	}

	private static void printRegion(MethodNode mth, IRegion region, String indent, boolean printInsns) {
		LOG.debug("{}{}", indent, region);
		indent += "|  ";
		for (IContainer container : region.getSubBlocks()) {
			if (container instanceof IRegion) {
				printRegion(mth, (IRegion) container, indent, printInsns);
			} else {
				LOG.debug("{}{}", indent, container);
				if (printInsns && container instanceof IBlock) {
					IBlock block = (IBlock) container;
					printInsns(mth, indent, block);
				}
			}
		}
	}

	private static void printInsns(MethodNode mth, String indent, IBlock block) {
		for (InsnNode insn : block.getInstructions()) {
			try {
				MethodGen mg = MethodGen.getFallbackMethodGen(mth);
				InsnGen ig = new InsnGen(mg, true);
				CodeWriter code = new CodeWriter();
				ig.makeInsn(insn, code);
				String insnStr = code.toString().substring(CodeWriter.NL.length());
				LOG.debug("{} - {}", indent, insnStr);
			} catch (CodegenException e) {
				LOG.debug("{} - {}", indent, insn);
			}
		}
	}

	public static void checkSSA(MethodNode mth) {
		for (BlockNode block : mth.getBasicBlocks()) {
			for (InsnNode insn : block.getInstructions()) {
				if (insn.getResult() != null) {
					checkSSAVar(mth, insn, insn.getResult());
				}
				for (InsnArg arg : insn.getArguments()) {
					if (arg instanceof RegisterArg) {
						checkSSAVar(mth, insn, (RegisterArg) arg);
					}
				}
			}
		}
		checkPHI(mth);
	}

	private static void checkSSAVar(MethodNode mth, InsnNode insn, RegisterArg reg) {
		SSAVar sVar = reg.getSVar();
		if (sVar == null) {
			throw new JadxRuntimeException("Null SSA var in " + insn + ", mth: " + mth);
		}
		for (RegisterArg useArg : sVar.getUseList()) {
			InsnNode parentInsn = useArg.getParentInsn();
			if (parentInsn != null) {
				if (!parentInsn.containsArg(useArg)) {
					throw new JadxRuntimeException("Incorrect use info in PHI insn");
				}
			}
		}
	}

	private static void checkPHI(MethodNode mth) {
		for (BlockNode block : mth.getBasicBlocks()) {
			List<PhiInsn> phis = new ArrayList<PhiInsn>();
			for (InsnNode insn : block.getInstructions()) {
				if (insn.getType() == InsnType.PHI) {
					PhiInsn phi = (PhiInsn) insn;
					phis.add(phi);
					if (phi.getArgsCount() != phi.getBlockBinds().size()) {
						throw new JadxRuntimeException("Incorrect args and binds in PHI");
					}
					if (phi.getArgsCount() == 0) {
						throw new JadxRuntimeException("No args and binds in PHI");
					}
					for (InsnArg arg : insn.getArguments()) {
						if (arg instanceof RegisterArg) {
							BlockNode b = phi.getBlockByArg((RegisterArg) arg);
							if (b == null) {
								throw new JadxRuntimeException("Predecessor block not found");
							}
						} else {
							throw new JadxRuntimeException("Not register in phi insn");
						}
					}
				}
			}
			PhiListAttr phiListAttr = block.get(AType.PHI_LIST);
			if (phiListAttr == null) {
				if (!phis.isEmpty()) {
					throw new JadxRuntimeException("Missing PHI list attribute");
				}
			} else {
				List<PhiInsn> phiList = phiListAttr.getList();
				if (phiList.isEmpty()) {
					throw new JadxRuntimeException("Empty PHI list attribute");
				}
				if (!phis.containsAll(phiList) || !phiList.containsAll(phis)) {
					throw new JadxRuntimeException("Instructions not match");
				}
			}
		}
		for (SSAVar ssaVar : mth.getSVars()) {
			PhiInsn usedInPhi = ssaVar.getUsedInPhi();
			if (usedInPhi != null) {
				boolean found = false;
				for (RegisterArg useArg : ssaVar.getUseList()) {
					InsnNode parentInsn = useArg.getParentInsn();
					if (parentInsn != null && parentInsn == usedInPhi) {
						found = true;
					}
				}
				if (!found) {
					throw new JadxRuntimeException("Used in phi incorrect");
				}
			}
		}
	}
}
