package org.luaj.vm2.luajc;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.apache.bcel.Constants;
import org.apache.bcel.generic.AALOAD;
import org.apache.bcel.generic.AASTORE;
import org.apache.bcel.generic.ANEWARRAY;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.Type;
import org.luaj.vm2.LuaValue;

final class DelegateJavaGen {
	private static final String STR_STRING = String.class.getName();
	private static final String STR_STRING_ARRAY = "[Ljava.lang.String;";
	private static final String STR_LUAJC_DELEGATE = LuaJCDelegateFunction.class.getName();
	private static final String STR_JSEPLATFORM = "org.luaj.vm2.libs.jse.JsePlatform";
	private static final ObjectType TYPE_STRING = new ObjectType(STR_STRING);
	private static final ArrayType TYPE_STRING_ARRAY = new ArrayType(TYPE_STRING, 1);
	private static final ObjectType TYPE_LUAVALUE = new ObjectType(LuaValue.class.getName());
	private static final Type[] ARG_TYPES_NONE = {};
	private static final Type[] ARG_TYPES_STRING_ARRAY = { TYPE_STRING_ARRAY };
	private static final Type[] ARG_TYPES_LUAVALUE_STRING_ARRAY = { TYPE_LUAVALUE, TYPE_STRING_ARRAY };

	final String classname;
	final byte[] bytecode;

	DelegateJavaGen(String classname, String filename, boolean genmain, String[] hexChunks) throws IOException {
		this.classname = classname;
		ClassGen cg = new ClassGen(classname, STR_LUAJC_DELEGATE, filename,
			Constants.ACC_PUBLIC | Constants.ACC_SUPER, null);
		ConstantPoolGen cp = cg.getConstantPool();
		InstructionFactory factory = new InstructionFactory(cg);

		addDefaultConstructor(cg, cp, factory);
		addPrototypeHexChunksMethod(cg, cp, factory, hexChunks);
		if (genmain) {
			addMainMethod(cg, cp, factory, classname);
		}

		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			cg.getJavaClass().dump(baos);
			this.bytecode = baos.toByteArray();
		} catch (IOException ioe) {
			throw new IOException("failed to generate delegated luajc class", ioe);
		}
	}

	private void addDefaultConstructor(ClassGen cg, ConstantPoolGen cp, InstructionFactory factory) {
		InstructionList il = new InstructionList();
		MethodGen mg = new MethodGen(Constants.ACC_PUBLIC, Type.VOID, ARG_TYPES_NONE, new String[] {},
			Constants.CONSTRUCTOR_NAME, cg.getClassName(), il, cp);
		il.append(InstructionConstants.THIS);
		il.append(factory.createInvoke(STR_LUAJC_DELEGATE, Constants.CONSTRUCTOR_NAME, Type.VOID, ARG_TYPES_NONE, Constants.INVOKESPECIAL));
		il.append(InstructionConstants.RETURN);
		mg.setMaxStack();
		cg.addMethod(mg.getMethod());
		il.dispose();
	}

	private void addPrototypeHexChunksMethod(ClassGen cg, ConstantPoolGen cp, InstructionFactory factory, String[] hexChunks) {
		InstructionList il = new InstructionList();
		MethodGen mg = new MethodGen(Constants.ACC_PROTECTED, TYPE_STRING_ARRAY, ARG_TYPES_NONE, new String[] {},
			"prototypeHexChunks", cg.getClassName(), il, cp);
		il.append(new PUSH(cp, hexChunks.length));
		il.append(new ANEWARRAY(cp.addClass(STR_STRING)));
		for (int i = 0; i < hexChunks.length; i++) {
			il.append(InstructionConstants.DUP);
			il.append(new PUSH(cp, i));
			il.append(new PUSH(cp, hexChunks[i]));
			il.append(new AASTORE());
		}
		il.append(InstructionConstants.ARETURN);
		mg.setMaxStack();
		cg.addMethod(mg.getMethod());
		il.dispose();
	}

	private void addMainMethod(ClassGen cg, ConstantPoolGen cp, InstructionFactory factory, String classname) {
		InstructionList il = new InstructionList();
		MethodGen mg = new MethodGen(Constants.ACC_PUBLIC | Constants.ACC_STATIC, Type.VOID,
			ARG_TYPES_STRING_ARRAY, new String[] { "arg" }, "main", classname, il, cp);
		il.append(factory.createNew(classname));
		il.append(InstructionConstants.DUP);
		il.append(factory.createInvoke(classname, Constants.CONSTRUCTOR_NAME, Type.VOID, ARG_TYPES_NONE, Constants.INVOKESPECIAL));
		il.append(InstructionConstants.ALOAD_0);
		il.append(factory.createInvoke(STR_JSEPLATFORM, "luaMain", Type.VOID, ARG_TYPES_LUAVALUE_STRING_ARRAY, Constants.INVOKESTATIC));
		il.append(InstructionConstants.RETURN);
		mg.setMaxStack();
		cg.addMethod(mg.getMethod());
		il.dispose();
	}
}
