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 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.RETURN: {
				break;
			}
			case Constants.GETSTATIC: {
				jcfaCode.getComment()
						.getCommentList()
						.add(JcfaUtil.getConstantFieldrefString(jc, codes[1],
								codes[2]));
				break;
			}
			case Constants.LDC: {
				jcfaCode.getComment().getCommentList()
						.add(JcfaUtil.getConstantString(jc, codes[1]));
			}
				break;
			case Constants.INVOKEVIRTUAL:
			case Constants.INVOKESPECIAL: {
				final int operand = JcfaUtil.byte2UnsignedShort(codes[1],
						codes[2]);
				jcfaCode.getComment().getCommentList()
						.add(JcfaUtil.getConstantMethodRefString(jc, operand));
			}
				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;
			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");
	}
}
