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.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.InsnList;
import org.objectweb.asm.tree.MethodNode;

import shadersmod.common.SMCLog;


import net.minecraft.launchwrapper.IClassTransformer;

// for RenderBlocks

public class SMCCTRenderBlocks 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 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.renderBlocks_renderBlockByRenderType.equalsNameDesc(name, desc))
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,remappedName);
				return new MVrenBlkByRenType(
						cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else
			if (Names.renderBlocks_renderBlockFlowerPot.equalsNameDesc(name, desc))
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,remappedName);
				return new MVrenBlkFlowerPot(
						cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else
			if (Names.renderBlocks_renderStdBlockWithAOP.equalsNameDesc(name, desc) ||
				Names.renderBlocks_renderStdBlockWithAO.equalsNameDesc(name, desc)
				)
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,remappedName);
				return new MVrenBlkWithAO(access, name, desc, signature, exceptions,
						cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else
			if (Names.renderBlocks_renderStdBlockWithCM.equalsNameDesc(name, desc) ||
				Names.renderBlocks_renderBlockCactusImpl.equalsNameDesc(name, desc) ||
				Names.renderBlocks_renderBlockBed.equalsNameDesc(name, desc) ||
				Names.renderBlocks_renderBlockFluids.equalsNameDesc(name, desc) ||
				Names.renderBlocks_renderBlockDoor.equalsNameDesc(name, desc) ||
				Names.renderBlocks_renderBlockSandFalling.equalsNameDesc(name, desc)
				)
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,remappedName);
				return new MVrenBlkFVar(
						cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else
			if (Names.renderBlocks_renderPistonExtension.equalsNameDesc(name, desc))
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,remappedName);
				return new MVrenBlkPistonExt(
						cv.visitMethod(access, name, desc, signature, exceptions));
			}
			return cv.visitMethod(access, name, desc, signature, exceptions);
		}
		
	}

	private static class MVrenBlkByRenType extends MethodVisitor
	{
		public MVrenBlkByRenType(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}
		int nPatch = 0;

		@Override
		public void visitCode() {
			mv.visitCode();
			mv.visitVarInsn(ALOAD, 0);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitVarInsn(ILOAD, 2);
			mv.visitVarInsn(ILOAD, 3);
			mv.visitVarInsn(ILOAD, 4);
			mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "pushEntity", "("+Names.renderBlocks_.desc+Names.block_.desc+"III)V");
			//SMCLog.info("    pushEntity");
			++nPatch;
		}

		@Override
		public void visitInsn(int opcode) {
			if (opcode==Opcodes.IRETURN) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "popEntity", "()V");
				//SMCLog.info("    popEntity");
				++nPatch;
			}
			mv.visitInsn(opcode);
		}

		@Override
		public void visitEnd() {
			mv.visitEnd();
			//SMCLog.info("    %d", nPatch);
		}
		
	}

	private static class MVrenBlkFlowerPot extends MethodVisitor
	{
		public MVrenBlkFlowerPot(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}
		int nPatch = 0;
		int state = 0;

		@Override
		public void visitVarInsn(int opcode, int var) {
			super.visitVarInsn(opcode, var);
			if (state==1) {
				++state;
				mv.visitVarInsn(ALOAD, var);
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "pushEntity", "("+Names.block_.desc+")V");
			}
		}

		@Override
		public void visitMethodInsn(int opcode, String owner, String name,
				String desc) {
			super.visitMethodInsn(opcode, owner, name, desc);
			switch (state) {
			case 0:
				if (Names.block_getBlockFromItem.equals(owner, name, desc)) {
					++state;
				}
				break;
			case 2:
				if (Names.tessellator_addTranslation.equals(owner, name, desc)) {
					++state;
				}
				break;
			case 3:
				if (Names.tessellator_addTranslation.equals(owner, name, desc)) {
					++state;
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "popEntity", "()V");
				}
				break;
			}
		}

		@Override
		public void visitCode() {
			mv.visitCode();
			mv.visitVarInsn(ALOAD, 0);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitVarInsn(ILOAD, 2);
			mv.visitVarInsn(ILOAD, 3);
			mv.visitVarInsn(ILOAD, 4);
			mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "pushEntity", "("+Names.renderBlocks_.desc+Names.block_.desc+"III)V");
			//SMCLog.info("    pushEntity");
			++nPatch;
		}

		@Override
		public void visitInsn(int opcode) {
			if (opcode==Opcodes.IRETURN) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "popEntity", "()V");
				//SMCLog.info("    popEntity");
				++nPatch;
			}
			mv.visitInsn(opcode);
		}

		@Override
		public void visitEnd() {
			mv.visitEnd();
			//SMCLog.info("    %d", nPatch);
		}
		
	}

	static final String[] fieldsBlockLightLevel = {null,"blockLightLevel05","blockLightLevel06","blockLightLevel08"}; 
	
	private static class MVrenBlkWithAO extends MethodVisitor
	{
		MethodVisitor mv1;
		MethodNode mn;
		int nPatch = 0;
		public MVrenBlkWithAO(final int access, final String name, final String desc,
	            final String signature, final String[] exceptions,
	            MethodVisitor mv) {
			super(Opcodes.ASM4);
			super.mv = this.mn = new MethodNode(access, name, desc, signature, exceptions);
			this.mv1 = mv;
		}
		@Override
		public void visitEnd() {
			mn.visitEnd();
			mn.accept(mv1);
			//SMCLog.info("    %d", nPatch);
		}
		
		@Override
		public void visitLdcInsn(Object cst) {
			int match1 = 0;
			if (cst instanceof Float) {
				float fcst = ((Float)cst).floatValue();
				if      (fcst == 0.5f) match1 = 1;
				else if (fcst == 0.6f) match1 = 2;
				else if (fcst == 0.8f) match1 = 3;
			}
			if (match1 != 0) {
				int match2 = 0;
				InsnList insns = mn.instructions;
				AbstractInsnNode insn = insns.getLast();
				if (insn!=null && insn.getOpcode()==Opcodes.FLOAD) {
					insn=insn.getPrevious();
					if (insn!=null && insn.getOpcode()==Opcodes.ALOAD) {
						insn=insn.getPrevious();
						if (insn!=null && insn.getOpcode()==Opcodes.ALOAD) {
							insn=insn.getPrevious();
							if (insn!=null && insn.getOpcode()==Opcodes.ALOAD) {
								insn=insn.getPrevious();
								if (insn!=null && insn.getOpcode()==Opcodes.ALOAD) {
									match2 = match1;								
								}
							}
						}
					}
				}
				if (insn!=null && insn.getOpcode()==Opcodes.ALOAD) {
					insn=insn.getPrevious();
					if (insn!=null && insn.getOpcode()==Opcodes.ALOAD) {
						insn=insn.getPrevious();
						if (insn!=null && insn.getOpcode()==Opcodes.ALOAD) {
							insn=insn.getPrevious();
							if (insn!=null && insn.getOpcode()==Opcodes.ALOAD) {
								match2 = match1;								
							}
						}
					}
				}
				if (match2 != 0) {
					String fieldName = fieldsBlockLightLevel[match2];
					mn.visitFieldInsn(GETSTATIC, "shadersmod/client/Shaders", fieldName, "F");
					//SMCLog.info("    %s", fieldName);
					++nPatch;
					return;
				}
			}
			mn.visitLdcInsn(cst);
		}
		
	}

	private static class MVrenBlkFVar extends MethodVisitor
	{
		int nPatch = 0;
		public MVrenBlkFVar(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}
		
		int state = 0;

		@Override
		public void visitLdcInsn(Object cst) {
			int match1 = 0;
			if (cst instanceof Float) {
				float fcst = ((Float)cst).floatValue();
				if      (fcst == 0.5f) match1 = 1;
				else if (fcst == 0.6f) match1 = 2;
				else if (fcst == 0.8f) match1 = 3;
			}
			if (match1 != 0 && state < 3) {
				++state;
				String fieldName = fieldsBlockLightLevel[match1];
				mv.visitFieldInsn(GETSTATIC, "shadersmod/client/Shaders", fieldName, "F");
				//SMCLog.info("    %s", fieldName);
				++nPatch;
				return;
			}
			mv.visitLdcInsn(cst);
		}
		
		@Override
		public void visitEnd() {
			mv.visitEnd();
			//SMCLog.info("    %d", nPatch);
		}
	}

	private static class MVrenBlkPistonExt extends MethodVisitor
	{
		int nPatch = 0;
		public MVrenBlkPistonExt(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}
		int state = 0;

		@Override
		public void visitTableSwitchInsn(int min, int max, Label dflt,
				Label... labels) {
			state = 1;
			mv.visitTableSwitchInsn(min, max, dflt, labels);
		}

		@Override
		public void visitLdcInsn(Object cst) {
			int match1 = 0;
			if (cst instanceof Float) {
				float fcst = ((Float)cst).floatValue();
				if      (fcst == 0.5f) match1 = 1;
				else if (fcst == 0.6f) match1 = 2;
				else if (fcst == 0.8f) match1 = 3;
			}
			if (match1 != 0 && state == 1) {
				String fieldName = fieldsBlockLightLevel[match1];
				mv.visitFieldInsn(GETSTATIC, "shadersmod/client/Shaders", fieldName, "F");
				//SMCLog.info("    %s", fieldName);
				++nPatch;
				return;
			}
			mv.visitLdcInsn(cst);
		}
		
		@Override
		public void visitEnd() {
			mv.visitEnd();
			//SMCLog.info("    %d", nPatch);
		}
	}

//	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);
//		}
//	}

}
