/**
 * Copyright (C) 2016 - 2030 youtongluan.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.yx.asm;

import static org.objectweb.asm.Opcodes.AASTORE;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ANEWARRAY;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.BIPUSH;
import static org.objectweb.asm.Opcodes.DLOAD;
import static org.objectweb.asm.Opcodes.DSTORE;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.FLOAD;
import static org.objectweb.asm.Opcodes.FSTORE;
import static org.objectweb.asm.Opcodes.ICONST_0;
import static org.objectweb.asm.Opcodes.ICONST_M1;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.ISTORE;
import static org.objectweb.asm.Opcodes.LLOAD;
import static org.objectweb.asm.Opcodes.LSTORE;
import static org.objectweb.asm.Opcodes.SIPUSH;

import org.objectweb.asm.MethodVisitor;
import org.yx.conf.AppInfo;

public class WriterHelper {
	public static final int SINGLE = AppInfo.getInt("sumk.aop.single", 1);
	public static final int WIDTH = AppInfo.getInt("sumk.aop.width", 2);

	public static void visitInt(MethodVisitor mv, int num) {
		if (num == -1) {
			mv.visitInsn(ICONST_M1);
			return;
		}
		if (num >= 0 && num <= 5) {
			mv.visitInsn(ICONST_0 + num);
			return;
		}
		if (num <= Byte.MAX_VALUE && num >= Byte.MIN_VALUE) {
			mv.visitIntInsn(BIPUSH, num);
			return;
		}
		if (num <= Short.MAX_VALUE && num >= Short.MIN_VALUE) {
			mv.visitIntInsn(SIPUSH, num);
			return;
		}
		mv.visitLdcInsn(num);
	}

	public static void buildParamArray(MethodVisitor mv, Class<?>[] params) {
		final int len = params == null ? 0 : params.length;
		visitInt(mv, len);

		mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
		if (len == 0) {
			return;
		}
		int frameIndex = 1;
		for (int i = 0; i < len; i++) {
			mv.visitInsn(DUP);
			visitInt(mv, i);
			Class<?> argType = params[i];
			frameIndex += loadFromLocalVariable(mv, argType, frameIndex, true);
			mv.visitInsn(AASTORE);
		}
	}

	public static int loadFromLocalVariable(MethodVisitor mv, Class<?> c, int frameIndex, boolean autoBox) {
		if (c == Integer.TYPE) {
			mv.visitVarInsn(ILOAD, frameIndex);
			if (autoBox) {
				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
			}
			return SINGLE;
		}
		if (c == Boolean.TYPE) {
			mv.visitVarInsn(ILOAD, frameIndex);
			if (autoBox) {
				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
			}
			return SINGLE;
		}
		if (c == Byte.TYPE) {
			mv.visitVarInsn(ILOAD, frameIndex);
			if (autoBox) {
				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
			}
			return SINGLE;
		}
		if (c == Character.TYPE) {
			mv.visitVarInsn(ILOAD, frameIndex);
			if (autoBox) {
				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
			}
			return SINGLE;
		}
		if (c == Short.TYPE) {
			mv.visitVarInsn(ILOAD, frameIndex);
			if (autoBox) {
				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
			}
			return SINGLE;
		}
		if (c == Float.TYPE) {
			mv.visitVarInsn(FLOAD, frameIndex);
			if (autoBox) {
				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
			}
			return SINGLE;
		}
		if (c == Double.TYPE) {
			mv.visitVarInsn(DLOAD, frameIndex);
			if (autoBox) {
				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
			}
			return WIDTH;
		}
		if (c == Long.TYPE) {
			mv.visitVarInsn(LLOAD, frameIndex);
			if (autoBox) {
				mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
			}
			return WIDTH;
		}

		mv.visitVarInsn(ALOAD, frameIndex);
		return SINGLE;

	}

	public static int boxPrimitive(MethodVisitor mv, Class<?> c) {
		if (c == Integer.TYPE) {
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
			return SINGLE;
		}
		if (c == Boolean.TYPE) {
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
			return SINGLE;
		}
		if (c == Byte.TYPE) {
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
			return SINGLE;
		}
		if (c == Character.TYPE) {
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
			return SINGLE;
		}
		if (c == Short.TYPE) {
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
			return SINGLE;
		}
		if (c == Float.TYPE) {
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
			return SINGLE;
		}
		if (c == Double.TYPE) {
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
			return WIDTH;
		}
		if (c == Long.TYPE) {
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
			return WIDTH;
		}
		return 0;

	}

	public static int storeToLocalVariable(MethodVisitor mv, Class<?> c, int frameIndex) {
		if (c == Integer.TYPE || c == Boolean.TYPE || c == Byte.TYPE || c == Character.TYPE || c == Short.TYPE) {
			mv.visitVarInsn(ISTORE, frameIndex);
			return SINGLE;
		}
		if (c == Float.TYPE) {
			mv.visitVarInsn(FSTORE, frameIndex);
			return SINGLE;
		}
		if (c == Double.TYPE) {
			mv.visitVarInsn(DSTORE, frameIndex);
			return WIDTH;
		}
		if (c == Long.TYPE) {
			mv.visitVarInsn(LSTORE, frameIndex);
			return WIDTH;
		}

		mv.visitVarInsn(ASTORE, frameIndex);
		return SINGLE;

	}

	public static String boxDesc(String desc) {
		switch (desc.charAt(0)) {
		case 'Z':
			return "Ljava/lang/Boolean;";
		case 'C':
			return "Ljava/lang/Character;";
		case 'B':
			return "Ljava/lang/Byte;";
		case 'S':
			return "Ljava/lang/Short;";
		case 'I':
			return "Ljava/lang/Integer;";
		case 'F':
			return "Ljava/lang/Float;";
		case 'J':
			return "Ljava/lang/Long;";
		case 'D':
			return "Ljava/lang/Double;";
		default:
			return desc;
		}
	}
}