package shadersmod.transform;

import java.util.Iterator;

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;

public class STTextureMap 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;
		boolean endFields = false;
		
		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 (name.equals("atlasWidth")) {
				return null;
			} else if (name.equals("atlasHeight")) {
				return null;
//			} else if (Names.textureMap_anisotropic.name.equals(name)) {
//				access = access & (~ACC_PRIVATE & ~ACC_PROTECTED) | ACC_PUBLIC;
			}
			return super.visitField(access, name, desc, signature, value);
		}

		@Override
		public MethodVisitor visitMethod(int access, String name, String desc,
				String signature, String[] exceptions) 
		{
			if (!endFields) {
				endFields = true;
				FieldVisitor fv;
				fv = cv.visitField(ACC_PUBLIC, "atlasWidth", "I", null, null);
				fv.visitEnd();
				fv = cv.visitField(ACC_PUBLIC, "atlasHeight", "I", null, null);
				fv.visitEnd();
			}
			//SMCLog.info("  method %s.%s%s = %s",classname,name,desc,remappedName);
//			if (Names.textureMap_getIconResLoc.equalsNameDesc(name, desc))
//			{
//				access = access & (~ACC_PRIVATE & ~ACC_PROTECTED) | ACC_PUBLIC;
//			}
//			else
			if (Names.textureMap_loadTexture.equalsNameDesc(name, desc))
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,nameM);
				return new MVloadTexture(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else
			if (Names.textureMap_loadTextureAtlas.equalsNameDesc(name, desc))
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,nameM);
				return new MVloadAtlas(cv.visitMethod(access, name, desc, signature, exceptions));
			}
			else
			if (Names.textureMap_updateAnimations.equalsNameDesc(name, desc))
			{
				//SMCLog.finer("  patching method %s.%s%s = %s",classname,name,desc,nameM);
				return new MVanimation(cv.visitMethod(access, name, desc, signature, exceptions));
			} else if (name.equals("callGetIconResLoc") && desc.equals("("+Names.textureMap_.desc+Names.resourceLocation_.desc+"I)"+Names.resourceLocation_.desc)) {
				return null;
			}
			return cv.visitMethod(access, name, desc, signature, exceptions);
		}

		@Override
		public void visitEnd() {
//			AccessorGenerator.generatePrivateMethodAccessor(cv, "completeResLoc", Names.textureMap_getIconResLoc);
			AccessorGenerator.generateFieldAccessor(cv, "basePath", Names.textureMap_basePath);
//			MethodVisitor mv;
//			/* FIXME name */
//			mv = cv.visitMethod(ACC_PUBLIC + ACC_STATIC, "callGetIconResLoc", "("+Names.textureMap_.desc+Names.resourceLocation_.desc+"I)"+Names.resourceLocation_.desc, null, null);
//			mv.visitCode();
//			mv.visitVarInsn(ALOAD, 0);
//			mv.visitVarInsn(ALOAD, 1);
//			mv.visitVarInsn(ILOAD, 2);
//			mv.visitMethodInsn(INVOKESPECIAL, Names.textureMap_getIconResLoc.clasName, Names.textureMap_getIconResLoc.name, Names.textureMap_getIconResLoc.desc);
//			mv.visitInsn(ARETURN);
//			mv.visitMaxs(3, 3);
//			mv.visitEnd();
			super.visitEnd();
		}
	}
	
	private static class MVloadTexture extends MethodVisitor
	{
		//protected MethodVisitor mv;
		public MVloadTexture(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
			//this.mv = mv;
		}

		@Override
		public void visitCode() {
			super.visitCode();
			mv.visitVarInsn(ALOAD, 1);
			mv.visitFieldInsn(PUTSTATIC, "shadersmod/client/ShadersTex", "resManager", Names.iResourceManager_.desc);
		}

	}

	private static class MVloadAtlas extends MethodVisitor
	{
		//protected MethodVisitor mv;
		public MVloadAtlas(MethodVisitor mv) {
			super(Opcodes.ASM4, mv);
			//this.mv = mv;
		}

		@Override
		public void visitCode() {
			super.visitCode();
			mv.visitVarInsn(ALOAD, 1);
			mv.visitFieldInsn(PUTSTATIC, "shadersmod/client/ShadersTex", "resManager", Names.iResourceManager_.desc);
		}
		
		@Override
		public void visitIntInsn(int opcode, int operand) {
			if (opcode == NEWARRAY && operand == T_INT) {
				mv.visitInsn(ICONST_3);
				mv.visitInsn(IMUL);
			}
			mv.visitIntInsn(opcode, operand);
		}

		int varStitcher = 0;
		boolean isStitcher = false;
		int state = 0;
		
		@Override
		public void visitVarInsn(int opcode, int var) {
			if (opcode==ASTORE) {
				if (isStitcher) {
					isStitcher = false;
					varStitcher = var;
				}
			}
			mv.visitVarInsn(opcode, var);
		}

		@Override
		public void visitMethodInsn(int opcode, String owner, String name,String desc) {
			//SMCLog.finest("    %s.%s%s",ownerM,nameM,descM);
			if (opcode==INVOKESPECIAL && Names.equals(Names.stitcher_.clasName,"<init>","(IIII)V",owner,name,desc)) {
				isStitcher = true;
			}
			else if (Names.iResourceManager_getResource.equals(owner,name,desc)) {
				SMCLog.finest("    %s","loadRes");
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/ShadersTex", "loadResource", "("+Names.iResourceManager_.desc+Names.resourceLocation_.desc+")"+Names.iResource_.desc);
				return;
			} 
			else if (Names.textureUtil_allocateTextureImpl.equals(owner,name,desc)) {
				SMCLog.finest("    %s","allocateTextureMap");
				mv.visitVarInsn(ALOAD, varStitcher);
				mv.visitVarInsn(ALOAD, 0);
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/ShadersTex", "allocateTextureMap", "(IIII"+Names.stitcher_.desc+Names.textureMap_.desc+")V");
				state = 1;
				return;
			}
			else if (state==1 &&
					Names.textureAtlasSpri_getIconName.equals(owner,name,desc)) {
				SMCLog.finest("    %s","setSprite setIconName");
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/ShadersTex", "setSprite", "("+Names.textureAtlasSpri_.desc+")"+Names.textureAtlasSpri_.desc);
				mv.visitMethodInsn(opcode, owner, name, desc);
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/ShadersTex", "setIconName", "(Ljava/lang/String;)Ljava/lang/String;");
				state = 0;
				return;
			}
			else if (Names.textureUtil_uploadTexSub.equals(owner,name,desc)) {
				SMCLog.finest("    %s","uploadTexSubForLoadAtlas");
				mv.visitMethodInsn(INVOKESTATIC, "shadersmod/client/ShadersTex", "uploadTexSubForLoadAtlas", "([[IIIIIZZ)V");
				return;
			}
			mv.visitMethodInsn(opcode, owner, name, desc);
		}

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

		@Override
		public void visitCode() {
			mv.visitCode();
			mv.visitVarInsn(ALOAD, 0);
			mv.visitMethodInsn(INVOKEVIRTUAL, Names.textureMap_.clasName, "getMultiTexID", "()Lshadersmod/client/MultiTexID;");
			mv.visitFieldInsn(PUTSTATIC, "shadersmod/client/ShadersTex", "updatingTex", "Lshadersmod/client/MultiTexID;");
		}

		@Override
		public void visitInsn(int opcode) {
			if (opcode==RETURN) {
				mv.visitInsn(ACONST_NULL);
				mv.visitFieldInsn(PUTSTATIC, "shadersmod/client/ShadersTex", "updatingTex", "Lshadersmod/client/MultiTexID;");
			}
			mv.visitInsn(opcode);
		}

	}

}
