package jadx.core.codegen;

import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.NewArrayNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.Named;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class InsnGen {
	private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);

	protected final MethodGen mgen;
	protected final MethodNode mth;
	protected final RootNode root;
	protected final boolean fallback;

	protected enum Flags {
		BODY_ONLY,
		BODY_ONLY_NOWRAP,
		INLINE
	}

	public InsnGen(MethodGen mgen, boolean fallback) {
		this.mgen = mgen;
		this.mth = mgen.getMethodNode();
		this.root = mth.dex().root();
		this.fallback = fallback;
	}

	private boolean isFallback() {
		return fallback;
	}

	public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
		int len = code.length();
		addArg(code, arg, true);
		if (len != code.length()) {
			code.add('.');
		}
	}

	public void addArg(CodeWriter code, InsnArg arg) throws CodegenException {
		addArg(code, arg, true);
	}

	public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
		if (arg.isRegister()) {
			code.add(mgen.getNameGen().useArg((RegisterArg) arg));
		} else if (arg.isLiteral()) {
			code.add(lit((LiteralArg) arg));
		} else if (arg.isInsnWrap()) {
			Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
			makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
		} else if (arg.isNamed()) {
			code.add(((Named) arg).getName());
		} else if (arg.isField()) {
			FieldArg f = (FieldArg) arg;
			if (f.isStatic()) {
				staticField(code, f.getField());
			} else {
				instanceField(code, f.getField(), f.getInstanceArg());
			}
		} else {
			throw new CodegenException("Unknown arg type " + arg);
		}
	}

	public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
		RegisterArg arg = insn.getResult();
		if (insn.contains(AFlag.DECLARE_VAR)) {
			declareVar(code, arg);
		} else {
			addArg(code, arg, false);
		}
	}

	public void declareVar(CodeWriter code, RegisterArg arg) {
		useType(code, arg.getType());
		code.add(' ');
		code.add(mgen.getNameGen().assignArg(arg));
	}

	private static String lit(LiteralArg arg) {
		return TypeGen.literalToString(arg.getLiteral(), arg.getType());
	}

	private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
		ClassNode pCls = mth.getParentClass();
		FieldNode fieldNode = pCls.searchField(field);
		while (fieldNode == null
				&& pCls.getParentClass() != pCls
				&& pCls.getParentClass() != null) {
			pCls = pCls.getParentClass();
			fieldNode = pCls.searchField(field);
		}
		if (fieldNode != null) {
			FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
			if (replace != null) {
				FieldInfo info = replace.getFieldInfo();
				if (replace.isOuterClass()) {
					useClass(code, info.getDeclClass());
					code.add(".this");
				}
				return;
			}
		}
		addArgDot(code, arg);
		fieldNode = mth.dex().resolveField(field);
		if (fieldNode != null) {
			code.attachAnnotation(fieldNode);
		}
		code.add(field.getName());
	}

	public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
		ClassInfo declClass = field.getDeclClass();
		boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
		if (!fieldFromThisClass) {
			// Android specific resources class handler
			ClassInfo parentClass = declClass.getParentClass();
			if (parentClass != null && parentClass.getShortName().equals("R")) {
				clsGen.useClass(code, parentClass);
				code.add('.');
				code.add(declClass.getAlias().getShortName());
			} else {
				clsGen.useClass(code, declClass);
			}
			code.add('.');
		}
		FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
		if (fieldNode != null) {
			code.attachAnnotation(fieldNode);
		}
		code.add(field.getName());
	}

	protected void staticField(CodeWriter code, FieldInfo field) {
		makeStaticFieldAccess(code, field, mgen.getClassGen());
	}

	public void useClass(CodeWriter code, ArgType type) {
		mgen.getClassGen().useClass(code, type);
	}

	public void useClass(CodeWriter code, ClassInfo cls) {
		mgen.getClassGen().useClass(code, cls);
	}

	protected void useType(CodeWriter code, ArgType type) {
		mgen.getClassGen().useType(code, type);
	}

	public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
		return makeInsn(insn, code, null);
	}

	protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
		try {
			Set<Flags> state = EnumSet.noneOf(Flags.class);
			if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
				state.add(flag);
				makeInsnBody(code, insn, state);
			} else {
				if (flag != Flags.INLINE) {
					code.startLineWithNum(insn.getSourceLine());
				}
				if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
					assignVar(code, insn);
					code.add(" = ");
				}
				makeInsnBody(code, insn, state);
				if (flag != Flags.INLINE) {
					code.add(';');
				}
			}
		} catch (Throwable th) {
			throw new CodegenException(mth, "Error generate insn: " + insn, th);
		}
		return true;
	}

	private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
		switch (insn.getType()) {
			case CONST_STR:
				String str = ((ConstStringNode) insn).getString();
				code.add(StringUtils.unescapeString(str));
				break;

			case CONST_CLASS:
				ArgType clsType = ((ConstClassNode) insn).getClsType();
				useType(code, clsType);
				code.add(".class");
				break;

			case CONST:
				LiteralArg arg = (LiteralArg) insn.getArg(0);
				code.add(lit(arg));
				break;

			case MOVE:
				addArg(code, insn.getArg(0), false);
				break;

			case CHECK_CAST:
			case CAST: {
				boolean wrap = state.contains(Flags.BODY_ONLY);
				if (wrap) {
					code.add('(');
				}
				code.add('(');
				useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
				code.add(") ");
				addArg(code, insn.getArg(0), true);
				if (wrap) {
					code.add(')');
				}
				break;
			}

			case ARITH:
				makeArith((ArithNode) insn, code, state);
				break;

			case NEG: {
				boolean wrap = state.contains(Flags.BODY_ONLY);
				if (wrap) {
					code.add('(');
				}
				code.add('-');
				addArg(code, insn.getArg(0));
				if (wrap) {
					code.add(')');
				}
				break;
			}

			case RETURN:
				if (insn.getArgsCount() != 0) {
					code.add("return ");
					addArg(code, insn.getArg(0), false);
				} else {
					code.add("return");
				}
				break;

			case BREAK:
				code.add("break");
				LoopLabelAttr labelAttr = insn.get(AType.LOOP_LABEL);
				if (labelAttr != null) {
					code.add(' ').add(mgen.getNameGen().getLoopLabel(labelAttr));
				}
				break;

			case CONTINUE:
				code.add("continue");
				break;

			case THROW:
				code.add("throw ");
				addArg(code, insn.getArg(0), true);
				break;

			case CMP_L:
			case CMP_G:
				code.add('(');
				addArg(code, insn.getArg(0));
				code.add(" > ");
				addArg(code, insn.getArg(1));
				code.add(" ? 1 : (");
				addArg(code, insn.getArg(0));
				code.add(" == ");
				addArg(code, insn.getArg(1));
				code.add(" ? 0 : -1))");
				break;

			case INSTANCE_OF: {
				boolean wrap = state.contains(Flags.BODY_ONLY);
				if (wrap) {
					code.add('(');
				}
				addArg(code, insn.getArg(0));
				code.add(" instanceof ");
				useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
				if (wrap) {
					code.add(')');
				}
				break;
			}
			case CONSTRUCTOR:
				makeConstructor((ConstructorInsn) insn, code);
				break;

			case INVOKE:
				makeInvoke((InvokeNode) insn, code);
				break;

			case NEW_ARRAY: {
				ArgType arrayType = ((NewArrayNode) insn).getArrayType();
				code.add("new ");
				useType(code, arrayType.getArrayRootElement());
				code.add('[');
				addArg(code, insn.getArg(0));
				code.add(']');
				int dim = arrayType.getArrayDimension();
				for (int i = 0; i < dim - 1; i++) {
					code.add("[]");
				}
				break;
			}

			case ARRAY_LENGTH:
				addArg(code, insn.getArg(0));
				code.add(".length");
				break;

			case FILLED_NEW_ARRAY:
				filledNewArray((FilledNewArrayNode) insn, code);
				break;

			case AGET:
				addArg(code, insn.getArg(0));
				code.add('[');
				addArg(code, insn.getArg(1), false);
				code.add(']');
				break;

			case APUT:
				addArg(code, insn.getArg(0));
				code.add('[');
				addArg(code, insn.getArg(1), false);
				code.add("] = ");
				addArg(code, insn.getArg(2), false);
				break;

			case IGET: {
				FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
				instanceField(code, fieldInfo, insn.getArg(0));
				break;
			}
			case IPUT: {
				FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
				instanceField(code, fieldInfo, insn.getArg(1));
				code.add(" = ");
				addArg(code, insn.getArg(0), false);
				break;
			}

			case SGET:
				staticField(code, (FieldInfo) ((IndexInsnNode) insn).getIndex());
				break;
			case SPUT:
				FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
				staticField(code, field);
				code.add(" = ");
				addArg(code, insn.getArg(0), false);
				break;

			case STR_CONCAT:
				boolean wrap = state.contains(Flags.BODY_ONLY);
				if (wrap) {
					code.add('(');
				}
				for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
					addArg(code, it.next());
					if (it.hasNext()) {
						code.add(" + ");
					}
				}
				if (wrap) {
					code.add(')');
				}
				break;

			case MONITOR_ENTER:
				if (isFallback()) {
					code.add("monitor-enter(");
					addArg(code, insn.getArg(0));
					code.add(')');
				}
				break;

			case MONITOR_EXIT:
				if (isFallback()) {
					code.add("monitor-exit(");
					addArg(code, insn.getArg(0));
					code.add(')');
				}
				break;

			case TERNARY:
				makeTernary((TernaryInsn) insn, code, state);
				break;

			case ONE_ARG:
				addArg(code, insn.getArg(0));
				break;

			/* fallback mode instructions */
			case IF:
				assert isFallback() : "if insn in not fallback mode";
				IfNode ifInsn = (IfNode) insn;
				code.add("if (");
				addArg(code, insn.getArg(0));
				code.add(' ');
				code.add(ifInsn.getOp().getSymbol()).add(' ');
				addArg(code, insn.getArg(1));
				code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
				break;

			case GOTO:
				assert isFallback();
				code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
				break;

			case MOVE_EXCEPTION:
				assert isFallback();
				code.add("move-exception");
				break;

			case SWITCH:
				assert isFallback();
				SwitchNode sw = (SwitchNode) insn;
				code.add("switch(");
				addArg(code, insn.getArg(0));
				code.add(") {");
				code.incIndent();
				for (int i = 0; i < sw.getCasesCount(); i++) {
					String key = sw.getKeys()[i].toString();
					code.startLine("case ").add(key).add(": goto ");
					code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
				}
				code.startLine("default: goto ");
				code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
				code.decIndent();
				code.startLine('}');
				break;

			case FILL_ARRAY:
				assert isFallback();
				FillArrayNode arrayNode = (FillArrayNode) insn;
				Object data = arrayNode.getData();
				String arrStr;
				if (data instanceof int[]) {
					arrStr = Arrays.toString((int[]) data);
				} else if (data instanceof short[]) {
					arrStr = Arrays.toString((short[]) data);
				} else if (data instanceof byte[]) {
					arrStr = Arrays.toString((byte[]) data);
				} else if (data instanceof long[]) {
					arrStr = Arrays.toString((long[]) data);
				} else {
					arrStr = "?";
				}
				code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
				break;

			case NEW_INSTANCE:
				// only fallback - make new instance in constructor invoke
				assert isFallback();
				code.add("new " + insn.getResult().getType());
				break;

			default:
				throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
		}
	}

	private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
		code.add("new ");
		useType(code, insn.getArrayType());
		code.add('{');
		int c = insn.getArgsCount();
		for (int i = 0; i < c; i++) {
			addArg(code, insn.getArg(i), false);
			if (i + 1 < c) {
				code.add(", ");
			}
		}
		code.add('}');
	}

	private void makeConstructor(ConstructorInsn insn, CodeWriter code)
			throws CodegenException {
		ClassNode cls = mth.dex().resolveClass(insn.getClassType());
		if (cls != null && cls.isAnonymous() && !fallback) {
			// anonymous class construction
			ArgType parent;
			if (cls.getInterfaces().size() == 1) {
				parent = cls.getInterfaces().get(0);
			} else {
				parent = cls.getSuperClass();
			}
			cls.add(AFlag.DONT_GENERATE);
			MethodNode defCtr = cls.getDefaultConstructor();
			if (defCtr != null) {
				if (RegionUtils.notEmpty(defCtr.getRegion())) {
					defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
				} else {
					defCtr.add(AFlag.DONT_GENERATE);
				}
			}
			code.add("new ");
			if (parent == null) {
				code.add("Object");
			} else {
				useClass(code, parent);
			}
			code.add("() ");
			new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).addClassBody(code);
			return;
		}
		if (insn.isSelf()) {
			throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
		}
		if (insn.isSuper()) {
			code.add("super");
		} else if (insn.isThis()) {
			code.add("this");
		} else {
			code.add("new ");
			useClass(code, insn.getClassType());
		}
		generateMethodArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
	}

	private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
		MethodInfo callMth = insn.getCallMth();

		// inline method
		MethodNode callMthNode = mth.dex().resolveMethod(callMth);
		if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
			return;
		}

		int k = 0;
		InvokeType type = insn.getInvokeType();
		switch (type) {
			case DIRECT:
			case VIRTUAL:
			case INTERFACE:
				InsnArg arg = insn.getArg(0);
				// FIXME: add 'this' for equals methods in scope
				if (!arg.isThis()) {
					addArgDot(code, arg);
				}
				k++;
				break;

			case SUPER:
				// use 'super' instead 'this' in 0 arg
				code.add("super").add('.');
				k++;
				break;

			case STATIC:
				ClassInfo insnCls = mth.getParentClass().getAlias();
				ClassInfo declClass = callMth.getDeclClass();
				if (!insnCls.equals(declClass)) {
					useClass(code, declClass);
					code.add('.');
				}
				break;
		}
		if (callMthNode != null) {
			code.attachAnnotation(callMthNode);
		}
		code.add(callMth.getName());
		generateMethodArguments(code, insn, k, callMthNode);
	}

	private void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
			@Nullable MethodNode callMth) throws CodegenException {
		int k = startArgNum;
		if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
			k++;
		}
		int argsCount = insn.getArgsCount();
		code.add('(');
		if (k < argsCount) {
			boolean overloaded = callMth != null && callMth.isArgsOverload();
			for (int i = k; i < argsCount; i++) {
				InsnArg arg = insn.getArg(i);
				boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
				if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
					continue;
				}
				addArg(code, arg, false);
				if (i < argsCount - 1) {
					code.add(", ");
				}
			}
		}
		code.add(')');
	}

	/**
	 * Add additional cast for overloaded method argument.
	 */
	private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
		ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
		if (!arg.getType().equals(origType)) {
			code.add('(');
			useType(code, origType);
			code.add(") ");
			return true;
		}
		return false;
	}

	/**
	 * Expand varArgs from filled array.
	 */
	private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
		if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
			return false;
		}
		if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
			return false;
		}
		InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
		if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
			int count = insn.getArgsCount();
			for (int i = 0; i < count; i++) {
				InsnArg elemArg = insn.getArg(i);
				addArg(code, elemArg, false);
				if (i < count - 1) {
					code.add(", ");
				}
			}
			return true;
		}
		return false;
	}

	private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
		MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
		if (mia == null) {
			return false;
		}
		InsnNode inl = mia.getInsn();
		if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
			makeInsn(inl, code, Flags.BODY_ONLY);
		} else {
			// remap args
			InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
			List<RegisterArg> callArgs = callMthNode.getArguments(true);
			for (int i = 0; i < callArgs.size(); i++) {
				InsnArg arg = insn.getArg(i);
				RegisterArg callArg = callArgs.get(i);
				regs[callArg.getRegNum()] = arg;
			}
			// replace args
			List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
			inl.getRegisterArgs(inlArgs);
			Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
			for (RegisterArg r : inlArgs) {
				int regNum = r.getRegNum();
				if (regNum >= regs.length) {
					LOG.warn("Unknown register number {} in method call: {} from {}", r, callMthNode, mth);
				} else {
					InsnArg repl = regs[regNum];
					if (repl == null) {
						LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
					} else {
						inl.replaceArg(r, repl);
						toRevert.put(r, repl);
					}
				}
			}
			makeInsn(inl, code, Flags.BODY_ONLY);
			// revert changes in 'MethodInlineAttr'
			for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
				inl.replaceArg(e.getValue(), e.getKey());
			}
		}
		return true;
	}

	private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
		boolean wrap = state.contains(Flags.BODY_ONLY);
		if (wrap) {
			code.add('(');
		}
		InsnArg first = insn.getArg(0);
		InsnArg second = insn.getArg(1);
		ConditionGen condGen = new ConditionGen(this);
		if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
			condGen.add(code, insn.getCondition());
		} else {
			condGen.wrap(code, insn.getCondition());
			code.add(" ? ");
			addArg(code, first, false);
			code.add(" : ");
			addArg(code, second, false);
		}
		if (wrap) {
			code.add(')');
		}
	}

	private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
		if (insn.contains(AFlag.ARITH_ONEARG)) {
			makeArithOneArg(insn, code);
			return;
		}
		// wrap insn in brackets for save correct operation order
		boolean wrap = state.contains(Flags.BODY_ONLY) && !insn.contains(AFlag.DONT_WRAP);
		if (wrap) {
			code.add('(');
		}
		addArg(code, insn.getArg(0));
		code.add(' ');
		code.add(insn.getOp().getSymbol());
		code.add(' ');
		addArg(code, insn.getArg(1));
		if (wrap) {
			code.add(')');
		}
	}

	private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
		ArithOp op = insn.getOp();
		InsnArg arg = insn.getArg(1);
		// "++" or "--"
		if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
			LiteralArg lit = (LiteralArg) arg;
			if (lit.isInteger() && lit.getLiteral() == 1) {
				assignVar(code, insn);
				String opSymbol = op.getSymbol();
				code.add(opSymbol).add(opSymbol);
				return;
			}
		}
		// +=, -= ...
		assignVar(code, insn);
		code.add(' ').add(op.getSymbol()).add("= ");
		addArg(code, arg, false);
	}
}
