package shadersmod.transform;

import java.util.Iterator;
import java.util.ListIterator;

import org.lwjgl.opengl.GL12;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import static org.objectweb.asm.Opcodes.*;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;

import shadersmod.common.SMCLog;


import net.minecraft.launchwrapper.IClassTransformer;

public class SMCCTRendererLivingEntity implements IClassTransformer {

	@Override
	public byte[] transform(String par1, String par2, byte[] par3) {
		SMCLog.fine("transforming %s %s",par1,par2);
		ClassReader cr = new ClassReader(par3);
		ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
		CVTransform cv = new CVTransform(cw);
		cr.accept(cv,0);
		return cw.toByteArray();
	}

	private static class CVTransform extends ClassVisitor
	{
		String classname;
		public CVTransform(ClassVisitor cv)
		{
			super(Opcodes.ASM4, cv);		
		}

		@Override
		public void visit(int version, int access, String name,
				String signature, String superName, String[] interfaces) {
			classname = name;
			//SMCLog.info(" class %s",name);
			cv.visit(version, access, name, signature, superName, interfaces);
		}

		@Override
		public FieldVisitor visitField(int access, String name, String desc,
				String signature, Object value) {
			if (Names.rendererLivingE_mainModel.name.equals(name) || 
				Names.rendererLivingE_renderPassModel.name.equals(name)) {
				access = access & ~(ACC_PUBLIC|ACC_PRIVATE|ACC_PROTECTED) | ACC_PUBLIC;
			}
			return cv.visitField(access, name, desc, signature, value);
		}

		@Override
		public MethodVisitor visitMethod(int access, String name, String desc,
				String signature, String[] exceptions) 
		{
			//SMCLog.info("  method %s.%s%s = %s",classname,name,desc,remappedName);
			if (Names.rendererLivingE_doRender.equalsNameDesc(name, desc))
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,nameM);
				return new MVdoRenderLiving(
						cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else
			if (Names.rendererLivingE_renderLabel.equalsNameDesc(name, desc))
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,nameM);
				return new MVrenderLivingLabel(
						cv.visitMethod(access, name, desc, signature, exceptions));
			}
			return cv.visitMethod(access, name, desc, signature, exceptions);
		}
		
	}

	private static class MVdoRenderLiving extends MethodVisitor
	{
		public MVdoRenderLiving(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}

		@Override
		public void visitCode() {
			//SMCLog.info("    insert code");
			mv.visitCode();
			mv.visitFieldInsn(GETSTATIC, "shadersmod/client/Shaders", "useEntityHurtFlash", "Z");
			Label label1 = new Label();
			mv.visitJumpInsn(IFEQ, label1);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitFieldInsn(GETFIELD, Names.entityLivingBase_hurtTime.clasName,
					Names.entityLivingBase_hurtTime.name,
					Names.entityLivingBase_hurtTime.desc);
			Label label2 = new Label();
			mv.visitJumpInsn(IFGT, label2);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitFieldInsn(GETFIELD, Names.entityLivingBase_deathTime.clasName,
					Names.entityLivingBase_deathTime.name,
					Names.entityLivingBase_deathTime.desc);
			Label label3 = new Label();
			mv.visitJumpInsn(IFLE, label3);
			mv.visitLabel(label2);
			mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
			mv.visitIntInsn(BIPUSH, 102);
			Label label4 = new Label();
			mv.visitJumpInsn(GOTO, label4);
			mv.visitLabel(label3);
			mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
			mv.visitInsn(ICONST_0);
			mv.visitLabel(label4);
			mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
			mv.visitVarInsn(ALOAD, 0);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitVarInsn(FLOAD, 9);
			mv.visitMethodInsn(INVOKEVIRTUAL, Names.entity_getBrightness.clasName,
					Names.entity_getBrightness.name,
					Names.entity_getBrightness.desc);
			mv.visitVarInsn(FLOAD, 9);
			mv.visitMethodInsn(INVOKEVIRTUAL, Names.rendererLivingE_getColorMultiplier.clasName,
					Names.rendererLivingE_getColorMultiplier.name,
					Names.rendererLivingE_getColorMultiplier.desc);
			mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "setEntityHurtFlash", "(II)V");
			mv.visitLabel(label1);
			mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
			//SMCLog.info("    end insert");
		}

		/** last SIPUSH operand */
		private int lastInt = 0;
		/** state */
		private int state = 0;
		private static final int stateEnd = 7;
		/** end of vanilla hurt rendering */
		Label labelEndVH = null;
		
		@Override
		public void visitIntInsn(int opcode, int operand) {
			if (opcode==SIPUSH)	{
				lastInt = operand;
			}
			mv.visitIntInsn(opcode, operand);
		}

		@Override
		public void visitLdcInsn(Object cst) {
			if (cst instanceof Integer)
			{
				int icst = ((Integer)cst).intValue();
				if (icst == GL12.GL_RESCALE_NORMAL)	{
					//SMCLog.info("    rescale_normal");
					if (labelEndVH != null) {
						//SMCLog.info("    jump target");
						mv.visitLabel(labelEndVH);
						labelEndVH = null;
					}
				}
			}
			mv.visitLdcInsn(cst);
		}

		@Override
		public void visitMethodInsn(int opcode, String owner, String name,
				String desc) {
			//SMCLog.info("    %s.%s%s",ownerM,nameM,descM);
			if (opcode==INVOKEVIRTUAL) {
				if (Names.rendererLivingE_renderEquippedItems.equals(owner, name, desc)) {
					//SMCLog.info("    renderEquippedItems");
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "resetEntityHurtFlash", "()V");
					mv.visitMethodInsn(opcode, owner, name, desc);
					mv.visitFieldInsn(GETSTATIC, "shadersmod/client/Shaders", "useEntityHurtFlash", "Z");
					labelEndVH = new Label();
					mv.visitJumpInsn(IFNE, labelEndVH);
					state = 1;
					return;
				}
			}
			//
			mv.visitMethodInsn(opcode, owner, name, desc);
			//
			if (opcode==INVOKESTATIC) {
				if (Names.equals("org/lwjgl/opengl/GL11","glDepthFunc","(I)V",owner,name,desc)) { 
					//SMCLog.info("    glDepthFunc");
					if (state==3) {
						mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "beginLivingDamage", "()V");
						++state;
					} else if (state==4) {
						mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "endLivingDamage", "()V");
						++state;
					}
				} else if (Names.openGlHelper_setActiveTexture.equals(owner,name,desc)) {
					//SMCLog.info("    setActiveTexture");
					if (state==1) {
						++state;
					} else if (state==2) {
						mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "disableLightmap", "()V");
						++state;
					} else if (state==5) {
						++state;
					} else if (state==6) {
						mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "enableLightmap", "()V");								
						++state;
					}
				}
			}
			
		}
		
		@Override
		public void visitEnd() {
			mv.visitEnd();
			if (state != stateEnd) {
				SMCLog.severe("state %d expected %d", state, stateEnd);
			}
		}
	}
	
	private static class MVrenderLivingLabel extends MethodVisitor
	{
		public MVrenderLivingLabel(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}

		int pushedInt = 0;
		
		@Override
		public void visitIntInsn(int opcode, int operand) {
			mv.visitIntInsn(opcode, operand);
			if (opcode == SIPUSH && operand == 3553)
				pushedInt = 3553;
		}

		@Override
		public void visitMethodInsn(int opcode, String owner, String name,
				String desc) {
			if (pushedInt == 3553)
			{
				pushedInt = 0;
				if (opcode==INVOKESTATIC && owner.equals("org/lwjgl/opengl/GL11") && desc.equals("(I)V"))
				{
					if (name.equals("glDisable"))
					{
						owner = "shadersmod/client/Shaders";
						name = "sglDisableT2D";
					}
					else
					if (name.equals("glEnable"))
					{
						owner = "shadersmod/client/Shaders";
						name = "sglEnableT2D";
					}
				}
			}
			mv.visitMethodInsn(opcode, owner, name, desc);
		}

	}


//	private static class MVNTransform extends MethodVisitor
//	{
//		protected MethodVisitor mv;
//		protected MethodNode mn;
//		public MVNTransform(MethodNode mn, MethodVisitor mv) {
//			super(Opcodes.ASM4);
//			super.mv = this.mn = mn;
//			this.mv = mv;
//		}
//		
//		@Override
//		public void visitEnd() {
//			super.visitEnd();
//			Iterator<AbstractInsnNode> it = mn.instructions.iterator();
//			while (it.hasNext())
//			{
//				AbstractInsnNode node = it.next();
//				System.out.println(node.toString());
//			}
//			// send to target
//			mn.accept(mv);
//		}
//	}

}
