package jadx.core.dex.instructions;

import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InstructionRemover;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;

import java.util.IdentityHashMap;
import java.util.Map;

import org.jetbrains.annotations.NotNull;

public final class PhiInsn extends InsnNode {

	private final Map<RegisterArg, BlockNode> blockBinds;

	public PhiInsn(int regNum, int predecessors) {
		super(InsnType.PHI, predecessors);
		this.blockBinds = new IdentityHashMap<RegisterArg, BlockNode>(predecessors);
		setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
		add(AFlag.DONT_INLINE);
	}

	public RegisterArg bindArg(BlockNode pred) {
		RegisterArg arg = InsnArg.reg(getResult().getRegNum(), getResult().getType());
		bindArg(arg, pred);
		return arg;
	}

	public void bindArg(RegisterArg arg, BlockNode pred) {
		if (blockBinds.containsValue(pred)) {
			throw new JadxRuntimeException("Duplicate predecessors in PHI insn: " + pred + ", " + this);
		}
		addArg(arg);
		blockBinds.put(arg, pred);
	}

	public BlockNode getBlockByArg(RegisterArg arg) {
		return blockBinds.get(arg);
	}

	public Map<RegisterArg, BlockNode> getBlockBinds() {
		return blockBinds;
	}

	@Override
	@NotNull
	public RegisterArg getArg(int n) {
		return (RegisterArg) super.getArg(n);
	}

	@Override
	public boolean removeArg(InsnArg arg) {
		if (!(arg instanceof RegisterArg)) {
			return false;
		}
		RegisterArg reg = (RegisterArg) arg;
		if (super.removeArg(reg)) {
			blockBinds.remove(reg);
			InstructionRemover.fixUsedInPhiFlag(reg);
			return true;
		}
		return false;
	}

	@Override
	public boolean replaceArg(InsnArg from, InsnArg to) {
		if (!(from instanceof RegisterArg) || !(to instanceof RegisterArg)) {
			return false;
		}
		BlockNode pred = getBlockByArg((RegisterArg) from);
		if (pred == null) {
			throw new JadxRuntimeException("Unknown predecessor block by arg " + from + " in PHI: " + this);
		}
		if (removeArg(from)) {
			bindArg((RegisterArg) to, pred);
		}
		return true;
	}

	@Override
	public void setArg(int n, InsnArg arg) {
		throw new JadxRuntimeException("Unsupported operation for PHI node");
	}

	@Override
	public String toString() {
		return "PHI: " + getResult() + " = " + Utils.listToString(getArguments())
				+ " binds: " + blockBinds;
	}
}
