package jp.igapyon.jcfa.util;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

import jp.igapyon.jcfa.vo.JcfaClass;
import jp.igapyon.jcfa.vo.JcfaCode;
import jp.igapyon.jcfa.vo.JcfaComment;
import jp.igapyon.jcfa.vo.JcfaField;
import jp.igapyon.jcfa.vo.JcfaMethod;
import jp.igapyon.jcfa.vo.JcfaUnit;
import jp.igapyon.jcfa.vo.item.JcfaItem;
import jp.igapyon.jcfa.vo.item.JcfaItemLocalVariable;
import jp.igapyon.jcfa.vo.item.JcfaItemReference;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;

public class JcfaWriteUtil {
	public static void writeToFile(final JcfaUnit jcfaUnit) throws IOException {
		final StringBuffer result = new StringBuffer();

		for (JcfaClass jcfaClass : jcfaUnit.getClassList()) {
			writeClass(jcfaClass, result);
		}

		final BufferedWriter writer = new BufferedWriter(
				new OutputStreamWriter(new FileOutputStream(
						jcfaUnit.getTargetFile())));
		writer.write(JcfaEclipseUtil.formatSource(result.toString()));
		writer.close();
	}

	/**
	 * Write class
	 * 
	 * @param jcfaClass
	 * @param result
	 * @throws IOException
	 */
	public static void writeClass(final JcfaClass jcfaClass,
			final StringBuffer result) throws IOException {

		if (jcfaClass.isMainClass()) {
			if (jcfaClass.getName().contains(".")) {
				result.append(" package "
						+ jcfaClass.getName().substring(0,
								jcfaClass.getName().lastIndexOf(".")) + ";");
			}
		}

		writeComment(jcfaClass.getComment(), result);

		result.append(jcfaClass.getAccess());
		result.append(" class " + jcfaClass.getLocalName());
		if (jcfaClass.getExtendsName() != null
				&& jcfaClass.getExtendsName().length() > 0
				&& jcfaClass.getExtendsName().equals("java.lang.Object") == false) {
			result.append(" extends " + jcfaClass.getExtendsName());
		}
		result.append("{");

		for (JcfaField jcfaField : jcfaClass.getFieldList()) {
			writeField(jcfaField, result);
		}

		for (JcfaMethod jcfaMethod : jcfaClass.getMethodList()) {
			writeMethod(jcfaClass, jcfaMethod, result);
		}

		result.append("}");
	}

	/**
	 * Write field.
	 * 
	 * @param jcfaField
	 * @param result
	 */
	public static void writeField(final JcfaField jcfaField,
			final StringBuffer result) {
		writeComment(jcfaField.getComment(), result);

		result.append(" " + jcfaField.getAccess() + " " + jcfaField.getType()
				+ " " + jcfaField.getName());
		if (jcfaField.getConstantValue() != null) {
			result.append(" = ");
			result.append(jcfaField.getConstantValue());
		}
		result.append(";");
	}

	/**
	 * Write method.
	 * 
	 * @param jcfaClass
	 * @param jcfaMethod
	 * @param result
	 * @throws IOException
	 */
	public static void writeMethod(final JcfaClass jcfaClass,
			final JcfaMethod jcfaMethod, final StringBuffer result)
			throws IOException {

		writeComment(jcfaMethod.getComment(), result);

		if (jcfaMethod.getName().equals("<init>")) {
			result.append("public " + jcfaClass.getLocalName() + "(");
		} else {
			result.append("public " + jcfaMethod.getType() + " "
					+ jcfaMethod.getName() + "(");
		}

		int argNo = 0;
		for (String argumentType : jcfaMethod.getArugumentTypeList()) {
			if (argNo != 0) {
				result.append(", ");
			}
			result.append(argumentType);
			result.append(" arg" + argNo);
		}

		result.append(")");

		result.append("{");

		writeCodes(jcfaClass, jcfaMethod, result);

		result.append("}");
	}

	public static void writeCodes(final JcfaClass jcfaClass,
			final JcfaMethod jcfaMethod, final StringBuffer result)
			throws IOException {
		for (JcfaCode jcfaCode : jcfaMethod.getCodeList()) {
			final byte[] codes = jcfaCode.getCodes();
			final JavaClass jc = jcfaCode.getJavaClass();

			switch (jcfaCode.getOpcode()) {
			case Constants.NOP:
			case Constants.ACONST_NULL:
			case Constants.ICONST_M1:
			case Constants.ICONST_0:
			case Constants.ICONST_1:
			case Constants.ICONST_2:
			case Constants.ICONST_3:
			case Constants.ICONST_4:
			case Constants.ICONST_5:
			case Constants.LCONST_0:
			case Constants.LCONST_1:
			case Constants.FCONST_0:
			case Constants.FCONST_1:
			case Constants.FCONST_2:
			case Constants.DCONST_0:
			case Constants.DCONST_1:
			case Constants.BIPUSH:
			case Constants.SIPUSH:
				jcfaCode.getComment().getCommentList()
						.add("TODO unsupported opcode");
				break;
			case Constants.LDC: {
				final JcfaItemReference osString = new JcfaItemReference();
				jcfaMethod.getFrame().getOperandStack().push(osString);
				osString.setObject(JcfaUtil.getConstantString(jc, codes[1]));

				jcfaCode.getComment().getCommentList()
						.add(osString.getObject());
				break;
			}
			case Constants.LDC_W:
			case Constants.LDC2_W:
			case Constants.ILOAD:
			case Constants.LLOAD:
			case Constants.FLOAD:
			case Constants.DLOAD:
				jcfaCode.getComment().getCommentList()
						.add("TODO unsupported opcode");
				break;
			case Constants.ALOAD:
			case Constants.ILOAD_0:
			case Constants.ILOAD_1:
			case Constants.ILOAD_2:
			case Constants.ILOAD_3:
			case Constants.LLOAD_0:
			case Constants.LLOAD_1:
			case Constants.LLOAD_2:
			case Constants.LLOAD_3:
			case Constants.FLOAD_0:
			case Constants.FLOAD_1:
			case Constants.FLOAD_2:
			case Constants.FLOAD_3:
			case Constants.DLOAD_0:
			case Constants.DLOAD_1:
			case Constants.DLOAD_2:
			case Constants.DLOAD_3:
				jcfaCode.getComment().getCommentList()
						.add("TODO unsupported opcode");
				break;
			case Constants.ALOAD_0: {
				final JcfaItemLocalVariable osLocalVariable = jcfaMethod
						.getFrame().getLocalVariable(0);
				jcfaMethod.getFrame().getOperandStack().push(osLocalVariable);

				jcfaCode.getComment().getCommentList()
						.add(osLocalVariable.toDisplayString());

				break;
			}
			case Constants.ALOAD_1:
			case Constants.ALOAD_2:
			case Constants.ALOAD_3:
			case Constants.IALOAD:
			case Constants.LALOAD:
			case Constants.FALOAD:
			case Constants.DALOAD:
			case Constants.AALOAD:
			case Constants.BALOAD:
			case Constants.CALOAD:
			case Constants.SALOAD:
			case Constants.ISTORE:
			case Constants.LSTORE:
			case Constants.FSTORE:
			case Constants.DSTORE:
			case Constants.ASTORE:
			case Constants.ISTORE_0:
			case Constants.ISTORE_1:
			case Constants.ISTORE_2:
			case Constants.ISTORE_3:
			case Constants.LSTORE_0:
			case Constants.LSTORE_1:
			case Constants.LSTORE_2:
			case Constants.LSTORE_3:
			case Constants.FSTORE_0:
			case Constants.FSTORE_1:
			case Constants.FSTORE_2:
			case Constants.FSTORE_3:
			case Constants.DSTORE_0:
			case Constants.DSTORE_1:
			case Constants.DSTORE_2:
			case Constants.DSTORE_3:
			case Constants.ASTORE_0:
			case Constants.ASTORE_1:
			case Constants.ASTORE_2:
			case Constants.ASTORE_3:
			case Constants.IASTORE:
			case Constants.LASTORE:
			case Constants.FASTORE:
			case Constants.DASTORE:
			case Constants.AASTORE:
			case Constants.BASTORE:
			case Constants.CASTORE:
			case Constants.SASTORE:
			case Constants.POP:
			case Constants.POP2:
			case Constants.DUP:
			case Constants.DUP_X1:
			case Constants.DUP_X2:
			case Constants.DUP2:
			case Constants.DUP2_X1:
			case Constants.DUP2_X2:
			case Constants.SWAP:
			case Constants.IADD:
			case Constants.LADD:
			case Constants.FADD:
			case Constants.DADD:
			case Constants.ISUB:
			case Constants.LSUB:
			case Constants.FSUB:
			case Constants.DSUB:
			case Constants.IMUL:
			case Constants.LMUL:
			case Constants.FMUL:
			case Constants.DMUL:
			case Constants.IDIV:
			case Constants.LDIV:
			case Constants.FDIV:
			case Constants.DDIV:
			case Constants.IREM:
			case Constants.LREM:
			case Constants.FREM:
			case Constants.DREM:
			case Constants.INEG:
			case Constants.LNEG:
			case Constants.FNEG:
			case Constants.DNEG:
			case Constants.ISHL:
			case Constants.LSHL:
			case Constants.ISHR:
			case Constants.LSHR:
			case Constants.IUSHR:
			case Constants.LUSHR:
			case Constants.IAND:
			case Constants.LAND:
			case Constants.IOR:
			case Constants.LOR:
			case Constants.IXOR:
			case Constants.LXOR:
			case Constants.IINC:
			case Constants.I2L:
			case Constants.I2F:
			case Constants.I2D:
			case Constants.L2I:
			case Constants.L2F:
			case Constants.L2D:
			case Constants.F2I:
			case Constants.F2L:
			case Constants.F2D:
			case Constants.D2I:
			case Constants.D2L:
			case Constants.D2F:
			case Constants.I2B:
			case Constants.I2C:
			case Constants.I2S:
			case Constants.LCMP:
			case Constants.FCMPL:
			case Constants.FCMPG:
			case Constants.DCMPL:
			case Constants.DCMPG:
			case Constants.IFEQ:
			case Constants.IFNE:
			case Constants.IFLT:
			case Constants.IFGE:
			case Constants.IFGT:
			case Constants.IFLE:
			case Constants.IF_ICMPEQ:
			case Constants.IF_ICMPNE:
			case Constants.IF_ICMPLT:
			case Constants.IF_ICMPGE:
			case Constants.IF_ICMPGT:
			case Constants.IF_ICMPLE:
			case Constants.IF_ACMPEQ:
			case Constants.IF_ACMPNE:
			case Constants.GOTO:
			case Constants.JSR:
			case Constants.RET:
			case Constants.TABLESWITCH:
				jcfaCode.getComment().getCommentList()
						.add("TODO unsupported opcode");
				break;
			case Constants.LOOKUPSWITCH: {
				if (true) {
					jcfaCode.getComment().getCommentList()
							.add("  TODO temporary disabled.");
					break;
				}
				int skipBytes = JcfaUtil.byte2Int(codes[1], codes[2], codes[3],
						codes[4]);

				jcfaCode.getComment().getCommentList()
						.add("  TODO skipping bytes: " + (skipBytes));

				int lookupOp = 5;

				short diff = JcfaUtil.byte2UnsignedByte(codes[lookupOp++]);
				jcfaCode.getComment().getCommentList()
						.add("  TODO skipping bytes: " + (diff));

				int loopCount = JcfaUtil
						.byte2Int(codes[lookupOp++], codes[lookupOp++],
								codes[lookupOp++], codes[lookupOp++]);
				for (int index = 0; index < loopCount; index++) {
					jcfaCode.getComment()
							.getCommentList()
							.add(JcfaUtil.byte2Int(codes[lookupOp++],
									codes[lookupOp++], codes[lookupOp++],
									codes[lookupOp++])
									+ ":"
									+ (JcfaUtil.byte2Int(codes[lookupOp++],
											codes[lookupOp++],
											codes[lookupOp++],
											codes[lookupOp++])));
				}

				short diff2 = JcfaUtil.byte2UnsignedByte(codes[lookupOp++]);
				jcfaCode.getComment().getCommentList()
						.add("  TODO skipping bytes: " + (diff2));

				break;
			}
			case Constants.IRETURN:
			case Constants.LRETURN:
			case Constants.FRETURN:
			case Constants.DRETURN:
			case Constants.ARETURN:
				jcfaCode.getComment().getCommentList()
						.add("TODO unsupported opcode");
				break;
			case Constants.RETURN: {
				break;
			}
			case Constants.GETSTATIC: {
				final JcfaItemReference osRef = new JcfaItemReference();
				jcfaMethod.getFrame().getOperandStack().push(osRef);
				osRef.setObject(JcfaUtil.getConstantFieldrefString(jc,
						codes[1], codes[2]));

				jcfaCode.getComment().getCommentList().add(osRef.getObject());
				break;
			}
			case Constants.PUTSTATIC:
			case Constants.GETFIELD:
			case Constants.PUTFIELD:
				jcfaCode.getComment().getCommentList()
						.add("TODO unsupported opcode");
				break;
			case Constants.INVOKEVIRTUAL:
			case Constants.INVOKESPECIAL: {
				final int operand = JcfaUtil.byte2UnsignedShort(codes[1],
						codes[2]);
				jcfaCode.getComment().getCommentList()
						.add(JcfaUtil.getConstantMethodRefString(jc, operand));

				jcfaCode.getComment().getCommentList()
						.add("TODO get args count from signature.");
				// get n args.
				final JcfaItem osNodeArg0 = jcfaMethod.getFrame()
						.getOperandStack().pop();

				final JcfaItem osRef = jcfaMethod.getFrame().getOperandStack()
						.pop();

				jcfaCode.getComment()
						.getCommentList()
						.add("" + osRef.toString() + "#"
								+ osNodeArg0.toString());

				break;
			}
			case Constants.INVOKESTATIC:
			case Constants.INVOKEINTERFACE:
			case Constants.NEW:
			case Constants.NEWARRAY:
			case Constants.ANEWARRAY:
			case Constants.ARRAYLENGTH:
			case Constants.ATHROW:
			case Constants.CHECKCAST:
			case Constants.INSTANCEOF:
			case Constants.MONITORENTER:
			case Constants.MONITOREXIT:
			case Constants.WIDE:
			case Constants.MULTIANEWARRAY:
			case Constants.IFNULL:
			case Constants.IFNONNULL:
			case Constants.GOTO_W:
			case Constants.JSR_W:
				jcfaCode.getComment().getCommentList()
						.add("TODO unsupported opcode");
				break;
			default:
				jcfaCode.getComment().getCommentList()
						.add("TODO unsupported opcode");
				break;
			}

			writeComment(jcfaCode.getComment(), result);

			// TODO and code...
		}
	}

	/**
	 * Write comment.
	 * 
	 * @param jcfaComment
	 * @param result
	 */
	public static void writeComment(final JcfaComment jcfaComment,
			final StringBuffer result) {
		if (jcfaComment.isJavaDoc()) {
			result.append("\n/** ");
		} else {
			result.append("\n/* ");
		}

		if (jcfaComment.getCommentList().size() > 1) {
			result.append("\n");
		}

		for (String comment : jcfaComment.getCommentList()) {
			if (jcfaComment.getCommentList().size() > 1) {
				result.append(" * " + comment + "\n");
			} else {
				result.append(comment);
			}
		}

		result.append(" */\n");
	}
}
