package shadersmod.transform;

import java.util.Iterator;

import org.lwjgl.opengl.GL11;
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.MethodNode;

import shadersmod.common.SMCLog;

import net.minecraft.launchwrapper.IClassTransformer;

// for RenderGlobal

public class STRenderGlobal 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) {
			return cv.visitField(access, name, desc, signature, value);
		}

		@Override
		public MethodVisitor visitMethod(int access, String name, String desc,
				String signature, String[] exceptions) 
		{
			//String remappedName = SMCRemap.remapper.mapMethodName(classname, name, desc);
			//SMCLog.info("  method %s.%s%s = %s",classname,name,desc,remappedName);
			if (Names.renderGlobal_setupTerrain.equalsNameDesc(name, desc))
			{
				SMCLog.finer("  patch method %s.%s%s",classname,name,desc);
				return new MVsetupTerrain(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else if (Names.renderGlobal_renderSky.equalsNameDesc(name, desc))
			{
				SMCLog.finer("  patch method %s.%s%s",classname,name,desc);
				return new MVrenderSky(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else if (Names.renderGlobal_renderBlockLayer1.equalsNameDesc(name, desc))
			{
				SMCLog.finer("  patch method %s.%s%s",classname,name,desc);
				return new MVrenderBlockLayer1(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else if (Names.renderGlobal_renderEntities.equalsNameDesc(name, desc))
			{
				SMCLog.finer("  patch method %s.%s%s",classname,name,desc);
				return new MVrenderEntities(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else if (Names.renderGlobal_preRenderBlockDamage.equalsNameDesc(name, desc))
			{
				SMCLog.finer("  patch method %s.%s%s",classname,name,desc);
				return new MVpreRenderBlockDamage(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else if (Names.renderGlobal_postRenderBlockDamage.equalsNameDesc(name, desc))
			{
				SMCLog.finer("  patch method %s.%s%s",classname,name,desc);
				return new MVpostRenderBlockDamage(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else if (Names.renderGlobal_drawSelectionBox.equalsNameDesc(name, desc))
			{
				SMCLog.finer("  patch method %s.%s%s",classname,name,desc);
				return new MVendisTexFog(cv.visitMethod(access, name, desc, signature, exceptions));
			}
//			else if (Names.renderGlobal_sortAndRender.equalsNameDesc(name, desc))
//			{
//				SMCLog.finer("  patch method %s.%s%s",classname,name,desc);
//				return new MVendisTexFog(cv.visitMethod(access, name, desc, signature, exceptions));
//			}
			return cv.visitMethod(access, name, desc, signature, exceptions);
		}
		
	}

	private static class MVrenderEntities extends MethodVisitor
	{
		MethodVisitor mvOut;
		
		public MVrenderEntities(MethodVisitor mv) {
			super(Opcodes.ASM4);
			this.mvOut = mv; 
			this.mv = new State0();
		}
		
		void setNextState(MVState state) {
			this.mv = state;
			//SMCLog.fine("     state %s",state.getClass().getName());
		}
		
		class MVState extends MethodVisitor {
			MVState() { super(Opcodes.ASM4,mvOut); }
			@Override
			public void visitEnd() {
				if (this.getClass()!=StateEnd.class)
					SMCLog.error("  ends in bad state %s",this.getClass().getSimpleName());
				mv.visitEnd();
			}
		}
		
		class State0 extends MVState {
			@Override
			public void visitLdcInsn(Object cst) {
				if (cst instanceof String) {
					String scst = (String)cst;
					if (scst.equals("entities")) {
						setNextState(new State1());
					}
				}
				mv.visitLdcInsn(cst);
			}
		}
		
		class State1 extends MVState {
			@Override
			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
				mv.visitMethodInsn(opcode, owner, name, desc); // endStartSection
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "beginEntities", "()V");
				setNextState(new State2());
			}
			
		}
		class State2 extends MVState {
			@Override
			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
				if (Names.renderManager_renderEntityFirst.equals(owner,name,desc)) {
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "nextEntity", "()V");
					mv.visitMethodInsn(opcode, owner, name, desc);
				} else if (Names.renderManager_renderEntitySecond.equals(owner,name,desc)) {
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "nextEntity", "()V");
					mv.visitMethodInsn(opcode, owner, name, desc);
					setNextState(new State3());
				} else {
					mv.visitMethodInsn(opcode, owner, name, desc);
				}
			}
		}
		class State3 extends MVState {
			@Override
			public void visitLdcInsn(Object cst) {
				if (cst instanceof String) {
					String scst = (String)cst;
					if (scst.equals("blockentities")) {
						setNextState(new State4());
					}
				}
				mv.visitLdcInsn(cst);
			}
		}
		class State4 extends MVState {
			@Override
			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
				mv.visitMethodInsn(opcode, owner, name, desc);
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "endEntities", "()V");
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "beginBlockEntities", "()V");
				setNextState(new State5());
			}
		}
		class State5 extends MVState {
			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
				mv.visitMethodInsn(opcode, owner, name, desc);
				if (Names.equals("java/util/Iterator", "next", "()Ljava/lang/Object;", owner, name, desc)) {
					setNextState(new State6());
				}
			}
		}
		class State6 extends MVState {
			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
				mv.visitMethodInsn(opcode, owner, name, desc);
				if (Names.equals("java/util/Iterator", "next", "()Ljava/lang/Object;", owner, name, desc)) {
					setNextState(new State7());
				}
			}
		}
		class State7 extends MVState {
			public void visitVarInsn(int opcode, int var) {
				mv.visitVarInsn(opcode, var);
				if (opcode==ASTORE) {
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "nextBlockEntity", "()V");
					setNextState(new State11());
				}
			}
		}
//		class State8 extends MVState {
//			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
//				mv.visitMethodInsn(opcode, owner, name, desc);
//				if (Names.equals("java/util/Iterator", "hasNext", "()Z", owner, name, desc)) {
//					setNextState(new State9());
//				}
//			}
//		}
//		class State9 extends MVState {
//			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
//				mv.visitMethodInsn(opcode, owner, name, desc);
//				if (Names.equals("java/util/Iterator", "hasNext", "()Z", owner, name, desc)) {
//					setNextState(new State11());
//				}
//			}
//		}
//		class State10 extends MVState {
//			public void visitJumpInsn(int opcode, Label label) {
//				mv.visitJumpInsn(opcode, label);
//				setNextState(new State11());
//			}
//		}
		class State11 extends MVState {
			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
				if (Names.renderGlobal_preRenderBlockDamage.equals(owner, name, desc)) {
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "endBlockEntities", "()V");
					setNextState(new State12());
				}
				mv.visitMethodInsn(opcode, owner, name, desc);
			}
		}
		class State12 extends MVState {
			public void visitMethodInsn(int opcode, String owner, String name, String desc) {
				mv.visitMethodInsn(opcode, owner, name, desc);
				if (Names.renderGlobal_postRenderBlockDamage.equals(owner, name, desc)) {
					setNextState(new StateEnd());
				}
			}
		}
		class StateEnd extends MVState {
			@Override
			public void visitEnd() {
				mv.visitEnd();
			}
		}
	}

	/* detect glEnable or glDisable (TEXTURE_2D or FOG) */
	private static class MVendisTexFog extends MethodVisitor
	{
		public MVendisTexFog(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}
		@Override
		public void visitMethodInsn(int opcode, String owner, String name, String desc) {
			mv.visitMethodInsn(opcode, owner, name, desc);
			if (Names.glStateManager_enableFog.equals(owner,name,desc)) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "enableFog", "()V");
			} else if (Names.glStateManager_disableFog.equals(owner,name,desc)) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "disableFog", "()V");
			} else if (Names.glStateManager_enableTexture2D.equals(owner,name,desc)) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "enableTexture2D", "()V");
			} else if (Names.glStateManager_disableTexture2D.equals(owner,name,desc)) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "disableTexture2D", "()V");
			}
		}
	}

	private static class MVrenderSky extends MethodVisitor
	{
		public MVrenderSky(MethodVisitor mv) {
			super(ASM4,mv);
		}
		
		int state = 0;
		
		int lastVar = 0;

		@Override
		public void visitVarInsn(int opcode, int var) {
			mv.visitVarInsn(opcode, var);
			if (opcode == ALOAD) {
				lastVar = var;
			} else {
				lastVar = 0;
			}
		}

		@Override
		public void visitFieldInsn(int opcode, String owner, String name, String desc) {
			switch (state)
			{
			case 0:
				if (Names.vec3_xCoord.equals(owner, name)) {
					++state;
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "setSkyColor", "("+Names.vec3d_.desc+")V");
					mv.visitVarInsn(ALOAD, lastVar);
					break;
				}
				break;
			}
			mv.visitFieldInsn(opcode, owner, name, desc);
		}

		@Override
		public void visitMethodInsn(int opcode, String owner, String name, String desc) {
			//SMCLog.info("    %s.%s%s",ownerM,nameM,descM);
			switch (state) {
			case 1:
				if (Names.glStateManager_color3.equals(owner, name, desc)) {
					++state;
					mv.visitMethodInsn(opcode, owner, name, desc);
					return;
				}
				break;
			case 2:
				if (Names.glStateManager_color3.equals(owner, name, desc)) {
					++state;
					mv.visitMethodInsn(opcode, owner, name, desc);
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "preSkyList", "()V");
					return;
				}
				break;
			case 3:
				if (Names.worldClient_getRainStrength.equals(owner, name, desc)) {
					++state;
				}
				break;
			}
			//
			mv.visitMethodInsn(opcode, owner, name, desc);
			//
			if (Names.glStateManager_enableFog.equals(owner,name,desc)) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "enableFog", "()V");
			} else if (Names.glStateManager_disableFog.equals(owner,name,desc)) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "disableFog", "()V");
			} else if (Names.glStateManager_enableTexture2D.equals(owner,name,desc)) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "enableTexture2D", "()V");
			} else if (Names.glStateManager_disableTexture2D.equals(owner,name,desc)) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "disableTexture2D", "()V");
			} else if (Names.glStateManager_rotate.equals(owner,name,desc)) {
				if (state == 4) {
					++state;
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "preCelestialRotate", "()V");
					//SMCLog.finest("    %s","preCelestialRotate");
				} else if (state == 5) {
					++state;
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "postCelestialRotate", "()V");
					//SMCLog.finest("    %s","postCelestialRotate");
				}
			}
		}
	}

	private static class MVpreRenderBlockDamage extends MethodVisitor
	{
		public MVpreRenderBlockDamage(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}
		@Override
		public void visitInsn(int opcode) {
			if (opcode==RETURN) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/ShadersRender", "beginBlockDamage", "()V");
			}
			mv.visitInsn(opcode);
		}
	}

	private static class MVpostRenderBlockDamage extends MethodVisitor
	{
		public MVpostRenderBlockDamage(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}
		@Override
		public void visitInsn(int opcode) {
			if (opcode==RETURN) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/ShadersRender", "endBlockDamage", "()V");
			}
			mv.visitInsn(opcode);
		}
	}
	
	private static class MVrenderBlockLayer1 extends MethodVisitor
	{
		public MVrenderBlockLayer1(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
		}

		@Override
		public void visitMethodInsn(int opcode, String owner, String name,
				String desc) {
			if (Names.chunkRenderContainer_renderChunkLayer.equals(owner, name, desc)) {
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/ShadersRender", "preRenderChunkLayer", "()V");
				mv.visitMethodInsn(opcode, owner, name, desc);
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/ShadersRender", "postRenderChunkLayer", "()V");
				return;
			}
			mv.visitMethodInsn(opcode, owner, name, desc);
		}
		
	}

	private static class MVsetupTerrain extends MethodVisitor
	{
		MethodVisitor mvOut;
		
		public MVsetupTerrain(MethodVisitor mv) {
			super(Opcodes.ASM4);
			mvOut = mv;
			setNextState(new State0());
		}
		
		void setNextState(MethodVisitor next) {
			mv = next;
		}

		class MVState extends MethodVisitor {
			public MVState() {
				super(ASM4, MVsetupTerrain.this.mvOut);
			}
			@Override
			public void visitEnd() {
				if (this.getClass()!=StateEnd.class)
					SMCLog.error("  ends in bad state %s",this.getClass().getSimpleName());
				mv.visitEnd();
			}
		}
		
		class State0 extends MVState {
			@Override
			public void visitFieldInsn(int opcode, String owner, String name, String desc) {
				if (Names.renderGlobal_renderDispatcher.equals(owner,name,desc)) {
					mv.visitInsn(POP);
					mv.visitFieldInsn(GETSTATIC, "shadersmod/client/Shaders", "isShadowPass", "Z");
					Label label1 = new Label();
					mv.visitJumpInsn(IFEQ, label1);
					mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/Shaders", "mcProfilerEndSection", "()V");
					mv.visitInsn(RETURN);
					mv.visitLabel(label1);
					mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
					mv.visitVarInsn(ALOAD, 0);
					setNextState(new StateEnd());
				}
				mv.visitFieldInsn(opcode, owner, name, desc);
			}
		}
		class StateEnd extends MVState {
			@Override
			public void visitEnd() {
				mv.visitEnd();
			}
		}
	}
}
